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

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

## 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


# つくる
## サンプルダウンロード
おなじみのサンプルを使用します。
任意のディレクトリで実行してください。

```terminal:いつものやつをダウンロード
// 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

```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"`
}
```

```go:適当に定数を作成しておきます
const COLLECTIONS="_implicit_org_Org1MSP"
```

```go: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)
}
```

```go: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を呼び出すアプリを作成（コピーして修正）します。

```terminal
cp invoke.js privateInvoke.js
cp query.js privateQuery.js
```

```javascript: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();
```

```javascript: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 実行

```terminal
// change directory
# cd fabric-sample/fabcar

// Start HLF
# ./startFabric
```

### admin & user 作成

```terminal
// change directory
# cd javascript

// npm install
# npm install

// enroll admin & register user
# node enrollAdmin.js
# node registerUser.js
```

### PrivateData 取得 → 登録 → 取得

```terminal
// 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 取得

```terminal
# 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にはアクセス権がありません。
挙動を確認してみましょう。

```terminal
# 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のコンテナで実施します。

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

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