Hyperledger FabricのGetHistoryForKeyを利用した資産のトレース

#マイカーの一生をトレースしましょう
Hyperledger Fabric(以下HF)の`GetHistoryForKey`を使って、中古で買ったマイカー「ミラ・キータ」の購入から廃車までをトレースしました。シナリオは次の通り。

1. Mira Qiitaの中古車を購入
2. 調子が悪いのでディーラーへ持ち込み
3. ディーラーから工場送り
4. ディーラーへ戻ってくる
5. 我が家へ戻ってくる
6. また調子が悪くなってディーラーに引き取ってもらうことに
7. とても古くて廃車送りに
8. 廃車のために工場送り
9. さようなら。。

# 環境について
動作環境については次の通りです。前回の投稿と同じですね。

Ubuntu 18.04.4 LTS
docker-compose 1.26.0
docker 19.03.11
HF 2.1.1
go 1.14.4
PostgreSQL(Dockerイメージ) 12.3(latest)

# データ構造を考える
あまり考えることもなく、`GetHistoryForKey`から得られる情報を構造体へ定義しました。

```go:asset.go
// データ構造の定義
type Asset struct {
        Year     string `json:"year"`     // 初度登録年
        Month    string `json:"month"`    // 初度登録月
        Mileage  int    `json:"mileage"`  // 走行距離(km)
        Battery  int    `json:"battery"`  // バッテリーライフ(%)
        Location string `jasn:"location"` // 位置
}
// クエリ結果（レーコード検索）
type QueryResult struct {
        Key     string          // レコードID(VINコード)
        Record  *Asset
}
// クエリ結果（履歴検索）
type GetHisResult struct {
        TxId      string        // トランザクションID
        Timestamp string        // タイムスタンプ
        IsDelete  bool          // 削除(廃車)フラグ
        Record    *Asset
}
```
前回の投稿から`GetHisResult`を追加しました。追加項目は`GetHistoryForKey`のResultから参照できるものです。あと、Asset構造体へ`Location`を追加しました。車の現在地を記録します。

# chaincodeへ実装

```go:asset.go
func (s *SmartContract) GetHistoryOfAsset(ctx contractapi.TransactionContextInterface, key string) ([]GetHisResult, error) {
        fmt.Println("GetHistoryForKey")

        resultsIterator, err := ctx.GetStub().GetHistoryForKey(key)
        if err != nil {
                return nil, fmt.Errorf("ctx.GetStub().GetHistoryForKey: %s", err.Error())
        }
        defer resultsIterator.Close()

        results := []GetHisResult{}

        for resultsIterator.HasNext() {
                queryResponse, err := resultsIterator.Next()
                if err != nil {
                        return nil, err
                }
                asset := new(Asset)
                _ = json.Unmarshal(queryResponse.Value, asset)
                t := time.Unix(queryResponse.Timestamp.Seconds, 0).String()
                //t := time.Unix(queryResponse.Timestamp.Seconds, int64(queryResponse.Timestamp.Nanos)).String()
                queryResult := GetHisResult{TxId:queryResponse.TxId, Timestamp:t, IsDelete:queryResponse.IsDelete, Record: asset}
                results = append(results, queryResult)
        }

        return results, nil
}
```
サンプルchaincodeの`marbles_chaincode.go`を参考にしました。メソッド`getHistoryForMarble`がとても勉強になります。[こちら](https://gist.github.com/michielmulders/f15b69a812db28f586cfd26dfc4a6cbc)にアップされています。

# シナリオを実行してみる
`HistoryMyCar.sh`という名のシェルスクリプトを作って、上のシナリオ通りに`peer`へ指示を出しました。
さすがに実時間ではできないので、各イベントは5分おきに発生させています。`sleep 5m`です。
イベント毎に`echo`で解説を出力してみました。何も出ないとどこまで物語が進んでいるか分からないので;-)
日時は協定世界時(いわゆるUTC)で出力しています。自動車会社はほとんどがグローバル企業なので、UTCで統一した方が一貫性があって良いのではないかと思います。必要ならクライアントアプリで変換するだけだし。

```bash
# ./HistoryMyCar.sh 
> ミラ・キータを購入しました。VINコードは[JMYMIRAGINO200302]です。
> Location:Owner
Mon Jun 22 05:38:40 UTC 2020
2020-06-22 14:38:40.554 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 
> 調子が悪いのでディーラーへ預けました。左ウィンカーが切れています。パワーウィンドウが動きません
> Location:ＱＩＩＴＡ東京販売
Mon Jun 22 05:43:40 UTC 2020
2020-06-22 14:43:40.700 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 
> 工場へ運んで色々調べると連絡がありました。
> Location:ＱＩＩＴＡ埼玉工場
Mon Jun 22 05:48:40 UTC 2020
2020-06-22 14:48:40.834 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 
> 工場から戻ってきたと連絡がありました。いつ受け取りに行っても良いとのこと。
> Location:ＱＩＩＴＡ東京販売
Mon Jun 22 05:53:40 UTC 2020
2020-06-22 14:53:40.970 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 
> 愛しのミラ・キータが戻ってきました！
> Location:Owner
Mon Jun 22 05:58:40 UTC 2020
2020-06-22 14:58:41.104 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 
> やっぱり調子が悪いのでディーラーに引き取ってもらいました。とても古いので廃車になるそうです。
> Location:ＱＩＩＴＡ東京販売
Mon Jun 22 06:03:41 UTC 2020
2020-06-22 15:03:41.238 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 
> 廃車にするため工場へ運ぶそうです
> Location:ＱＩＩＴＡ千葉工場
Mon Jun 22 06:08:41 UTC 2020
2020-06-22 15:08:41.371 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 
> さようならミラ・キータ。。。
Mon Jun 22 06:13:41 UTC 2020
2020-06-22 15:13:41.509 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 
#
```
UTCで日時も出力しています。最後は泣けてきそうです。。。

# 物語をトレースしてみる
上に書いた`GetHistoryOfAsset`を実行してみます。

```json
# ./GetHistoryForKey.sh | jq
[
  {
    "TxId": "55d1762ffc7e4e847325001d281a55c08fabaf625b57168109c41f64c9c42cb3",
    "Timestamp": "2020-06-22 06:13:41 +0000 UTC",
    "IsDelete": true,
    "Record": {
      "year": "",
      "month": "",
      "mileage": 0,
      "battery": 0,
      "Location": ""
    }
  },
  {
    "TxId": "456b107f7b2eddadf2de2533f9d577080f10ba6a699c6da36949476bc075cece",
    "Timestamp": "2020-06-22 06:08:41 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 67890,
      "battery": 100,
      "Location": "ＱＩＩＴＡ千葉工場"
    }
  },
  {
    "TxId": "7131bbd32f7764ea10b5a8e39f5b5e20174c3da71057451a330f9e1ddbd36e4f",
    "Timestamp": "2020-06-22 06:03:41 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 67890,
      "battery": 100,
      "Location": "ＱＩＩＴＡ東京販売"
    }
  },
  {
    "TxId": "dc897a82f050de0e18cb436232e08afaf1f8958654389080bee38a77c5012632",
    "Timestamp": "2020-06-22 05:58:41 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 45680,
      "battery": 100,
      "Location": "Owner"
    }
  },
  {
    "TxId": "8e64eb2407525ae5e810a1b50c7c72f66d0e02fa068de51d6640fb76445f032b",
    "Timestamp": "2020-06-22 05:53:40 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 45680,
      "battery": 100,
      "Location": "ＱＩＩＴＡ東京販売"
    }
  },
  {
    "TxId": "f3677847074200048aef105a8a9fce994ba8d9e59a815d21a729d7b686d8bbde",
    "Timestamp": "2020-06-22 05:48:40 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 45679,
      "battery": 100,
      "Location": "ＱＩＩＴＡ埼玉工場"
    }
  },
  {
    "TxId": "ad0b6b63845f19e2fa6b396d52ddcbf6f1759d92f9d16c6ffd219f111c48110c",
    "Timestamp": "2020-06-22 05:43:40 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 45678,
      "battery": 100,
      "Location": "ＱＩＩＴＡ東京販売"
    }
  },
  {
    "TxId": "47d362443f579ec96c1d05b3134ad73238378968314a94b1e3d9b3f485dd0241",
    "Timestamp": "2020-06-22 05:38:40 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 43871,
      "battery": 100,
      "Location": "Owner"
    }
  }
]
#
```
履歴は逆順で出力されるようです。下から読んでいくとシナリオ通りに物語が進んでいたことが分かります。
`IsDelete`が`true`になって、何もかもが無になるのですね。。。レコードが削除されたので当たり前ですが。

# 最後に
取引履歴はブロックチェーンが標準で持っている機能です。そもそもは取引履歴を改ざんされないよう、このような仕組みを取り入れたのだと思いますが、物流システムでも便利に使えそうです。EV車のバッテリー管理なんかに使えそう。

ちなみに、僕の17年落ちのミラ・ジーノは今日も絶好調な走りでしたよ！