【Hyperledger Fabric】GetHistoryForKeyを使って、StateDBのKeyに紐づくHistoryをGetしよう！！

# 2019/12/17（火）追記ここから

2019/12/12（木）にHyperledger Fabric v2.0 β が公開されました。
FabTokenの延期等ありますが、この記事で紹介している「GetHistoryForKey」の取得結果が最新から順序付するようになったそうです。
※今現在だと、順序に関する保証がないらしい・・・
※「FAB-16303: GetHistoryForKey returns results from newest to oldest」

その他、コンセンサスのSoloやKafkaは非推奨となりRAFTが推奨されるなど、色々と変更がありそうです。
詳しくはリリースノートをご参照ください

▼ リリースノート
https://github.com/hyperledger/fabric/releases/tag/v2.0.0-beta

# 2019/12/17（火）追記ここまで

## はじめに
みなさま、お久しぶりです。
どーも、のぶこふです。

プロキシという足枷に行く手を阻まれながらも、
どーにかこーにか、生きながらえております。

今回は、Hyperledger Fabricで履歴（History）を取得するやり方です。

諸事情により、Golangでの記述となりますが、Node.jsやJavaでも同様の方法で取得できるかと思います（未確認）。

なお、環境構築やその他チェーンコード箇所等については記載しませんのであしからず。

## 環境
* Windows10 Behind Proxy（ホストOS）
* VirtualBox
* CentOS7（ゲストOS）
* Hyperledger Fabric（HLF）1.4.3
* Golang

## 登場人物
`Fabric-Shim`の`GetHistoryForKey（key string）`
▼GoDoc
https://godoc.org/github.com/hyperledger/fabric/core/chaincode/shim#ChaincodeStub.GetHistoryForKey

## コード（全量）
公式サンプルのFabcarを修正していきます。
全量は下記になります
※「コメント」「initLedger」「queryAllCars」「changeCarOwner」は削除し、「getHistoryForKey」を追加しています。
`shim.Success`で返すため、「queryAllCars」と同じ様にbufferに詰め込んで、byteに変換しています。

```golang:fabcar.go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"github.com/hyperledger/fabric/core/chaincode/shim"
	sc "github.com/hyperledger/fabric/protos/peer"
)

type SmartContract struct {
}

type Car struct {
	Make   string `json:"make"`
	Model  string `json:"model"`
	Colour string `json:"colour"`
	Owner  string `json:"owner"`
}

func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
	return shim.Success(nil)
}

func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
	function, args := APIstub.GetFunctionAndParameters()

	if function == "queryCar" {
		return s.queryCar(APIstub, args)
	} else if function == "createCar" {
		return s.createCar(APIstub, args)
	} else if function == "getHistoryForKey" {
		return s.getHistoryForKey(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) 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) getHistoryForKey(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
        if len(args) != 1 {
                return shim.Error("Incorrect number of arguments. Expecting 1")
        }

        var key = args[0]

        historyIterator, _ := APIstub.GetHistoryForKey(key)
        defer historyIterator.Close()

        var buffer bytes.Buffer
        buffer.WriteString("[")

        bArrayMemberAlreadyWritten := false
        for historyIterator.HasNext() {
                queryResponse, err := historyIterator.Next()
                if err != nil {
                        return shim.Error(err.Error())
                }
                if bArrayMemberAlreadyWritten == true {
                        buffer.WriteString(",")
                }

                t := time.Unix(queryResponse.Timestamp.Seconds, 0)
                const layout = "Monday Jan 02 15:04:05 JST 2006"

                buffer.WriteString("{\"TxID\":")
                buffer.WriteString("\"")
                buffer.WriteString(queryResponse.TxId)
                buffer.WriteString("\"")

                buffer.WriteString(", \"Record\":")
                buffer.WriteString("\"")
                buffer.WriteString(string(queryResponse.Value))
                buffer.WriteString("\"")

                buffer.WriteString(", \"Timestamp\":")
                buffer.WriteString("\"")
                buffer.WriteString(t.Format(layout))
                buffer.WriteString("\"")

                buffer.WriteString(", \"IsDelete\":")
                buffer.WriteString("\"")
                buffer.WriteString(strconv.FormatBool(queryResponse.IsDelete))
                buffer.WriteString("\"")

                buffer.WriteString("}")
                bArrayMemberAlreadyWritten = true
        }
        buffer.WriteString("]")

        return shim.Success(buffer.Bytes())
}


func main() {
	err := shim.Start(new(SmartContract))
	if err != nil {
		fmt.Printf("Error creating new Smart Contract: %s", err)
	}
}
```

## コード（解説）
追加した箇所について解説していきます。

* 関数の定義と、引数のサイズチェックです
    * Keyを動的に変更させたかったので、引数から取得しています

```golang:fabcar.go
func (s *SmartContract) getHistoryForKey(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
        if len(args) != 1 {
                return shim.Error("Incorrect number of arguments. Expecting 1")
        }

        var key = args[0]
```

* Shimから`GetHistoryForKey`を呼び出しています。
* deferを使用して、忘れる前にClose処理も入れておきましょう。

```golang:fabcar.go
        historyIterator, _ := APIstub.GetHistoryForKey(key)
        defer historyIterator.Close()
```

* 取得した内容をbufferに詰め込んでいきます。
    * 行っている内容は「queryAllCars」と同じで、詰めている内容をGetHistoryForKeyに合わせているだけです。

```golang:fabcar.go
        var buffer bytes.Buffer
        buffer.WriteString("[")

        bArrayMemberAlreadyWritten := false
        for historyIterator.HasNext() {
                queryResponse, err := historyIterator.Next()
                if err != nil {
                        return shim.Error(err.Error())
                }
                if bArrayMemberAlreadyWritten == true {
                        buffer.WriteString(",")
                }

                t := time.Unix(queryResponse.Timestamp.Seconds, 0)
                const layout = "Monday Jan 02 15:04:05 JST 2006"

                buffer.WriteString("{\"TxID\":")
                buffer.WriteString("\"")
                buffer.WriteString(queryResponse.TxId)
                buffer.WriteString("\"")

                buffer.WriteString(", \"Record\":")
                buffer.WriteString("\"")
                buffer.WriteString(string(queryResponse.Value))
                buffer.WriteString("\"")

                buffer.WriteString(", \"Timestamp\":")
                buffer.WriteString("\"")
                buffer.WriteString(t.Format(layout))
                buffer.WriteString("\"")

                buffer.WriteString(", \"IsDelete\":")
                buffer.WriteString("\"")
                buffer.WriteString(strconv.FormatBool(queryResponse.IsDelete))
                buffer.WriteString("\"")

                buffer.WriteString("}")
                bArrayMemberAlreadyWritten = true
        }
        buffer.WriteString("]")
```

* 最後に、bufferをByteへ変換して、Successとして返しています。

```golang:fabcar.go
        return shim.Success(buffer.Bytes())
}
```

## 呼び出し側
このままでは、使用することができないので、`javascript/query.js`にも変更を加えます。

* 関数名を作成した関数名に変更
* 動的にKeyを変更させたいので、コンソールから取得するように設定
    * 「node query.js hoge」とすると、process.argv[0]がnode、process.argv[1]がquery.js、process.argv[2]がhoge、となります

```javascript:javascript/query.js
// 変更前
// const result = await contract.evaluateTransaction('queryAllCars');

//変更後
const result = await contract.evaluateTransaction('getHistoryForKey',  process.argv[2]);
```

また、登録処理も中身を動的に設定できるように修正します。

```javascript:javascript/invoke.js
// 変更前
// await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');

//変更後
await contract.submitTransaction('createCar', process.argv[2], process.argv[3], process.argv[4], process.argv[5], process.argv[6]);
```

## 実行
準備が整いました。
実際に動かしてみましょう。
※ネットワーク起動、install、instantiate、adminとuser作成等はスキップ

▼登録
※代わり映えがしないって？

```terminal:複数回登録を行っておきます
# node invoke.js test Zeon MS-06S Red Char
# node invoke.js test Zeon MSM-07S Red Char
# node invoke.js test Zeon MSN-00100 Gold Char
# node invoke.js test Zeon MSN-04 Red Char
```

▼検索

```terminal:Keyはtestです
# node query.js test
Wallet path: /opt/fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is: [{"TxID":"88cf49dbae85c890d6c760adcbc1a508381b7120c96aff500ba129ccb57ccb5b", "Record":"{"make":"Zeon","model":"MS-06S","colour":"Red","owner":"Char"}", "Timestamp":"Tuesday Nov 05 07:41:32 JST 2019", "IsDelete":"false"},{"TxID":"01a1f67af0a574914f68067a16abee9b1015a7e04471c151822951fc3dff432b", "Record":"{"make":"Zeon","model":"MSM-07S","colour":"Red","owner":"Char"}", "Timestamp":"Tuesday Nov 05 07:41:37 JST 2019", "IsDelete":"false"},{"TxID":"861c0d8b47af62d4c4752beaef2d3fd782fd2a982a4bbd959c2bc6adb8cebb10", "Record":"{"make":"Zeon","model":"MSN-00100","colour":"Gold","owner":"Char"}", "Timestamp":"Tuesday Nov 05 07:41:43 JST 2019", "IsDelete":"false"},{"TxID":"660989fbc24f8ee7b5612fb32a3388a8ae193d5a2138fc7d70d99c18e325e7ce", "Record":"{"make":"Zeon","model":"MSN-04","colour":"Red","owner":"Char"}", "Timestamp":"Tuesday Nov 05 07:41:48 JST 2019", "IsDelete":"false"}]
```

上記だとわかりにくいので、resultの部分をJSONっぽく表示してみます。

```json:"{　と　}"　を　{　と　}　に置換してます
[
  {
    "TxID": "88cf49dbae85c890d6c760adcbc1a508381b7120c96aff500ba129ccb57ccb5b",
    "Record": {
      "make": "Zeon",
      "model": "MS-06S",
      "colour": "Red",
      "owner": "Char"
    },
    "Timestamp": "Tuesday Nov 05 07:41:32 JST 2019",
    "IsDelete": "false"
  },
  {
    "TxID": "01a1f67af0a574914f68067a16abee9b1015a7e04471c151822951fc3dff432b",
    "Record": {
      "make": "Zeon",
      "model": "MSM-07S",
      "colour": "Red",
      "owner": "Char"
    },
    "Timestamp": "Tuesday Nov 05 07:41:37 JST 2019",
    "IsDelete": "false"
  },
  {
    "TxID": "861c0d8b47af62d4c4752beaef2d3fd782fd2a982a4bbd959c2bc6adb8cebb10",
    "Record": {
      "make": "Zeon",
      "model": "MSN-00100",
      "colour": "Gold",
      "owner": "Char"
    },
    "Timestamp": "Tuesday Nov 05 07:41:43 JST 2019",
    "IsDelete": "false"
  },
  {
    "TxID": "660989fbc24f8ee7b5612fb32a3388a8ae193d5a2138fc7d70d99c18e325e7ce",
    "Record": {
      "make": "Zeon",
      "model": "MSN-04",
      "colour": "Red",
      "owner": "Char"
    },
    "Timestamp": "Tuesday Nov 05 07:41:48 JST 2019",
    "IsDelete": "false"
  }
]
```

　　　　　　　　　＼表示できました／

## おわりに
サプライチェーンを考える上で、トレースは欠かせないですよね。
本当はブロックの中身をいじくり回したいのですが、いかんせんSDKをインポートできない病にかかってまして、とりあえずトレースする方法を記述しました。

HLF1.4のLTSもそろそろ切れる頃だし、HLF2.0が正式リリースされないかなぁーーー|дﾟ)ﾁﾗｯ

あー、Nodeで書きたいよー。
あと、時たま、初期のインストール＆インスタンス化ではチェーンコードが反映されていなくて、手動アップグレードが必要だったりするけど、あれはなんじゃろなーーー

※がんだむはくわしくありません

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