0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-06-22

#マイカーの一生をトレースしましょう
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から得られる情報を構造体へ定義しました。

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へ実装

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がとても勉強になります。こちらにアップされています。

シナリオを実行してみる

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

# ./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:QIITA東京販売
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:QIITA埼玉工場
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:QIITA東京販売
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:QIITA東京販売
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:QIITA千葉工場
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を実行してみます。

# ./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": "QIITA千葉工場"
    }
  },
  {
    "TxId": "7131bbd32f7764ea10b5a8e39f5b5e20174c3da71057451a330f9e1ddbd36e4f",
    "Timestamp": "2020-06-22 06:03:41 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 67890,
      "battery": 100,
      "Location": "QIITA東京販売"
    }
  },
  {
    "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": "QIITA東京販売"
    }
  },
  {
    "TxId": "f3677847074200048aef105a8a9fce994ba8d9e59a815d21a729d7b686d8bbde",
    "Timestamp": "2020-06-22 05:48:40 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 45679,
      "battery": 100,
      "Location": "QIITA埼玉工場"
    }
  },
  {
    "TxId": "ad0b6b63845f19e2fa6b396d52ddcbf6f1759d92f9d16c6ffd219f111c48110c",
    "Timestamp": "2020-06-22 05:43:40 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 45678,
      "battery": 100,
      "Location": "QIITA東京販売"
    }
  },
  {
    "TxId": "47d362443f579ec96c1d05b3134ad73238378968314a94b1e3d9b3f485dd0241",
    "Timestamp": "2020-06-22 05:38:40 +0000 UTC",
    "IsDelete": false,
    "Record": {
      "year": "2003",
      "month": "02",
      "mileage": 43871,
      "battery": 100,
      "Location": "Owner"
    }
  }
]
#

履歴は逆順で出力されるようです。下から読んでいくとシナリオ通りに物語が進んでいたことが分かります。
IsDeletetrueになって、何もかもが無になるのですね。。。レコードが削除されたので当たり前ですが。

最後に

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

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

0
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?