はじめに
あけましておめでとうございます(ぇ
どーも、のぶこふです。
ICE BREAK
日本時間 2020/01/30(木)7:00頃 に HLF v2.0 が正式リリースされましたね。
私はGithubに張り付いていたのでリリースされた瞬間にTwitterで発信しましたが、
いかんせん私のフォロワーが少ないので、拡散するには力が及びませんでした。
とまぁ、そんなことは置いておいて、
HLF v2.0 の目玉機能といえば
「Private Data」ですね(たぶん
※どんな変更点があるか等は、他の方がまとめてくださっているので、
そちらをご参考願います。ワタシ エイゴ シャベレマセーン
▼コチラとか
https://gakumura.hatenablog.com/entry/2020/01/30/180540
「おいおい、のぶこふさんよぉ。Private Dataなら、昔からあったぜぇ?」
はい、そうです。ありました。
なんなら、使い方をまとめた記事も書きました。
▼【Hyperledger Fabric】PrivateDataを使ってみよう
https://qiita.com/nobkovskii/items/a46160d1c1ba80d0fdb7
v2.0になり、よりお手軽にPrivate Dataを扱えるようになりました。
HLFがPrivate Data推しになり、
なんとなくCordaっぽくなり始めてる感があります。
もう、わけわからんです。
閑話休題
ということで、今回は「v2.0でPrivate Dataを扱う」に焦点を当てて説明していきます。
v1.4系なら、マーブルにサンプルがあるのですが、
v2.0系は、まだサンプルが公式には出ていないので、参考になればと思います。
今回も、おなじみのFabcarをいじくり回します。
※なお、修正内容については、前回記事と同様です。
想定読者・ゴール・環境
- 想定読者
- HLFのPrivateDataを使いたい人
- ゴール
- HLF v2.0 でPrivateDataが実装できるようになる
- 環境
- HLF v2.0
- CentOS7 On Oracle VM VirtualBox On Windows10
- Chaincode:Golang
つくる
サンプルダウンロード
おなじみのサンプルを使用します。
任意のディレクトリで実行してください。
// get hlf-sample
# curl -sSL https://bit.ly/2ysbOFE | bash -s
【不要】collections_config.jsonを作成
v2.0 では、collections_config.jsonは不要になりました。
詳しい説明は公式ドキュメントをご参照ください。
ただし、Private Data の挙動等を設定する場合には、必要になります。
ケースバイケースで、要否に応じて、作成する/しないを決めれば良いと思います。
Chaincode修正
PrivateDataに保存、取得ができるように、CCを修正します。
fabcar.go
// Private Data Collection
type CarPrivate struct {
Make string `json:"make"`
Model string `json:"model"`
Colour string `json:"colour"`
Owner string `json:"owner"`
Price string `json:"price"`
}
const COLLECTIONS="_implicit_org_Org1MSP"
func (s *SmartContract) CreatePrivateCar(ctx contractapi.TransactionContextInterface, carNumber string, make string, model string, colour string, owner string, price string) error {
car := CarPrivate{
Make: make,
Model: model,
Colour: colour,
Owner: owner,
Price: price,
}
carAsBytes, _ := json.Marshal(car)
return ctx.GetStub().PutPrivateData(COLLECTIONS, carNumber, carAsBytes)
}
func (s *SmartContract) QueryPrivateCar(ctx contractapi.TransactionContextInterface, carNumber string) (*CarPrivate, error) {
carAsBytes, err := ctx.GetStub().GetPrivateData(COLLECTIONS,carNumber)
if err != nil {
return nil, fmt.Errorf("Failed to read from world state. %s", err.Error())
}
if carAsBytes == nil {
return nil, fmt.Errorf("%s does not exist", carNumber)
}
car := new(CarPrivate)
_ = json.Unmarshal(carAsBytes, car)
return car, nil
}
【不要】インスタンス化時にcollections_config.jsonを読み込むようにする
読み込みも不要になりました。
そもそも、v2.0ではインスタンス化の概念がなくなりましたね。
collections_config.jsonを読み込む場合は、Chaincodeを「approveformyorg」「commit」する際に「--collections-config」と「collections_config.jsonのパス」を指定します。
Chaincode呼び出し元アプリ作成
作成したChaincodeを呼び出すアプリを作成(コピーして修正)します。
cp invoke.js privateInvoke.js
cp query.js privateQuery.js
/*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = 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 = await Wallets.newFileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const identity = await wallet.get('user1');
if (!identity) {
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.
// <<<<<<<<<<<<<<<< ADD >>>>>>>>>>>>>>>
await contract.submitTransaction('createPrivateCar', '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();
/*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = 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 = await Wallets.newFileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const identity = await wallet.get('user1');
if (!identity) {
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.
// <<<<<<<<<<<<<<<< ADD >>>>>>>>>>>>>>>
const result = await contract.evaluateTransaction('queryPrivateCar', 'CAR12');
console.log(`Transaction has been evaluated, result is: ${result.toString()}`);
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
process.exit(1);
}
}
main();
動かす
startFabric.sh 実行
// change directory
# cd fabric-sample/fabcar
// Start HLF
# ./startFabric
admin & user 作成
// change directory
# cd javascript
// npm install
# npm install
// enroll admin & register user
# node enrollAdmin.js
# node registerUser.js
PrivateData 取得 → 登録 → 取得
// query ledger
# node privateQuery.js
warn: [Query]: evaluate: Query ID "[object Object]" of peer "peer0.org1.example.com:7051" failed: message=CAR12 does not exist, stack=Error: CAR12 does not exist
// ↑ Private Data に未登録のため、Warningが発生。
// invoke ledger
# node privateInvoke.js
Transaction has been submitted
// ↑ 無事に登録
// query ledger
# node privateQuery.js
Transaction has been evaluated, result is: {"make":"Honda","model":"Accord","colour":"Black","owner":"Tom","price":"1000"}
// ↑ 登録した内容が表示された
StateDB 取得
# node query.js
Transaction has been evaluated, result is: [{"Key":"CAR0","Record":{"make":"Toyota","model":"Prius","colour":"blue","owner":"Tomoko"}},{"Key":"CAR1","Record":{"make":"Ford","model":"Mustang","colour":"red","owner":"Brad"}},{"Key":"CAR2","Record":{"make":"Hyundai","model":"Tucson","colour":"green","owner":"Jin Soo"}},{"Key":"CAR3","Record":{"make":"Volkswagen","model":"Passat","colour":"yellow","owner":"Max"}},{"Key":"CAR4","Record":{"make":"Tesla","model":"S","colour":"black","owner":"Adriana"}},{"Key":"CAR5","Record":{"make":"Peugeot","model":"205","colour":"purple","owner":"Michel"}},{"Key":"CAR6","Record":{"make":"Chery","model":"S22L","colour":"white","owner":"Aarav"}},{"Key":"CAR7","Record":{"make":"Fiat","model":"Punto","colour":"violet","owner":"Pari"}},{"Key":"CAR8","Record":{"make":"Tata","model":"Nano","colour":"indigo","owner":"Valeria"}},{"Key":"CAR9","Record":{"make":"Holden","model":"Barina","colour":"brown","owner":"Shotaro"}}]
// ↑ Private Data に登録した「"Key":"CAR12"」は無い
別Org(Org2)で確認
今回、Private Data を登録したのは、Org1です。
Org2にはアクセス権がありません。
挙動を確認してみましょう。
# docker exec -it cli sh
- cliコンテナに入ります
- 以下、cliコンテナ内
// Org2 用の環境変数設定
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":["queryPrivateCar","CAR12"]}'
Error: endorsement failure during query. response: status:500 message:"Failed to read from world state. GET_STATE failed: transaction ID: 0d650b361164a8f69aef1e0cae6092ec1ea7292c93cff8d4d7ca34540d0541c8: private data matching public hash version is not available. Public hash version = {BlockNum: 7, TxNum: 0}, Private data version = <nil>"
// ↑ エラーが返ってきました
正常Org(Org1)で確認
続いて、Org1でも確認してみます。
cliのコンテナで実施します。
// 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":["queryPrivateCar","CAR12"]}'
{"make":"Honda","model":"Accord","colour":"Black","owner":"Tom","price":"1000"}
// 登録した内容が表示された
宿題
collections_config.json を使用してみる
HLFv2.0から、collections_config.jsonを使用しなくてもPrivate Dataを扱えるようになりました。
しかし、Private Data を柔軟に扱うためには、collections_config.jsonはかかせません。たぶん。
だけど、ちょっと調べてみると、"_implicit_org_Org1MSP"を使ってしまうと、collections_config.jsonは参照されなくなるっぽいな・・・
ここらへん、どうしたら良いんだろ?
調査続けます。
終わりに
以前(v1.4系)よりも、よりお手軽にPrivate Dataを扱うことができるようになりました。
ますますPrivate Dataの使用が推進されそうですね。
今回はここまでです。
ありがとうございました。