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に変換しています。
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を動的に変更させたかったので、引数から取得しています
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処理も入れておきましょう。
historyIterator, _ := APIstub.GetHistoryForKey(key)
defer historyIterator.Close()
- 取得した内容をbufferに詰め込んでいきます。
- 行っている内容は「queryAllCars」と同じで、詰めている内容をGetHistoryForKeyに合わせているだけです。
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として返しています。
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、となります
// 変更前
// const result = await contract.evaluateTransaction('queryAllCars');
//変更後
const result = await contract.evaluateTransaction('getHistoryForKey', process.argv[2]);
また、登録処理も中身を動的に設定できるように修正します。
// 変更前
// 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作成等はスキップ
▼登録
※代わり映えがしないって?
# 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
▼検索
# 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っぽく表示してみます。
[
{
"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で書きたいよー。
あと、時たま、初期のインストール&インスタンス化ではチェーンコードが反映されていなくて、手動アップグレードが必要だったりするけど、あれはなんじゃろなーーー
※がんだむはくわしくありません
今回はここまでです。
ありがとうございました。