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


## 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ブロックにする

```json:collections_config.json
[
 {
    "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ファイルを開きます
```

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


```diff:Invoke関数に、PrivateDataを保存、取得する関数を呼べるように修正します
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.")
}
```

```golang:fabcar.go：PrivateDataを保存する関数を作成します
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)
}
```

```golang:fabcar.go：PrivateDataを取得する関数を作成します
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の修正は終了です。
全量は下記を御覧ください。

<details><summary>全量（不要なコメントは削除しています）</summary><div>

```golang:fabcar.go
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)
        }
}
```
</div></details>


以上で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行目の末尾に「\」を追記する事も忘れずに！

```diff:
     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
　- コピーしたファイルを開きます
```

```diff
'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
　- コピーしたファイルを開きます
```

```diff
'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はチャネルと同様に重要な概念となるので、しっかりと抑えておきたいところです。
（肝に銘じる）

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