Hyperledger FabricのChaincodeを書く時のチートコード
はじめに
- fabric-samplesのコードを例に、実際に使用するときに困らないような参照インデックスを作ります。
- インデックス化が目的なので詳細はリンク先の公式ページを見ること
- go言語版chaincodeがメインです
- ターゲットバージョンはHLF v2.2
定型文
main.go
- 一番最初に読まれる部分.
- テンプレ構文なので基本は使いまわす
package main
import (
"log"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
"github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode" // メインのロジックはここ
)
func main() {
// chaincodeパッケージ内のSmartContract構造体をインスタンス化する
assetChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{})
if err != nil {
log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
}
// .Start()で開始
if err := assetChaincode.Start(); err != nil {
log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)
}
}
smartcontract.go
- main.goから参照される部分.
- メインのビジネスロジックを記述する
package chaincode
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/shim" // chaincodeとpeer間のやり取りで使用する
"github.com/hyperledger/fabric-contract-api-go/contractapi" // アプリがChaincode呼び出す時にいい感じに抽象化してくれるやつ
)
// 定型文. とりあえず定義しておく
type SmartContract struct {
contractapi.Contract
}
- アプリから実行可能な関数を宣言.
// (s *SmartContract) でこの関数は構造体の要素であることを示す
// `ctx contractapi.TransactionContextInterface` はTransactionに必要な最低限の要素を実装したライブラリ?
// お約束的に必要なので、アプリから呼び出される関数にはこれを付けておくこと
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
return nil
}
Chaincodeの構文&関数
ステートの値を読み込み
// 参照したいStateのID
ctx.GetStub().GetState(id)
ステートの値を範囲で検索
// 検索のstartKeyとendKeyを指定する.
// 両方とも空欄の場合は、chaincode内の全てのstateを返す
ctx.GetStub().GetStateByRange("", "")
defer resultsIterator.Close() // 必ず終了時処理を追加する
ステートの値を範囲で検索(ページネーション付き)
- 検索結果が多い時に使う
- bookmark
- 最初のページでは空文字を指定
- 2ページ目以降は、前ページのQueryResponseMetadata内にあるbookmarkを指定
ctx.GetStub().GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark)
ステートに書き込み
// Stateに書き込むときに一意に決まるID, JSON構造の書き込みたい内容
// すでにIDが存在する場合は上書きになる
ctx.GetStub().PutState(ID, assetJSON)
ステートから削除
// Stateに書き込んだ時のID,
ctx.GetStub().DelState(id)
複合キー(CompositeKey)の作成
- プレフィックスに種別、属性のリストにアイテムをユニークに識別できるだけの要素を列挙するとよい
- Stateに保存するときのkey-valueのkeyは一意である必要があるので、複合キーを使うことでkey管理が容易になる。
ctx.GetStub().CreateCompositeKey("プレフィックス", "属性のリスト")
ctx.GetStub().CreateCompositeKey("color~name", []string{"black", "12345"})
// -> "color~name[NULL文字]black[NULL文字]12345"
複合キー(CompositeKey)の内、指定した要素のレコードを検索する
- 前方一致検索
- 途中の要素だけの検索は不可。中間の値を検索したい場合は必ず前の要素も含めること
// Compositkey, {部品名, 色, シリアルナンバー}
ctx.GetStub().CreateCompositeKey("partsList", []string{"arm", "silver", "12345"})
ctx.GetStub().CreateCompositeKey("partsList", []string{"arm", "gold", "23455"})
ctx.GetStub().CreateCompositeKey("partsList", []string{"wing", "gold", "67890"})
// compositkeyに"arm"を含むものをすべて抽出
ctx.GetStub().GetStateByPartialCompositeKey("partsList", []string{"arm"})
// compositkeyに"arm"と"silver"を含むものを検索
ctx.GetStub().GetStateByPartialCompositeKey("partsList", []string{"arm", "silver"})
// ↓は動かない
ctx.GetStub().GetStateByPartialCompositeKey("partsList", []string{"silver"})
ctx.GetStub().GetStateByPartialCompositeKey("partsList", []string{"123456"})
ctx.GetStub().GetStateByPartialCompositeKey("partsList", []string{"silver", "123456"})
複合キーの分割
- CompositKeyの要素をstring型配列に変換する
- (実機で再チェック)
_, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(item.Key)
// compositeKeyParts = {"arm", "silver", "12345"}
クエリ
- リッチクエリ
- JSON形式のStateに対して検索可能
- couchDBで使用可能。levelDBでは不可
queryString := fmt.Sprintf(`{"selector":{"docType":"asset","owner":"%s"}}`, owner)
ctx.GetStub().GetQueryResult(queryString)
StateのKeyの更新履歴を取得
ctx.GetStub().GetHistoryForKey(assetID)
一時的なデータの取得
- チェーンコードで使用できるが元帳には保存されない一時的なマップを返す
- PrivateDataCollectionでは引数をnodejs側のSstTransient()事前に設定しておく
transientMap, err := ctx.GetStub().GetTransient()
transientAssetJSON, ok := transientMap["asset_properties"]
PrivateDataの取得
- GetStateと使い方は同じ
- 第一引数はPrivateDataCollectionの名前
ctx.GetStub().GetPrivateData(assetCollection, assetInput.ID)
PrivateDataの書き込み
- PutStateと使い方は同じ
- 第一引数はPrivateDataCollectionの名前
ctx.GetStub().PutPrivateData(assetCollection, assetInput.ID, assetJSONasBytes)
PrivateDataの削除
- DelStateと使い方は同じ
- 第一引数はPrivateDataCollectionの名前
- 2021/7/18現在、公式サイトに記述無し
ctx.GetStub().DelPrivateData(ownersCollection, assetTransferInput.ID)
PrivateDataのハッシュ値を取得
- 指定したPrivateDataCollectionのKeyに紐づくデータのハッシュ値を取得
- 異なるprivateCollection間で同一のkeyが同一の値になっていることをチェックするときに使用する
- 第一引数はコレクション名
ownerAppraisedValueHash, err := ctx.GetStub().GetPrivateDataHash(collectionOwner, assetID)
buyerAppraisedValueHash, err := ctx.GetStub().GetPrivateDataHash(collectionBuyer, assetID)
// Verify that the two hashes match
if !bytes.Equal(ownerAppraisedValueHash, buyerAppraisedValueHash) {
return fmt.Errorf("hash for appraised value for owner %x does not value for seller %x", ownerAppraisedValueHash, buyerAppraisedValueHash)
}
TransactionのTimestampを取得
- Transactionが作成された時のtimestamp
- 全TX承認者で同一の時刻になる
txTimestamp, err := ctx.GetStub().GetTxTimestamp()
timestamp, err := ptypes.Timestamp(txTimestamp)
Transaction IDを取得
ctx.GetStub().GetTxID()
構造体をJSON構造に変換
type Asset struct {
ID string `json:"ID"`
Color string `json:"color"`
Size int `json:"size"`
Owner string `json:"owner"`
AppraisedValue int `json:"appraisedValue"`
}
// overwriting original asset with new asset
asset := Asset{
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetJSON, err := json.Marshal(asset)
JSON構造を構造体に変換
type Asset struct {
ID string `json:"ID"`
Color string `json:"color"`
Size int `json:"size"`
Owner string `json:"owner"`
AppraisedValue int `json:"appraisedValue"`
}
var asset Asset
err = json.Unmarshal(assetJSON, &asset)
クライアントのMSP IDを取得
ctx.GetClientIdentity().GetMSPID()
PeerのMSP IDを取得
shim.GetMSPID()
呼び出し元クライアントに割り当てられているIDを取得
- MSP内で一意に決まるID
- base64でデコードできる
b64ID, err := ctx.GetClientIdentity().GetID()
decodeID, err := base64.StdEncoding.DecodeString(b64ID)
Stateベースの更新ポリシーの設定
- State単位で更新ポリシーを定義可能.
- 使い方は実装例を参照
ctx.GetStub().SetStateValidationParameter(assetID, policy)
よく使う実装
Stateの取得
// ReadAsset returns the public asset data
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) {
// Since only public data is accessed in this function, no access control is required
assetJSON, err := ctx.GetStub().GetState(assetID)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %v", err)
}
if assetJSON == nil {
return nil, fmt.Errorf("%s does not exist", assetID)
}
var asset *Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
return asset, nil
}
func (s *SmartContract) hoge(ctx contractapi.TransactionContextInterface, assetID string) error {
asset, err := s.ReadAsset(ctx, assetID)
}
Stateの存在確認
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return false, fmt.Errorf("failed to read from world state: %v", err)
}
return assetJSON != nil, nil
}
PrivateDataの存在確認
// Check if asset already exists
assetAsBytes, err := ctx.GetStub().GetPrivateData(assetCollection, assetInput.ID)
if err != nil {
return fmt.Errorf("failed to get asset: %v", err)
} else if assetAsBytes != nil {
fmt.Println("Asset already exists: " + assetInput.ID)
return fmt.Errorf("this asset already exists: " + assetInput.ID)
}
// Get ID of submitting client identity
clientID, err := submittingClientIdentity(ctx)
if err != nil {
return err
}
Compositkeyを検索し、ヒットした要素のCompositkeyを分解する
// Execute a key range query on all keys starting with 'color'
coloredAssetResultsIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(index, []string{color})
if err != nil {
return err
}
defer coloredAssetResultsIterator.Close()
for coloredAssetResultsIterator.HasNext() {
responseRange, err := coloredAssetResultsIterator.Next()
if err != nil {
return err
}
_, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(responseRange.Key)
if err != nil {
return err
}
(略)
他組織のクライアントが、このPeerのPrivateDataの読書をさせないようにする
// getClientOrgID gets the client org ID.
// The client org ID can optionally be verified against the peer org ID, to ensure that a client
// from another org doesn't attempt to read or write private data from this peer.
// The only exception in this scenario is for TransferAsset, since the current owner
// needs to get an endorsement from the buyer's peer.
func getClientOrgID(ctx contractapi.TransactionContextInterface, verifyOrg bool) (string, error) {
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return "", fmt.Errorf("failed getting client's orgID: %v", err)
}
if verifyOrg {
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
if err != nil {
return "", err
}
}
return clientOrgID, nil
}
// verifyClientOrgMatchesPeerOrg checks the client org id matches the peer org id.
func verifyClientOrgMatchesPeerOrg(clientOrgID string) error {
peerOrgID, err := shim.GetMSPID()
if err != nil {
return fmt.Errorf("failed getting peer's orgID: %v", err)
}
if clientOrgID != peerOrgID {
return fmt.Errorf("client from org %s is not authorized to read or write private data from an org %s peer",
clientOrgID,
peerOrgID,
)
}
return nil
}
State(Key)単位で更新ポリシーを設定する
// setAssetStateBasedEndorsement adds an endorsement policy to a asset so that only a peer from an owning org
// can update or transfer the asset.
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetID string, orgToEndorse string) error {
// 更新ポリシーのオブジェクトを用意する。引数がnilの場合は新規作成
endorsementPolicy, err := statebased.NewStateEP(nil)
if err != nil {
return err
}
// 更新ポリシーにmsp.MSPRole_PEERと組織名の配列を指定する
err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgToEndorse)
if err != nil {
return fmt.Errorf("failed to add org to endorsement policy: %v", err)
}
// 定義した更新ポリシーでオブジェクト化する.(お作法)
policy, err := endorsementPolicy.Policy()
if err != nil {
return fmt.Errorf("failed to create endorsement policy bytes from org: %v", err)
}
// Stateベースの更新ポリシーをState(key)に割り当てる
err = ctx.GetStub().SetStateValidationParameter(assetID, policy)
if err != nil {
return fmt.Errorf("failed to set validation parameter on asset: %v", err)
}
return nil
}
設定値
/etc/hyperledger/fabric/core.yaml
ledger:
state:
totalQueryLimit: 100000 // 1回のqueryで返せるレコードの上限数
Go言語的なお作法
構造体の宣言と使用
type Asset struct {
ID string `json:"ID"`
Color string `json:"color"`
Size int `json:"size"`
Owner string `json:"owner"`
AppraisedValue int `json:"appraisedValue"`
}
assets := []Asset{
{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
}
構造体返し
// (*Asset, error) の第一引数に構造体を設定している
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
// JSON形式のStateを構造体に変換する
var asset Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
// 構造体を返す
return &asset, nil
}
イテレータ
// イテレータ.HasNext で次の要素が存在するかチェック.
for resultsIterator.HasNext() {
// イテレータ.Next で要素を取得.
queryResponse, err := resultsIterator.Next()
}
基礎文法
-
:=
変数の宣言と代入を同時にやる.
以下は同じ意味- string hoge = "hello"
- hoge := "hello
ログ
import ("log")
log.Printf("ReadAsset: collection %v, ID %v", assetCollection, assetID)
log.Panicf("Error creating asset-transfer-private-data chaincode: %v", err)
ノウハウ
- chaincodeのパッケージ化したものはローカルに保存しておくこと。後日再ビルドするとパッケージのハッシュが変わるので別物として認識されてしまう。
コマンド
Org1の認証情報取得
- CA Adminの登録
mkdir -p organizations/peerOrganizations/org1.example.com/
export FABRIC_CA_CLIENT_HOME=/home/vagrant/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/
FABRIC_CA_CLIENT_HOME=/home/vagrant/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/
fabric-ca-client enroll -u https://admin:adminpw@localhost:7054 --caname ca-org1 --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/org1/tls-cert.pem
- Peer0の登録
fabric-ca-client register --caname ca-org1 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/org1/tls-cert.pem
- org1の一般ユーザー作成
fabric-ca-client register --caname ca-org1 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/org1/tls-cert.pem
- org1のadminユーザー作成
fabric-ca-client register --caname ca-org1 --id.name org1admin --id.secret org1adminpw --id.type admin --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/org1/tls-cert.pem
- peer0のMSP作成
fabric-ca-client enroll -u https://peer0:peer0pw@localhost:7054 --caname ca-org1 -M /home/vagrant/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp --csr.hosts peer0.org1.example.com --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/org1/tls-cert.pem
- peer0のtls作成
fabric-ca-client enroll -u https://peer0:peer0pw@localhost:7054 --caname ca-org1 -M /home/vagrant/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls --enrollment.profile tls --csr.hosts peer0.org1.example.com --csr.hosts localhost --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/org1/tls-cert.pem
cp ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/tlscacerts/* ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
cp ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/signcerts/* ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt
cp ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/keystore/* ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key
mkdir -p ${PWD}/organizations/peerOrganizations/org1.example.com/msp/tlscacerts
cp ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/tlscacerts/* ${PWD}/organizations/peerOrganizations/org1.example.com/msp/tlscacerts/ca.crt
mkdir -p ${PWD}/organizations/peerOrganizations/org1.example.com/tlsca
cp ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/tlscacerts/* ${PWD}/organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
mkdir -p ${PWD}/organizations/peerOrganizations/org1.example.com/ca
cp ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/cacerts/* ${PWD}/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem
- org1の一般userのmsp作成
fabric-ca-client enroll -u https://user1:user1pw@localhost:7054 --caname ca-org1 -M /home/vagrant/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/org1/tls-cert.pem
cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/config.yaml
- org1のadminのmsp作成
fabric-ca-client enroll -u https://org1admin:org1adminpw@localhost:7054 --caname ca-org1 -M /home/vagrant/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/org1/tls-cert.pem
cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/config.yaml
Orderer Orgの認証情報取得
- CA Adminの登録
mkdir -p organizations/ordererOrganizations/example.com
export FABRIC_CA_CLIENT_HOME=/home/vagrant/fabric-samples/test-network/organizations/ordererOrganizations/example.com
fabric-ca-client enroll -u https://admin:adminpw@localhost:9054 --caname ca-orderer --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/ordererOrg/tls-cert.pem
- ordererの登録
fabric-ca-client register --caname ca-orderer --id.name orderer --id.secret ordererpw --id.type orderer --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/ordererOrg/tls-cert.pem
- orderer adminの登録
fabric-ca-client register --caname ca-orderer --id.name ordererAdmin --id.secret ordererAdminpw --id.type admin --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/ordererOrg/tls-cert.pem
- orderer msp作成
fabric-ca-client enroll -u https://orderer:ordererpw@localhost:9054 --caname ca-orderer -M /home/vagrant/fabric-samples/test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp --csr.hosts orderer.example.com --csr.hosts localhost --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/ordererOrg/tls-cert.pem
cp ${PWD}/organizations/ordererOrganizations/example.com/msp/config.yaml ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/config.yaml
- ordererのtls作成
fabric-ca-client enroll -u https://orderer:ordererpw@localhost:9054 --caname ca-orderer -M /home/vagrant/fabric-samples/test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls --enrollment.profile tls --csr.hosts orderer.example.com --csr.hosts localhost --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/ordererOrg/tls-cert.pem
cp ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/tlscacerts/* ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
cp ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/signcerts/* ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
cp ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/keystore/* ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.key
mkdir -p ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts
cp ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/tlscacerts/* ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
mkdir -p ${PWD}/organizations/ordererOrganizations/example.com/msp/tlscacerts
cp ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/tlscacerts/* ${PWD}/organizations/ordererOrganizations/example.com/msp/tlscacerts/tlsca.example.com-cert.pem
- adminのmsp作成
fabric-ca-client enroll -u https://ordererAdmin:ordererAdminpw@localhost:9054 --caname ca-orderer -M /home/vagrant/fabric-samples/test-network/organizations/ordererOrganizations/example.com/users/Admin@example.com/msp --tls.certfiles /home/vagrant/fabric-samples/test-network/organizations/fabric-ca/ordererOrg/tls-cert.pem
cp ${PWD}/organizations/ordererOrganizations/example.com/msp/config.yaml ${PWD}/organizations/ordererOrganizations/example.com/users/Admin@example.com/msp/config.yaml
genesis Blockの生成
configtxgen -profile TwoOrgsOrdererGenesis -channelID system-channel -outputBlock ./system-genesis-block/genesis.block