1
1

More than 3 years have passed since last update.

【Hyperledger Fabric v2.0】PrivateDataを使ってみよう

Posted at

はじめに

あけましておめでとうございます(ぇ
どーも、のぶこふです。

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 の挙動等を設定する場合には、必要になります。
ケースバイケースで、要否に応じて、作成する/しないを決めれば良いと思います。

▼公式ドキュメント
https://hyperledger-fabric.readthedocs.io/en/latest/private-data-arch.html#referencing-implicit-collections-from-chaincode

Chaincode修正

PrivateDataに保存、取得ができるように、CCを修正します。

fabcar.go

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"
fabcar.go:PrivateDataを保存する関数を作成します
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)
}
fabcar.go:PrivateDataを取得する関数を作成します
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
privateInvoke.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();
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');

        // 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の使用が推進されそうですね。

今回はここまでです。
ありがとうございました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1