1.はじめに
12月になりましたね。寒いです。
AdventCalendarとは無縁の、どーも、のぶこふです。
今回は、Hyperledger Fabric(HLF)のPrivate Data Collections(プライベート データ コレクション:PrivateData)の使用方法について書きます。
「PrivateData?」という方は、公式ドキュメントをご参照ください。
▼公式ドキュメント
https://hyperledger-fabric.readthedocs.io/en/release-1.4/private-data/private-data.html
ざっくり説明すると「許可したPeer間でTxを共有するので、許可されていないPeerはTxの内容を見ることができませんよ」というものです。
- 「チャネルでも同じようにTxの中身が見れないけど、違いはなんやねん?」
- 「どこに保存されんねん?」
- などなど、疑問が湧くかもしれませんが、公式ドキュメントや他の方が説明してくださっているので、私からは割愛させていただきます。
oO(ここで、少し興味を持ってくれたら嬉しい)
- などなど、疑問が湧くかもしれませんが、公式ドキュメントや他の方が説明してくださっているので、私からは割愛させていただきます。
1.1.想定読者
- HLFのPrivateDataを使いたい人
1.2.ゴール
- HLFでPrivateDataが実装できるようになる
1.3.環境
- HLF v1.4.4
- CentOS7 On Oracle VM VirtualBox On Windows10
- Chaincode:Golang
という環境で、今回の記事をお送りいたします。
2.つくる
marbleだと、すでにサンプルがあるのですが、今回は1から作成するということで、fabcarを修正していきます。
2.1.サンプルダウンロード
おなじみのサンプルを使用します。
# cd /opt
- /opt に移動します
- 任意のディレクトリで構いません
# curl -sSL http://bit.ly/2ysbOFE | bash -s 1.4.4
- ダウンロードが行われます
- 失敗した場合は、proxyの設定などを見直してみてください
# ls -l
- 「fabric-samples」があることを確認してください
# cd fabric-samples/chaincode/fabcar/go
- fabric-samples/chaincode/fabcar/go のフォルダまで移動します
2.2.collections_config.jsonの作成
collections_config.jsonを作成します。
collections_config.jsonは、その名の通り、PrivateDataの定義ファイルです。
PrivateDataの名前やポリシー、ブロックの存続時間などを設定できます。
詳細は「公式ドキュメント」をご参照ください。
▼公式ドキュメント
https://hyperledger-fabric.readthedocs.io/en/release-1.4/private-data-arch.html
# vi collections_config.json
- collections_config.jsonファイルを開きます(新規作成)
collections_config.jsonの中身は、次のようにします。
おおまかな内容としては、下記の通りです。
- collectionCars という名前にする
- Org1MSP.memberのみ許可する
- ブロックの生存時間は、10ブロックにする
[
{
"name": "collectionCars",
"policy": "OR('Org1MSP.member')",
"requiredPeerCount": 0,
"maxPeerCount": 3,
"blockToLive":10,
"memberOnlyRead": true
}
]
2.3.Chaincode(CC)修正
PrivateDataに保存、取得ができるように、CCを修正します。
2.3.1.fabcar.go
# vi fabcar.go
- fabcar.goファイルを開きます
// PrivateData用の構造体を定義します
// 通常のCarとは異なり「Price」を追加しています
type CarPrivate struct {
Make string `json:"make"`
Model string `json:"model"`
Colour string `json:"colour"`
Owner string `json:"owner"`
Pirce string `json:"price"`
}
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
// Retrieve the requested Smart Contract function and arguments
function, args := APIstub.GetFunctionAndParameters()
// Route to the appropriate handler function to interact with the ledger appropriately
if function == "queryCar" {
return s.queryCar(APIstub, args)
} else if function == "initLedger" {
return s.initLedger(APIstub)
} else if function == "createCar" {
return s.createCar(APIstub, args)
} else if function == "queryAllCars" {
return s.queryAllCars(APIstub)
} else if function == "changeCarOwner" {
return s.changeCarOwner(APIstub, args)
+ } else if function == "setPrivateData" {
+ return s.setPrivateData(APIstub, args)
+ } else if function == "getPrivateData" {
+ return s.getPrivateData(APIstub, args)
+ }
return shim.Error("Invalid Smart Contract function name.")
}
func (s *SmartContract) setPrivateData(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 6 {
return shim.Error("Incorrect number of arguments. Expecting 6")
}
var car = CarPrivate{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4], Price:args[5]}
carAsBytes, _ := json.Marshal(car)
err := APIstub.PutPrivateData("collectionCars", args[0], carAsBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
func (s *SmartContract) getPrivateData(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
var jsonResp string
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
carAsBytes, err := APIstub.GetPrivateData("collectionCars", args[0])
if err != nil {
jsonResp = "{\"Error\":\"Failed to get state for " + args[0] + "\"}"
return shim.Error(jsonResp)
} else if carAsBytes == nil {
jsonResp = "{\"Error\":\"Car does not exist: " + args[0] + "\"}"
return shim.Error(jsonResp)
}
return shim.Success(carAsBytes)
}
以上でCCの修正は終了です。
全量は下記を御覧ください。
全量(不要なコメントは削除しています)
package main
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
sc "github.com/hyperledger/fabric/protos/peer"
)
// Define the Smart Contract structure
type SmartContract struct {
}
// Define the car structure, with 4 properties. Structure tags are used by encoding/json library
type Car struct {
Make string `json:"make"`
Model string `json:"model"`
Colour string `json:"colour"`
Owner string `json:"owner"`
}
// <<<<<< ADD >>>>>
type CarPrivate struct {
Make string `json:"make"`
Model string `json:"model"`
Colour string `json:"colour"`
Owner string `json:"owner"`
Price string `json:"price"`
}
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
return shim.Success(nil)
}
// <<<<<< MOD >>>>>
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
// Retrieve the requested Smart Contract function and arguments
function, args := APIstub.GetFunctionAndParameters()
// Route to the appropriate handler function to interact with the ledger appropriately
if function == "queryCar" {
return s.queryCar(APIstub, args)
} else if function == "initLedger" {
return s.initLedger(APIstub)
} else if function == "createCar" {
return s.createCar(APIstub, args)
} else if function == "queryAllCars" {
return s.queryAllCars(APIstub)
} else if function == "changeCarOwner" {
return s.changeCarOwner(APIstub, args)
} else if function == "setPrivateData" {
return s.setPrivateData(APIstub, args)
} else if function == "getPrivateData" {
return s.getPrivateData(APIstub, args)
}
return shim.Error("Invalid Smart Contract function name.")
}
func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
carAsBytes, _ := APIstub.GetState(args[0])
return shim.Success(carAsBytes)
}
func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
cars := []Car{
Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
}
i := 0
for i < len(cars) {
fmt.Println("i is ", i)
carAsBytes, _ := json.Marshal(cars[i])
APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
fmt.Println("Added", cars[i])
i = i + 1
}
return shim.Success(nil)
}
func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 5 {
return shim.Error("Incorrect number of arguments. Expecting 5")
}
var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]}
carAsBytes, _ := json.Marshal(car)
APIstub.PutState(args[0], carAsBytes)
return shim.Success(nil)
}
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {
startKey := "CAR0"
endKey := "CAR999"
resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close()
// buffer is a JSON array containing QueryResults
var buffer bytes.Buffer
buffer.WriteString("[")
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"Key\":")
buffer.WriteString("\"")
buffer.WriteString(queryResponse.Key)
buffer.WriteString("\"")
buffer.WriteString(", \"Record\":")
// Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")
fmt.Printf("- queryAllCars:\n%s\n", buffer.String())
return shim.Success(buffer.Bytes())
}
func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
carAsBytes, _ := APIstub.GetState(args[0])
car := Car{}
json.Unmarshal(carAsBytes, &car)
car.Owner = args[1]
carAsBytes, _ = json.Marshal(car)
APIstub.PutState(args[0], carAsBytes)
return shim.Success(nil)
}
// <<<<<< ADD >>>>>
func (s *SmartContract) setPrivateData(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 6 {
return shim.Error("Incorrect number of arguments. Expecting 6")
}
var car = CarPrivate{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4], Price:args[5]}
carAsBytes, _ := json.Marshal(car)
err := APIstub.PutPrivateData("collectionCars", args[0], carAsBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
// <<<<<< ADD >>>>>
func (s *SmartContract) getPrivateData(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
var jsonResp string
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
carAsBytes, err := APIstub.GetPrivateData("collectionCars", args[0])
if err != nil {
jsonResp = "{\"Error\":\"Failed to get state for " + args[0] + "\"}"
return shim.Error(jsonResp)
} else if carAsBytes == nil {
jsonResp = "{\"Error\":\"Car does not exist: " + args[0] + "\"}"
return shim.Error(jsonResp)
}
return shim.Success(carAsBytes)
}
// The main function is only relevant in unit test mode. Only included here for completeness.
func main() {
// Create a new Smart Contract
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error creating new Smart Contract: %s", err)
}
}
以上でCCの修正は完了です。
それでは実行・・・いえいえ、まだ大事な作業があります。
2.4.インスタンス化時にcollections_config.jsonを読み込むようにする
PrivateDataを使用するには、先程作成したcollections_config.jsonを読み込むようにする必要があります。
読み込みのタイミングは、インスタンス化を行うタイミングです。
# cd ../../../fabcar/
- /opt/fabric-samples/fabcar/ まで移動します
# vi startFabric.sh
- startFabric.sh を開きます
インスタンス化を行っている箇所で、collections_config.jsonを読み込むように加筆します。
98行目の末尾に「\」を追記する事も忘れずに!
82 echo "Instantiating smart contract on mychannel"
83 docker exec \
84 -e CORE_PEER_LOCALMSPID=Org1MSP \
85 -e CORE_PEER_MSPCONFIGPATH=${ORG1_MSPCONFIGPATH} \
86 cli \
87 peer chaincode instantiate \
88 -o orderer.example.com:7050 \
89 -C mychannel \
90 -n fabcar \
91 -l "$CC_RUNTIME_LANGUAGE" \
92 -v 1.0 \
93 -c '{"Args":[]}' \
94 -P "AND('Org1MSP.member','Org2MSP.member')" \
95 --tls \
96 --cafile ${ORDERER_TLS_ROOTCERT_FILE} \
97 --peerAddresses peer0.org1.example.com:7051 \
+ 98 --tlsRootCertFiles ${ORG1_TLS_ROOTCERT_FILE} \
+ 99 --collections-config /opt/gopath/src/github.com/chaincode/fabcar/go/collections_config.json
2.5.CC呼び出し元アプリ作成
最後にCCを呼び出すアプリを作成します。
と言っても、すでにあるのを複製して作成します。
まずはPrivateDataを保存するCCを呼び出すアプリです。
# cd javascript/
- javascriptフォルダに移動します
# cp invoke.js setPrivateData.js
- invoke.js をコピーします
# vi setPrivateData.js
- コピーしたファイルを開きます
'use strict';
const { FileSystemWallet, Gateway } = require('fabric-network');
const path = require('path');
const ccpPath = path.resolve(__dirname, '..', '..', 'first-network', 'connection-org1.json');
async function main() {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('fabcar');
// Submit the specified transaction.
// createCar transaction - requires 5 argument, ex: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom')
// changeCarOwner transaction - requires 2 args , ex: ('changeCarOwner', 'CAR10', 'Dave')
- await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
+ await contract.submitTransaction('setPrivateData', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom', '1000' );
console.log('Transaction has been submitted');
// Disconnect from the gateway.
await gateway.disconnect();
} catch (error) {
console.error(`Failed to submit transaction: ${error}`);
process.exit(1);
}
}
main();
続いて、PrivateDataを取得するアプリです。
# cp query.js getPrivateData.js
- query.jsをコピーします
# vi getPrivateData.js
- コピーしたファイルを開きます
'use strict';
const { FileSystemWallet, Gateway } = require('fabric-network');
const path = require('path');
const ccpPath = path.resolve(__dirname, '..', '..', 'first-network', 'connection-org1.json');
async function main() {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('fabcar');
// Evaluate the specified transaction.
// queryCar transaction - requires 1 argument, ex: ('queryCar', 'CAR4')
// queryAllCars transaction - requires no arguments, ex: ('queryAllCars')
- const result = await contract.evaluateTransaction('queryAllCars');
+ const result = await contract.evaluateTransaction('getPrivateData', 'CAR12');
console.log(`Transaction has been evaluated, result is: ${result.toString()}`);
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
process.exit(1);
}
}
main();
これで、ようやく準備が整いました。
それでは実行してみましょう。
3.うごかす
3.1.startFabric.sh実行
# cd ../
- ディレクトリを移動します
# ls -l
- startFabric.sh があることを確認します
# ./startFabric.sh
- Errorが発生せずに終了すること
3.2.admin&user1作成
# cd javascript/
- javascriptフォルダに移動します
# npm install
- 不足しているライブラリをインストールします
- エラーが出た場合は、メッセージに則って対応します
- 私の場合は「nycをインストールしてください」と出たので、下記コマンドでインストールしています
# npm audit
# npm install --save-dev nyc@14.1.1
# node enrollAdmin.js
- エラーが発生しないこと
# node registerUser.js
- エラーが発生しないこと
# ls -l wallet/
- adminとuser1のフォルダが作成されていること
3.3.PrivateData保存、取得
# node setPrivateData.js
- Transaction has been submitted
# node getPrivateData.js
- Transaction has been evaluated, result is: {"colour":"Black","make":"Honda","model":"Accord","owner":"Tom","price":"1000"}
無事に保存、取得が行えたようですね。
3.4.StateDBの取得
続いて、普通のStateDBへ取得を行ってみましょう。
# node query.js
- Transaction has been evaluated, result is: [{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}},{"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}},{"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},{"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}},{"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}},{"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}},{"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}},{"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}},{"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}},{"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]
PrivateDataに保存した「"Key":"CAR12"」がありませんね。
3.5.別ユーザで取得
デフォルトで作成すると、user1はOrg1の所属です。
Org2でも試してみます。
# docker exec -it cli bash
- cliコンテナに入ります
- 以下、cliコンテナ内
CHANNEL_NAME=mychannel
ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
PEER0_ORG1_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
PEER0_ORG2_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
CORE_PEER_LOCALMSPID="Org2MSP"
CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG2_CA
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
CORE_PEER_ADDRESS=peer0.org2.example.com:9051
- 環境変数を設定
peer chaincode query -C $CHANNEL_NAME -n fabcar -c '{"Args":["getPrivateData","CAR12"]}'
- Error: endorsement failure during query. response: status:500 message:"{\"Error\":\"Failed to get state for CAR12\"}"
エラーが返ってきました。
念の為、Org1でも試してみましょう。
CHANNEL_NAME=mychannel
ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
PEER0_ORG1_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
PEER0_ORG2_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
CORE_PEER_LOCALMSPID="Org1MSP"
CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG1_CA
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
CORE_PEER_ADDRESS=peer0.org1.example.com:7051
- 環境変数を設定
peer chaincode query -C $CHANNEL_NAME -n fabcar -c '{"Args":["getPrivateData","CAR12"]}'
- {"colour":"Black","make":"Honda","model":"Accord","owner":"Tom","price":"1000"}
想定通りの結果が返ってきました。
3.6.ブロックの生存時間を確認する
ブロックの生存時間は10と設定していました。
Txを大量に発行して、ブロックを進めてみます。
peer channel fetch newest -o $ORDERER_CA -c mychannel last.block
- ブロック番号を確認
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n fabcar -c '{"Args":["changeCarOwner","CAR1", "Nobkov"]}'
- 大量にTx発行(ブロックを進める)
peer channel fetch newest -o $ORDERER_CA -c mychannel last.block
- ブロック番号を確認
peer chaincode query -C $CHANNEL_NAME -n fabcar -c '{"Args":["getPrivateData","CAR12"]}'
- Error: endorsement failure during query. response: status:500 message:"{\"Error\":\"Car does not exist: CAR12\"}"
削除されているので、参照ができなくなりました。
4.おわりに
さて、いかがでしたでしょうか。
CCの実装も特に大きく変えることなく、PrivateDataへ保存・取得する事ができました。
また、生存時間の確認も行えましたね。
PrivateDataはチャネルと同様に重要な概念となるので、しっかりと抑えておきたいところです。
(肝に銘じる)
今回はここまでです。
ありがとうございました。