LoginSignup
8
5

More than 5 years have passed since last update.

OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう(2/3)〜 chaincode解説編

Last updated at Posted at 2018-03-09

この記事は、OSSの分散型台帳技術であるHyperledger/fabricを用いて簡易な入出金処理を作り、docker swarm modeで分散処理させてみる、という連載の2回目です。

今回は、Hyperledger/fabricのchaincodeを開発するための環境構築手順と、前回動作させた入金・送金・出金サンプルアプリのchaincodeの内容を解説します。Hyperledger/fabricのAPI利用方法がわかれば、chaincodeの記述は難しく無いと思います。

  1. OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう(1/3)〜 動作確認編
  2. OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう(2/3)〜 chaincode解説編
  3. OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう(3/3)〜 分散環境解説編

Hyperledger/fabricのchaincode開発環境の構築

Hyperledger/fabric 1.1のchaincodeは、Goあるいはnode.jsで記述します。最終的にはJava等も視野に入っているようですが、リポジトリ(hyperledger/fabric-chaincode-java)を見る限りまだまだというところです。

今回はGoでchaincodeを記述することにします。

goとdockerのインストール

  1. goのインストール

    • 1.9以降のgolangをインストールし、適切にGOPATHを設定してください。
  2. dockerとdoker-composeのインストール

    • chaincodeの動作確認を行えるように、17.06.2-ce 以降のdocker、及び対応するバージョンのdocker-composeをインストールしてください。

Hyperledger/fabricの取得

  1. Hyperledger/fabricの開発用ライブラリを取得する

    ubuntu@node0:~$ go get -d github.com/hyperledger/fabric/protos/peer
    ubuntu@node0:~$ go get -u --tags nopkcs11 github.com/hyperledger/fabric/core/chaincode/shim
    
    • 注意
      • githubで公開されているHyperledger/fabricをそのままgit clone (go get) しただけでは、暗号処理関連のライブラリが足りないためビルドができません。
      • そのため暗号処理を用いない開発用tag nopkcs11 を指定して github.com/hyperledger/fabric/core/chaincode/shimを上書きすることで、手元でビルドの成否が確認できるようにします。

chaincodeのスケルトンを作成しビルド

開発用ライブラリを正しくgo getできているか確認するために、ログを出すだけで何もしない空のchaincodeを作ってビルドしてみます。

  1. chaincode用のディレクトリを${GOPATH}/src以下に作成する

    ubuntu@node0:~$ mkdir -p ${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/
    ubuntu@node0:~$ cd ${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/
    
  2. chaincodeのスケルトンを作成する

    ${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/fabric-payment.go
    package main
    
    import (
        "fmt"
    
        "github.com/hyperledger/fabric/core/chaincode/shim"
        sc "github.com/hyperledger/fabric/protos/peer"
    )
    
    // shim.NewLoggerでロガーが取得できる
    var logger = shim.NewLogger("main")
    
    type EntryPoint struct {
    }
    
    // chaincodeのinstantiate時に起動される初期化メソッド
    // (今回のサンプルでは、何もしない)
    func (s *EntryPoint) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
        logger.Info("instantiated chaincode")
        return shim.Success(nil)
    }
    
    // cliやSDKからchaincodeを呼び出した際に起動するエントリーポイントとなるメソッド
    // 最終的には、指定されたfunctionに従って、具体的な処理を他のメソッドに委譲する
    func (s *EntryPoint) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
        function, args := APIstub.GetFunctionAndParameters()
    
        msg := fmt.Sprintf("No such function. function = %s, args = %s", function, args)
        logger.Error(msg)
        return shim.Error(msg)
    }
    
    func main() {
        if err := shim.Start(new(EntryPoint)); err != nil {
            logger.Errorf("Error creating new Chaincode. Error = %s\n", err)
        }
    }
    
    • InitInvokeを実装している型であれば、Hyperledger/fabricネットワーク上で実行可能なchaincodeとなります。
  3. chaincodeのスケルトンをビルドする

    • ビルド時にも、 --tags nopkcs11 を指定してください。
    ubuntu@node0:~/go/src/github.com/nmatsui/fabric-payment-sample$ go build --tags nopkcs11 fabric-payment.go
    
    • 何もエラーメッセージが表示されず、実行ファイルfabric-paymentが生成されていればビルド成功です。
    • 実行ファイルを動作させると、次のようなログが出力されると思います。
    ubuntu@node0:~/go/src/github.com/nmatsui/fabric-payment-sample$ ./fabric-payment
    2018-03-04 09:49:19.168 JST [shim] SetupChaincodeLogging -> INFO 001 Chaincode log level not provided; defaulting to: INFO
    2018-03-04 09:49:19.168 JST [shim] SetupChaincodeLogging -> INFO 002 Chaincode (build level: ) starting up ...
    2018-03-04 09:49:19.168 JST [main] Error -> ERRO 003 Error creating new Chaincode. Error = Error chaincode id not provided
    

モデルの作成

Hyperledger/fabricは、JSON(のバイト配列)としてデータを台帳に記帳します。そのため、JSON化できるのであればどのような非定型データであれ扱うことはできますが、ある程度固定されたデータ構造を定義しておいた方がchaincodeの見通しがよくなります。また記帳する最新データ(state)の保存先としてcouchdbを用いる場合は、データ構造が固定されていればIndexを有効に活用できます。
そこでまず、記帳したいデータ構造をstructとして定義します。

以下で紹介するコードでは、importやエラー処理を省略しています。実際のコードはgithubリポジトリを参照してください。

モデルを定義する際の注意点

Hyperleder/fabricは、台帳にデータを記帳する際に以下のようなステップで処理を行います。

  1. SDKはpeerにトランザクションのproposalを送信
  2. peerは自身の最新の台帳状態を用いてchaincodeをシミュレート実行する
    • 無限ループに陥るなどしないことを確認
    • シミュレートに成功した場合、「シミュレート時点の台帳の状態」と「更新後の台帳の状態」をSDKに返す
      • この時点ではまだ台帳に記帳されない
  3. シミュレート実行が全て成功した場合、SDKは(peerをproxyにして)ordererにトランザクション処理を依頼する
  4. ordererは依頼されたトランザクションを整列させ、各peerに順序立ててコミットを依頼する
  5. 各peerは、「シミュレート時点の台帳の状態」が他のトランザクションによって更新されていないことを確認し、更新後の状態を記帳してトランザクションをコミットする

Figure 1. Illustration of one possible transaction flow (common-case path).
出典:http://hyperledger-fabric.readthedocs.io/en/release-1.1/arch-deep-dive.html

そのため、複数のトランザクションが並列して「同じデータ」を更新しようとすると、どれか一つのトランザクション以外は全て却下されてしまいます。つまり、毎回同じデータを更新するようなchaincodeは、更新スループットが劣悪になってしまうということです(実際に性能テストをしたわけではありませんが・・・)。
モデルの定義時には、このトランザクション処理のアーキテクチャに十分注意しましょう。

モデル種別を定義する

今回のサンプルアプリでは、「口座」と「イベント(入金・送金・出金)」という2つのモデルを扱います。これらの種別を判別するための型を定義します。

  • 口座(AccountModel)
  • イベント(EventModel)
    • 入金(DepositEvent)
    • 送金(RemitEvent)
    • 出金(WithdrawEvent)

ただしGoには、Enum型がありません。そこで以下のような、JSON serializableなEnumもどきの型を定義します。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/types/model_type.go
const (
    unknownModelStr = "unknown"
    accountModelStr = "account"
    eventModelStr   = "event"
)

// ModelType : model type
type ModelType int

// concrete ModelType
const (
    UnKnownModel ModelType = iota
    AccountModel
    EventModel
)

// String : Stringer interface
func (t ModelType) String() string {
    switch t {
    case AccountModel:
        return accountModelStr
    case EventModel:
        return eventModelStr
    default:
        return unknownModelStr
    }
}

// MarshalJSON : Marshaler interface
func (t ModelType) MarshalJSON() ([]byte, error) {
    return json.Marshal(t.String())
}

// UnmarshalJSON : Marshaler interface
func (t *ModelType) UnmarshalJSON(data []byte) error {
    var s string
    switch s {
    case accountModelStr:
        *t = AccountModel
    case eventModelStr:
        *t = EventModel
    default:
        *t = UnKnownModel
    }
    return nil
}

モデルを定義する

AccountモデルとEventモデルは、次のようになります。非常にシンプルです。
台帳からモデル種別で絞り込んでstateを検索できるように、ModelTypeやEventTypeを型に組み込んでいます。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/models/account.go
type Account struct {
    ModelType types.ModelType `json:"model_type"`
    No        string          `json:"no"`
    Name      string          `json:"name"`
    Balance   int             `json:"balance"`
}
${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/models/event.go
type AccountState struct {
    No              string `json:"no"`
    Name            string `json:"name"`
    PreviousBalance int    `json:"previous_balance"`
    CurrentBalance  int    `json:"current_balance"`
}

type Event struct {
    ModelType        types.ModelType `json:"model_type"`
    EventType        types.EventType `json:"event_type"`
    No               string          `json:"no"`
    Amount           int             `json:"amount"`
    FromAccountState *AccountState   `json:"from_account"`
    ToAccountState   *AccountState   `json:"to_account"`
}

口座のCRUD

次に口座(Account)の検索、作成、変更、削除を行うchaincodeを記述します。単なる関数として実装しても良いのですが、処理をグループ化するためにAccountContract型のメソッドとして定義します。

REST APIで指定したパラメータは、SDK経由で引数のargs []stringに渡されてきます。

口座一覧の取得(ListAccount)

couchdbのクエリ文字列を引数としてshim.ChaincodeStubInterface.GetQueryResultを呼び出すことで、stateから最新の口座を一覧で取得します。

LevelDBを用いる場合、クエリを利用することができません。この場合、CompositeKeyという機能を使うことになります。詳細は、公式のサンプルコードを参照してください。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/account.go
// AccountContract : a struct which has the methods related to manage Account
type AccountContract struct {
}

// ListAccount : return a list of all accounts
func (ac *AccountContract) ListAccount(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    accountLogger.Infof("invoke ListAccount, args=%s\n", args)
    if len(args) != 0 { //パラメータ数をチェック(口座一覧取得にはパラメータが無いはず)
    }

    // stateから最新のAccountを全て取得するクエリを生成(couchdbのQuery文法に従う)
    query := map[string]interface{}{
        "selector": map[string]interface{}{
            "model_type": types.AccountModel,
        },
    }
    queryBytes, err := json.Marshal(query)
    accountLogger.Infof("Query string = '%s'", string(queryBytes))

    // クエリを用いてstateから最新のAccount一覧を取得するIteratorを生成
    resultsIterator, err := APIstub.GetQueryResult(string(queryBytes))
    // 取得したIteratorは、メソッド離脱時にCloseする
    defer resultsIterator.Close() 

    // stateから取得したJSONを*Account型にキャストしてsliceに追加
    results := make([]*models.Account, 0)
    for resultsIterator.HasNext() {
        queryResponse, err := resultsIterator.Next()
        account := new(models.Account)
        results = append(results, account)
    }

    // レスポンスとなるJSONのバイト配列を生成
    jsonBytes, err := json.Marshal(results)
    return shim.Success(jsonBytes)
}

口座の新規作成(CreateAccount)

AccontオブジェクトのJSONのバイト配列を、口座番号をキーとしてshim.ChaincodeStubInterface.PutStateに渡せば、口座情報を新規登録できます。
なお同一キーがある場合、PutStateはそのオブジェクトを上書きしてしまうため、口座番号は一意でなければなりません。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/account.go
// CreateAccount : create a new account
func (ac *AccountContract) CreateAccount(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    accountLogger.Infof("invoke CreateAccount, args=%s\n", args)
    if len(args) != 1 {
    }
    name := args[0] // 一つ目のパラメータが "name"

    // 一意な口座番号を生成する(詳細はgithubのソースコードを参照)
    no, err := utils.GetAccountNo(APIstub) 

    // Accountオブジェクトを生成してJSONバイト配列化
    account := models.Account{
        ModelType: types.AccountModel,
        No:        no,
        Name:      name,
        Balance:   0,
    }
    jsonBytes, err := json.Marshal(account)

    // Accountオブジェクトを保存
    if err := APIstub.PutState(no, jsonBytes); err != nil {
    }
    // 作成したAccountオブジェクトのJSONバイト配列をレスポンスとして返す
    return shim.Success(jsonBytes)
}

口座の取得(RetrieveAccount)

Accountオブジェクトは口座番号をキーとして台帳に記帳されていますので、shim.ChaincodeStubInterface.GetStateに口座番号を渡せばAccountオブジェクト(のバイト配列)が得られます。Accountオブジェクトは口座名の更新や口座削除、入送出金処理でも必要になりますので、関数化しています。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/utils/validator.go
func GetAccount(APIstub shim.ChaincodeStubInterface, no string) (*models.Account, error) {
    var account = new(models.Account) // 空のAccountオブジェクトを生成
    accountBytes, err := APIstub.GetState(no)
    if err != nil { // GetStateでエラーが発生した場合は、空のAccountオブジェクトと発生したerrを返す
        return account, err
    } else if accountBytes == nil { // Accountオブジェクトが無い場合は、Error interfaceを実装したWarningResultオブジェクトを返す(WarningResultの詳細はgithubのソースコードを参照)
        msg := fmt.Sprintf("Account does not exist, no = %s", no)
        warning := &WarningResult{StatusCode: 404, Message: msg}
        return account, warning
    }

    if err := json.Unmarshal(accountBytes, account); err != nil {
        return account, err // AccountオブジェクトをJSONバイト配列化できなかった場合はエラーを返す
    }
    return account, nil // 正常に取得できた場合、エラーをnilとしてAccountオブジェクトを返す
}
${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/account.go
func (ac *AccountContract) RetrieveAccount(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    accountLogger.Infof("invoke RetrieveAccount, args=%s\n", args)
    if len(args) != 1 {
    }
    no := args[0]

    // 引数として渡された口座番号をキーにしてAccountオブジェクトをstateから取得
    account, err := utils.GetAccount(APIstub, no)
    jsonBytes, err := json.Marshal(account)
    // 取得したAccountオブジェクトのJSONバイト配列をレスポンスとして返す
    return shim.Success(jsonBytes)
}

口座名の更新(UpdateAccountName)

shim.ChaincodeStubInterface.GetStateに口座番号を指定してAccountオブジェクトを取得し、名前を変更した後にshim.ChaincodeStubInterface.PutStateすれば、口座名を変更できます。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/account.go
// UpdateAccountName : update the name of an account
func (ac *AccountContract) UpdateAccountName(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    accountLogger.Infof("invoke UpdateAccountName, args=%s\n", args)
    if len(args) != 2 {
    }
    no := args[0]
    name := args[1]

    // 引数として渡された口座番号をキーにしてAccountオブジェクトをstateから取得
    account, err := utils.GetAccount(APIstub, no)

    // 取得したAccountオブジェクトのnameを更新
    account.Name = name

    jsonBytes, err := json.Marshal(account)
    // nameを変更したAccountオブジェクトを保存
    if err := APIstub.PutState(no, jsonBytes); err != nil {
    }
    // 更新後のAccountオブジェクトのJSONバイト配列をレスポンスとして返す
    return shim.Success(jsonBytes)
}

口座の削除(DeleteAccount)

口座番号を指定してshim.ChaincodeStubInterface.DelStateすれば、その口座を削除することができます。存在しない口座は削除できないので、DelStateする前にAccountオブジェクトの存在確認をしています。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/account.go
// DeleteAccount : delete an account
func (ac *AccountContract) DeleteAccount(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    accountLogger.Infof("invoke DeleteAccount, args=%s\n", args)
    if len(args) != 1 {
    }
    no := args[0]

    // 引数として渡された口座番号をキーにしてAccountオブジェクトをstateから取得(取得したAccountオブジェクトは使わないため、_で受ける
    _, err := utils.GetAccount(APIstub, no)

    // 口座番号を指定して、保存されているオブジェクトをstate dbから削除
    if err := APIstub.DelState(no); err != nil {
    }
    return shim.Success(nil)
}

入金・送金・出金

入金・出金・送金処理も、口座の操作と同様、GetQueryResultやGetState、PutStateを用いて記述することができます。

入金(Deposit)

入金先のAccountオブジェクトの残高更新と、入金イベントの記録を同時に行います。これら二つのオブジェクトの更新は、一つのトランザクションとして処理されます。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/event.go
// EventContract : a struct which has the methods related to event
type EventContract struct {
}

// Deposit : deposit amount to an account
func (ec *EventContract) Deposit(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    eventLogger.Infof("invoke Deposit, args=%s\n", args)
    if len(args) != 2 {
    }
    toAccountNo := args[0] // 一つ目の引数は、入金先の口座番号
    amountStr := args[1] // 二つ目の引数は、入金額(の文字列)

    // 入金額をvalidate(0より大きい整数であること)
    amount, err := utils.GetAmount(amountStr)

    // 入金先のAccountオブジェクトを取得
    toAccount, err := utils.GetAccount(APIstub, toAccountNo)

    // 一意なイベント番号を生成
    eventNo, err := utils.GetEventNo(APIstub)

    // 入金前の残高と入金後の残高を計算
    toAccountPreviousBalance := toAccount.Balance
    toAccount.Balance += amount

    // 残高の変化を記録したAccountStateオブジェクトを作成
    toAccountState := &models.AccountState{
        No:              toAccount.No,
        Name:            toAccount.Name,
        PreviousBalance: toAccountPreviousBalance,
        CurrentBalance:  toAccount.Balance,
    }

    // 入金イベントを記録したEventオブジェクトを作成
    event := &models.Event{
        ModelType:        types.EventModel,
        EventType:        types.DepositEvent,
        No:               eventNo,
        Amount:           amount,
        FromAccountState: nil,
        ToAccountState:   toAccountState,
    }

    // 残高更新後のAccountオブジェクトを保存
    toAccountBytes, err := json.Marshal(toAccount)
    if err := APIstub.PutState(toAccount.No, toAccountBytes); err != nil {
    }

    // 入金Eventオブジェクトを保存
    eventBytes, err := json.Marshal(event)
    if err := APIstub.PutState(event.No, eventBytes); err != nil {
    }
    return shim.Success(eventBytes)
}

送金(Remit)

送金元と送金先のAccountオブジェクトの残高更新と、送金イベントの記録を同時に行います。送金元の残高が0より小さくなる場合はエラーを発生させます。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/event.go
// Remit : remit amount from an account to another account
func (ec *EventContract) Remit(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    eventLogger.Infof("invoke Remit, args=%s\n", args)
    if len(args) != 3 {
    }
    fromAccountNo := args[0]
    toAccountNo := args[1]
    amountStr := args[2]

    // 送金額をvalidate(0より大きい整数であること)
    amount, err := utils.GetAmount(amountStr)

    // 送金元のAccountオブジェクトを取得
    fromAccount, err := utils.GetAccount(APIstub, fromAccountNo)
    // 送金先のAccountオブジェクトを取得
    toAccount, err := utils.GetAccount(APIstub, toAccountNo)

    if fromAccount.Balance < amount {
        // 残高不足の場合はエラー発生
    }

    // 一意なイベント番号を生成
    eventNo, err := utils.GetEventNo(APIstub)

    // 送金元の残高から送金額を減額
    fromAccountPreviousBalance := fromAccount.Balance
    fromAccount.Balance -= amount

    // 送金先の残高へ送金額を増額
    toAccountPreviousBalance := toAccount.Balance
    toAccount.Balance += amount

    // 送金元の残高の変化を記録したAccountStateオブジェクトを作成
    fromAccountState := &models.AccountState{
        No:              fromAccount.No,
        Name:            fromAccount.Name,
        PreviousBalance: fromAccountPreviousBalance,
        CurrentBalance:  fromAccount.Balance,
    }

    // 送金先の残高の変化を記録したAccountStateオブジェクトを作成
    toAccountState := &models.AccountState{
        No:              toAccount.No,
        Name:            toAccount.Name,
        PreviousBalance: toAccountPreviousBalance,
        CurrentBalance:  toAccount.Balance,
    }

    // 送金Eventオブジェクトを作成
    event := &models.Event{
        ModelType:        types.EventModel,
        EventType:        types.RemitEvent,
        No:               eventNo,
        Amount:           amount,
        FromAccountState: fromAccountState,
        ToAccountState:   toAccountState,
    }

    // 残高更新後のAccountオブジェクトを保存
    fromAccountBytes, err := json.Marshal(fromAccount)
    if err := APIstub.PutState(fromAccount.No, fromAccountBytes); err != nil {
    }

    toAccountBytes, err := json.Marshal(toAccount)
    if err := APIstub.PutState(toAccount.No, toAccountBytes); err != nil {
    }

    // 送金Eventオブジェクトを保存
    eventBytes, err := json.Marshal(event)
    if err := APIstub.PutState(event.No, eventBytes); err != nil {
    }
    return shim.Success(eventBytes)
}

出金(Withdraw)

出金元のAccountオブジェクトの残高更新と、出金イベントを記録します。送金と同様、出金元の残高が0より小さくなる場合はエラーを発生させます。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/event.go
// Withdraw : withdraw amount from an account
func (ec *EventContract) Withdraw(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    eventLogger.Infof("invoke Withdraw, args=%s\n", args)
    if len(args) != 2 {
    }
    fromAccountNo := args[0]
    amountStr := args[1]

    // 出金額をvalidate(0より大きい整数であること)
    amount, err := utils.GetAmount(amountStr)

    // 出金元のAccountオブジェクトを取得
    fromAccount, err := utils.GetAccount(APIstub, fromAccountNo)

    if fromAccount.Balance < amount {
        // 残高不足の場合はエラー発生
    }

    // 一意なイベント番号を生成
    eventNo, err := utils.GetEventNo(APIstub)

    // 出金後の残高を計算
    fromAccountPreviousBalance := fromAccount.Balance
    fromAccount.Balance -= amount

    // 出金元の残高の変化を記録したAccountStateオブジェクトを作成
    fromAccountState := &models.AccountState{
        No:              fromAccount.No,
        Name:            fromAccount.Name,
        PreviousBalance: fromAccountPreviousBalance,
        CurrentBalance:  fromAccount.Balance,
    }

    // 出金Eventオブジェクトを作成
    event := &models.Event{
        ModelType:        types.EventModel,
        EventType:        types.WithdrawEvent,
        No:               eventNo,
        Amount:           amount,
        FromAccountState: fromAccountState,
        ToAccountState:   nil,
    }

    // 残高更新後のAccountオブジェクトを保存
    fromAccountBytes, err := json.Marshal(fromAccount)
    if err := APIstub.PutState(fromAccount.No, fromAccountBytes); err != nil {
    }

    // 出金Eventオブジェクトを保存
    eventBytes, err := json.Marshal(event)
    if err := APIstub.PutState(event.No, eventBytes); err != nil {
    }
    return shim.Success(eventBytes)
}

入金・送金・出金の一覧取得

口座一覧の取得と同様、shim.ChaincodeStubInterface.GetQueryResultを呼び出すことでstateから最新のイベント(入金・送金・出金)を一覧で取得します。また引数として"deposit|remit|withdraw"のいずれかが引き渡されている場合は、それらのイベントに絞り込んで検索することもできるようにしています。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/event.go
// ListEvent : return a list of all events
func (ec *EventContract) ListEvent(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    eventLogger.Infof("invoke ListEvent, args=%s\n", args)
    if len(args) > 1 {
    }

    // "model_type"として"event"を指定したクエリを生成
    query := map[string]interface{}{
        "selector": map[string]interface{}{
            "model_type": types.EventModel,
        },
    }

    // 一つ目の引数が渡されている場合、"event_type"として"deposit" or "remit" or "withdraw"をクエリに追加指定
    if len(args) == 1 {
        switch args[0] {
        case types.DepositEvent.String():
            query["selector"].(map[string]interface{})["event_type"] = types.DepositEvent
        case types.RemitEvent.String():
            query["selector"].(map[string]interface{})["event_type"] = types.RemitEvent
        case types.WithdrawEvent.String():
            query["selector"].(map[string]interface{})["event_type"] = types.WithdrawEvent
        default:
            // "deposit", "remit", "withdraw" 以外の引数はエラー
        }
    }

    // クエリJSONのバイト配列を生成
    queryBytes, err := json.Marshal(query)
    eventLogger.Infof("Query string = '%s'", string(queryBytes))
    // クエリを用いてstateから最新のEvent一覧を取得するIteratorを生成
    resultsIterator, err := APIstub.GetQueryResult(string(queryBytes))
    // 取得したIteratorは、メソッド離脱時にCloseする
    defer resultsIterator.Close()

    // stateから取得したJSONを*Event型にキャストしてsliceに追加
    results := make([]*models.Event, 0)
    for resultsIterator.HasNext() {
        queryResponse, err := resultsIterator.Next()
        event := new(models.Event)
        if err := json.Unmarshal(queryResponse.Value, event); err != nil {
        }
        results = append(results, event)
    }
    jsonBytes, err := json.Marshal(results)
    return shim.Success(jsonBytes)
}

更新履歴の取得

Hyperledter/fabricは台帳に記帳されたデータの更新情報を全て記録していますので、更新履歴を取得することも容易です。キーを指定して更新履歴を取得するshim.ChaincodeStubInterface.GetHistoryForKeyというAPIが提供されてます。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/history.go
// HistoryContract : a struct which has the methods related to query Histories
type HistoryContract struct {
}

// ListHistory : return all histories of a contract
func (hc *HistoryContract) ListHistory(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
    historyLogger.Infof("invoke ListHistory, args=%s\n", args)
    if len(args) != 1 {
    }
    no := args[0] // 引数としてキー(口座番号かイベント番号)を渡す

    // 指定したキーの更新履歴のIteratorを取得する
    resultsIterator, err := APIstub.GetHistoryForKey(no)
    // 取得したIteratorは、メソッド離脱時にCloseする
    defer resultsIterator.Close()

    // 指定した更新履歴をhistoryType型にキャストしてsliceに追加
    histories := make([]*historyType, 0)
    for resultsIterator.HasNext() {
        response, err := resultsIterator.Next()

        // 指定したキーが口座であれイベントであれ同じように扱えるように、Account型やEvent型ではなく、どのようなJSONでも受け付けることのできる map[string]interface{} 型を宣言する
        var state map[string]interface{}

        // 削除済みのオブジェクトでは無いならば、response.valueとして記帳時点のオブジェクトの状態がJSONバイト配列として得られる
        if !response.IsDelete {
            if err := json.Unmarshal(response.Value, &state); err != nil {
                historyLogger.Error(err.Error())
                return shim.Error(err.Error())
            }
        }
        // 変更履歴オブジェクトを生成
        history := &historyType{
            TxID:      response.TxId, // Hyperledger/fabricが生成したトランザクションID
            No:        no, // 口座番号かイベント番号
            State:     state, // 記帳された時点でのオブジェクトの状態
            Timestamp: time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)).String(), // トランザクションがコミットされた日時
            IsDelete:  response.IsDelete, // 削除済みフラグ
        }
        // 変更履歴オブジェクトをsliceに追加
        histories = append(histories, history)
    }
    jsonBytes, err := json.Marshal(histories)
    return shim.Success(jsonBytes)
}

エントリポイントの更新

口座の操作や入送出金処理が実装できたら、cliやSDKから呼び出せるようにInvokeメソッドを更新します。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/fabric-payment.go
var accountContract = new(contracts.AccountContract)
var eventContract = new(contracts.EventContract)
var historyContract = new(contracts.HistoryContract)

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

    switch function {
    case "listAccount":
        return accountContract.ListAccount(APIstub, args)
    case "createAccount":
        return accountContract.CreateAccount(APIstub, args)
    case "retrieveAccount":
        return accountContract.RetrieveAccount(APIstub, args)
    case "updateAccountName":
        return accountContract.UpdateAccountName(APIstub, args)
    case "deleteAccount":
        return accountContract.DeleteAccount(APIstub, args)
    case "listEvent":
        return eventContract.ListEvent(APIstub, args)
    case "deposit":
        return eventContract.Deposit(APIstub, args)
    case "remit":
        return eventContract.Remit(APIstub, args)
    case "withdraw":
        return eventContract.Withdraw(APIstub, args)
    case "listHistory":
        return historyContract.ListHistory(APIstub, args)
    }
    msg := fmt.Sprintf("No such function. function = %s, args = %s", function, args)
    logger.Error(msg)
    return shim.Error(msg)
}

couchdbのインデックス作成

クエリを用いてオブジェクトを取得するshim.ChaincodeStubInterface.GetQueryResultは柔軟に利用できて便利ですが、デフォルトの状態だと全オブジェクトを探索してしまいます。Hyperledger 1.1からは、couchdbにindexを作成させることができるようになりました。またindexを活用すると、検索結果のsortも指定することができます。(CouchDB as the State Database
クエリを用いる場合は、indexを上手く活用しましょう。

実行されるクエリ文字列を確認する

chaincodeコンテナのログを確認すると、どのようなクエリ文字列が利用されたのかがわかります。
例えば口座一覧を取得した場合は{"selector":{"model_type":"account"}}、入金イベント一覧を取得した場合は{"selector":{"event_type":"deposit","model_type":"event"}}のようなクエリ文字列が利用されていることがわかります。

ubuntu@node0:~$ docker logs -f $(docker ps -q -f name=dev-peer)
...
2018-03-04 03:16:50.976 UTC [contracts/account] Infof -> INFO 004 Query string = '{"selector":{"model_type":"account"}}'
...
2018-03-04 03:19:44.746 UTC [contracts/event] Infof -> INFO 012 Query string = '{"selector":{"event_type":"deposit","model_type":"event"}}'

発行されるクエリ文字列にマッチするように、indexを定義します。

indexを定義する

chaincodeに META-INF/statedb/couchdb/indexes/*.json というJSONファイルが含まれていると、couchdbのindexを自動生成します。
例えばmodel_typeを指定したクエリに対するindexとしては、次のようなJSONになります。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/META-INF/statedb/couchdb/indexes/model_index.json
{
  "index": {
    "fields": ["model_type"]
  },
  "ddoc": "modelIndexDoc",
  "name":"modelIndex",
  "type":"json"
}

index定義JSONのフォーマットは、couchdbのマニュアルを参照してください。
クエリ文字列のパターンを見ると、これ以外にもmodel_typeevent_typeを双方指定したクエリに対するindexも必要なことがわかります。

couchdbのDBを確認する

チャネルにchaincodeをinstallしてinstantiateすると、couchdbに <<CHANNEL_NAME>>_<<CHAINCODE_NAME>> というstateを保持するためのDBが作成されます。今回のサンプルでは、 fabric-sample_fabric-payment がstate DBになります。

ubuntu@node0:~$ docker exec cli curl http://localhost:5984/_all_dbs/ | jq .
[
  "_global_changes",
  "_replicator",
  "_users",
  "fabric-sample_",
  "fabric-sample_fabric-payment",
  "fabric-sample_lscc"
]

設定したindexを確認する

上記で確認したstate DBに対して /_index/をGETすると、state DBにどのようなindexが設定されているか確認できます。model_typeを指定したクエリに対するindexと、model_type及びevent_typeを指定したクエリに対するindexが定義されていることがわかります。

ubuntu@node0:~$ docker exec cli curl http://couchdb0:5984/fabric-sample_fabric-payment/_index/ | jq .
{
  "total_rows": 3,
  "indexes": [
    {
      "ddoc": null,
      "name": "_all_docs",
      "type": "special",
      "def": {
        "fields": [
          {
            "_id": "asc"
          }
        ]
      }
    },
    {
      "ddoc": "_design/modelEventIndexDoc",
      "name": "modelEventIndex",
      "type": "json",
      "def": {
        "fields": [
          {
            "model_type": "asc"
          },
          {
            "event_type": "asc"
          }
        ],
        "partial_filter_selector": {}
      }
    },
    {
      "ddoc": "_design/modelIndexDoc",
      "name": "modelIndex",
      "type": "json",
      "def": {
        "fields": [
          {
            "model_type": "asc"
          }
        ],
        "partial_filter_selector": {}
      }
    }
  ]
}

indexが利用されることを確認する

couchdbのDBに対して /_explain/ にクエリ文字列を与えてPOSTすると、そのクエリが発行された場合にどのようなindexが利用されるか、実行計画を確認することができます。設定したindexが利用されていることを確認しましょう。

ubuntu@node0:~$ docker exec cli curl -H "Content-Type: application/json" http://couchdb0:5984/fabric-sample_fabric-payment/_explain/ -X POST -d '{"selector":{"model_type":"account"}}' | jq .
{
  "dbname": "fabric-sample_fabric-payment",
  "index": {
    "ddoc": "_design/modelIndexDoc",
    "name": "modelIndex",
    "type": "json",
    "def": {
      "fields": [
        {
          "model_type": "asc"
        }
      ],
      "partial_filter_selector": {}
    }
  },
  "selector": {
    "model_type": {
      "$eq": "account"
    }
  },
...
ubuntu@node0:~$ docker exec cli curl -H "Content-Type: application/json" http://couchdb0:5984/fabric-sample_fabric-payment/_explain/ -X POST -d '{"selector":{"event_type":"deposit","model_type":"event"}}' | jq .
{
  "dbname": "fabric-sample_fabric-payment",
  "index": {
    "ddoc": "_design/modelEventIndexDoc",
    "name": "modelEventIndex",
    "type": "json",
    "def": {
      "fields": [
        {
          "model_type": "asc"
        },
        {
          "event_type": "asc"
        }
      ],
      "partial_filter_selector": {}
    }
  },
  "selector": {
    "$and": [
      {
        "event_type": {
          "$eq": "deposit"
        }
      },
      {
        "model_type": {
          "$eq": "event"
        }
      }
    ]
  },
...

chaincodeの実行

お疲れ様でした。これで一通りchaincodeが完成です。作成したchaincodeを実行してみましょう。第一回で構築した、単体のdocker環境を用います。

solo.png

ただし、REST APIからSDK経由で呼び出すのではなく、cliコンテナから直接chaincodeを起動します。

開発用dockerコンテナを起動

  1. fabric-payment-sample-docker/dev-solo/ に移動

    ubuntu@node0:~$ cd fabric-payment-sample-docker/dev-solo/
    
  2. 各コンポーネントの暗号鍵やアーティファクトを生成する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ source .env
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ export CA_ADMIN_PASSWORD=<<任意の文字列>>
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ ./generate_artifact.sh ${CA_ADMIN_PASSWORD}
    
  3. 環境変数を設定する

    ORDERER_ADDRESS="orderer:7050"
    PEER_ADDRESS="peer:7051"
    LOCALMSPID="Org1MSP"
    PEER_MSPCONFIGPATH="/etc/hyperledger/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp"
    CHAINCODE_SRC="github.com/nmatsui/fabric-payment-sample-chaincode"
    CHAINCODE_VERSION="0.1"
    
    • fabric-payment-sample-chaincode以外の名前にした場合、CHAINCODE_SRCを適切に設定してください。
  4. docker内部にbridge networkを作成する

    docker network create --driver=bridge --attachable=true fabric-sample-nw
    
    • docker-compose.yamlでは、fabric-sample-nwというネットワークにコンテナを接続します。ネットワーク名を変更した場合はdocker-compose.yamlも修正してください。
  5. docker-composeを用いてコンテナ群を起動する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker-compose -f docker-compose.yaml up -d
    
    • peer, couchdb, orderer, cliの各コンテナが起動していることを確認
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker ps
    CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                                              NAMES
    41b3370d272c        fabric-payment/api           "npm start"              37 seconds ago      Up 35 seconds       0.0.0.0:3000->3000/tcp                             api
    070fe3e78f64        hyperledger/fabric-tools     "/bin/bash"              37 seconds ago      Up 36 seconds                                                          cli
    23efcf53f8c1        hyperledger/fabric-peer      "peer node start"        38 seconds ago      Up 36 seconds       0.0.0.0:32772->7051/tcp, 0.0.0.0:32771->7053/tcp   peer
    b13b50dcffc8        hyperledger/fabric-couchdb   "tini -- /docker-ent…"   38 seconds ago      Up 37 seconds       4369/tcp, 9100/tcp, 0.0.0.0:32769->5984/tcp        couchdb
    bf9770e74b1e        hyperledger/fabric-orderer   "orderer"                38 seconds ago      Up 37 seconds       0.0.0.0:32770->7050/tcp                            orderer
    85d6c89d3596        hyperledger/fabric-ca        "sh -c 'fabric-ca-se…"   38 seconds ago      Up 36 seconds       0.0.0.0:32768->7054/tcp                            ca
    
  6. cliコンテナからチャネルを作成する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" cli peer channel create -o ${ORDERER_ADDRESS} -c ${CHANNEL_NAME} -f /etc/hyperledger/artifacts/channel.tx
    
  7. peerをチャネルにjoinさせる

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" cli peer channel join -b /etc/hyperledger/artifacts/${CHANNEL_NAME}.block
    
  8. anchorを更新する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" cli peer channel update -o ${ORDERER_ADDRESS} -c ${CHANNEL_NAME} -f /etc/hyperledger/artifacts/Org1MSPanchors.tx
    

chaincodeの初回インストール

  1. 作成したchaincodeをインストールする

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" cli peer chaincode install -n ${CHAINCODE_NAME} -p ${CHAINCODE_SRC} -v ${CHAINCODE_VERSION}
    
  2. chaincodeをinstantiateする

    • -c '{"Args":[""]}'にて渡す文字列は、chaincodeのInitメソッドの引数となります。今回のchaincodeのInitメソッドは何の引数も取らないため、何も渡しません。
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" cli peer chaincode instantiate -o ${ORDERER_ADDRESS} -C ${CHANNEL_NAME} -n ${CHAINCODE_NAME} -v ${CHAINCODE_VERSION} -c '{"Args":[""]}' -P "OR ('Org1MSP.member')"
    
    • instantiateに成功すると、dev-<<peerのID>>-<<CHAINCODE_NAME>>-バージョン番号-...というchaincodeコンテナが自動起動しているはずです。
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker ps
    CONTAINER ID        IMAGE                                                                                                            COMMAND                  CREATED             STATUS              PORTS                                              NAMES
    6e4dd2c7baff        dev-peer0.org1.example.com-fabric-payment-0.1-5937be8eee46b8bca564d23f7316cb6fd253fb536b7aece7268323db087b8073   "chaincode -peer.add…"   2 minutes ago       Up 2 minutes                                                           dev-peer0.org1.example.com-fabric-payment-0.1
    41b3370d272c        fabric-payment/api                                                                                               "npm start"              12 minutes ago      Up 12 minutes       0.0.0.0:3000->3000/tcp                             api
    070fe3e78f64        hyperledger/fabric-tools                                                                                         "/bin/bash"              12 minutes ago      Up 12 minutes                                                          cli
    23efcf53f8c1        hyperledger/fabric-peer                                                                                          "peer node start"        12 minutes ago      Up 12 minutes       0.0.0.0:32772->7051/tcp, 0.0.0.0:32771->7053/tcp   peer
    b13b50dcffc8        hyperledger/fabric-couchdb                                                                                       "tini -- /docker-ent…"   12 minutes ago      Up 12 minutes       4369/tcp, 9100/tcp, 0.0.0.0:32769->5984/tcp        couchdb
    bf9770e74b1e        hyperledger/fabric-orderer                                                                                       "orderer"                12 minutes ago      Up 12 minutes       0.0.0.0:32770->7050/tcp                            orderer
    85d6c89d3596        hyperledger/fabric-ca                                                                                            "sh -c 'fabric-ca-se…"   12 minutes ago      Up 12 minutes       0.0.0.0:32768->7054/tcp                            ca
    

cliからchaincodeを実行(検索)

ListAccountやRetrieveAccountなど、台帳の状態を更新しないchaincodeを実行する場合、cliコンテナでpeer chaincode queryというコマンドを実行します。
-c '{"Args":["listAccount"]}'の一つ目の引数は実際の処理を委譲する関数のラベルで、二つ目以降は関数に引数として渡されます。

${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/fabric-payment.go
func (s *EntryPoint) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
    function, args := APIstub.GetFunctionAndParameters() // 一つ目の引数がfunction、それ以降がargsに代入される

    switch function {
    case "listAccount":
        return accountContract.ListAccount(APIstub, args) // 一つ目の引数が "listAccount" だった場合、ListAccountメソッドに処理を委譲する
    ...
}
ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" cli peer chaincode query -C ${CHANNEL_NAME} -n ${CHAINCODE_NAME} -c '{"Args":["listAccount"]}'
2018-03-04 07:51:50.577 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2018-03-04 07:51:50.578 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2018-03-04 07:51:50.578 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2018-03-04 07:51:50.578 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2018-03-04 07:51:50.578 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
2018-03-04 07:51:50.578 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0AB9070A7508031A0C0896B4FED40510...741A0D0A0B6C6973744163636F756E74
2018-03-04 07:51:50.578 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: 5AB6FA2458A4B4B88C0D50A9FABBBEE4B085D23F93F6662F94F021378493BDA3
2018-03-04 07:51:50.678 UTC [main] main -> INFO 008 Exiting.....
Query Result: []

まだ台帳に口座を登録していないため、結果は空配列です。

cliからchaincodeを実行(更新)

CreateAccountやDepositなど、台帳の状態を更新する処理は peer chaincode invokeコマンドを用います。invokeの場合、トランザクションのコミット処理をordererに依頼しなければならないため、依頼先のordererのアドレスを引数として渡す必要があります。

ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" cli peer chaincode invoke -o ${ORDERER_ADDRESS} -C ${CHANNEL_NAME} -n ${CHAINCODE_NAME} -c '{"Args":["createAccount", " ほげほげ"]}'
2018-03-04 08:03:04.194 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2018-03-04 08:03:04.194 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2018-03-04 08:03:04.196 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2018-03-04 08:03:04.197 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2018-03-04 08:03:04.197 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
2018-03-04 08:03:04.198 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0AB8070A7408031A0B08B8B9FED40510...6E740A0CE381BBE38192E381BBE38192
2018-03-04 08:03:04.198 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: E3951FEBD9BCAA12081D106639FB9F1EA3B9D00FD00B06808F3CF195C08B5DF8
2018-03-04 08:03:04.214 UTC [msp/identity] Sign -> DEBU 008 Sign: plaintext: 0AB8070A7408031A0B08B8B9FED40510...42E66439C107E3EAD2B99A6ED0E8BE91
2018-03-04 08:03:04.215 UTC [msp/identity] Sign -> DEBU 009 Sign: digest: 30E4C37CA4F49674731E8F2C119D7136F9D3AC98F2292337D4F06F9682E1AEEA
2018-03-04 08:03:04.220 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> DEBU 00a ESCC invoke result: version:1 response:<status:200 message:"OK" payload:"{\"model_type\":\"account\",\"no\":\"3391863708800601\",\"name\":\"\343\201\273\343\201\222\343\201\273\343\201\222\",\"balance\":0}" > payload:"\n BZG\005\236N2\020\000~N\336\215H\255.\230f$\356Dm\251|\230\220\t \355\232\376X\022\244\002\n\261\001\022\216\001\n\016fabric-payment\022|\n\022\n\0203391863708800601\032f\n\0203391863708800601\032R{\"model_type\":\"account\",\"no\":\"3391863708800601\",\"name\":\"\343\201\273\343\201\222\343\201\273\343\201\222\",\"balance\":0}\022\036\n\004lscc\022\026\n\024\n\016fabric-payment\022\002\010\002\032W\010\310\001\032R{\"model_type\":\"account\",\"no\":\"3391863708800601\",\"name\":\"\343\201\273\343\201\222\343\201\273\343\201\222\",\"balance\":0}\"\025\022\016fabric-payment\032\0030.1" endorsement:<endorser:"\n\007Org1MSP\022\226\006-----BEGIN CERTIFICATE-----\nMIICGTCCAcCgAwIBAgIRAMytpI0Cnu3FKTpKDIAe3xIwCgYIKoZIzj0EAwIwczEL\nMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG\ncmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh\nLm9yZzEuZXhhbXBsZS5jb20wHhcNMTgwMzA3MDcyMzM2WhcNMjgwMzA0MDcyMzM2\nWjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN\nU2FuIEZyYW5jaXNjbzEfMB0GA1UEAxMWcGVlcjAub3JnMS5leGFtcGxlLmNvbTBZ\nMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDzdWDN8BOKdV8fC21wqvR+JmA6jwnai\n7quHCeynh2XZmFDFXE9aAeXDagKe94Amhs3epx6J8Qp8NGKC8gMbm+ejTTBLMA4G\nA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAIN/NM15GEaD1\n/7sk/O/LETlo5/vsn0/9v1o80vvtqsZCMAoGCCqGSM49BAMCA0cAMEQCIGLVBC25\nBGy5ik8w6pCy7egFiOk5q7/5B0DNvB5ZMjrgAiA/cAA4fe4sqzYBmfR1M0sVbSG5\nZbIaHX1pHlNydQ7HRg==\n-----END CERTIFICATE-----\n" signature:"0E\002!\000\361\007\251\034\327\220g\340~m`gb1\252d\000\014\314\303\036\220\t\275Am8\207lF\255^\002 \013\353\221\026,e\365>\335_\376\254\\\034\272\266B\346d9\301\007\343\352\322\271\232n\320\350\276\221" >
2018-03-04 08:03:04.221 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 00b Chaincode invoke successful. result: status:200 payload:"{\"model_type\":\"account\",\"no\":\"3391863708800601\",\"name\":\"\343\201\273\343\201\222\343\201\273\343\201\222\",\"balance\":0}"
2018-03-07 08:03:04.221 UTC [main] main -> INFO 00c Exiting.....

peer chaincode invokeコマンドはすぐに結果が戻ってきますが、この時点ではまだpeerにproposalを投げただけです。実際にトランザクションがコミットされるまでには時間がかかりますので、 peer chaincode invokeした直後にpeer chaincode queryを実行した場合、更新前のデータが表示される場合があります。RDB的にはMVCCのような挙動です。

コミットされたトランザクションは、queryすると取得することができます。

ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" cli peer chaincode query -C ${CHANNEL_NAME} -n ${CHAINCODE_NAME} -c '{"Args":["listAccount"]}'
2018-03-04 08:04:18.109 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2018-03-04 08:04:18.109 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2018-03-04 08:04:18.109 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2018-03-04 08:04:18.109 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2018-03-04 08:04:18.109 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
2018-03-04 08:04:18.110 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0AB8070A7408031A0B0882BAFED40510...741A0D0A0B6C6973744163636F756E74
2018-03-04 08:04:18.110 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: C0B00291DDB568DCB73CE29665073AFC43667F38E56B692EB285D7D5E074091F
Query Result: [{"model_type":"account","no":"3391863708800601","name":"ほげほげ","balance":0}]

chaincodeの修正

同じチャネルに同じ名前のchaincodeを再度instantiateすることはできません。Hyperledger/fabric 1.1の時点では、chaincodeをuninstallするコマンドも提供されていないようです(peer chaincdeコマンドのマニュアル

登録済みのchaincodeのupgradeをすることは可能ですので、chaincodeを修正してupgradeしてみましょう。

  1. chaincodeの修正

    • とりあえずListAccountを起動すると、問答無用でエラーが発生するように変更する
    ${GOPATH}/src/github.com/nmatsui/fabric-payment-sample-chaincode/contracts/account.go
    // ListAccount : return a list of all accounts
    func (ac *AccountContract) ListAccount(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
        accountLogger.Error("ERROR!!!!")
        return shim.Error("ERROR!!!!")
        ...
    }
    
  2. ローカルでビルドが成功することを確認

    ubuntu@node0:~/go/src/github.com/nmatsui/fabric-payment-sample-chaincode$ go build --tags nopkcs11 fabric-payment.go
    
  3. バージョンを上げてchaincodeをインストール

    ubuntu@node0:~/go/src/github.com/nmatsui/fabric-payment-sample-chaincode$ CHAINCODE_VERSION="0.1.1"
    ubuntu@node0:~/go/src/github.com/nmatsui/fabric-payment-sample-chaincode$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" cli peer chaincode install -n ${CHAINCODE_NAME} -p ${CHAINCODE_SRC} -v ${CHAINCODE_VERSION}
    
    • -vは内部的には単なる文字列として扱われているようなので、インストール済みのchaincodeのバージョンと重複しなければどのような文字列でもかまわないと思います。日時とかでも良いかもしれません。
  4. インストールしたバージョン番号を指定してchaincodeをupgrade

    • peer chaincode upgradeコマンドを用いてchaincodeをupgradeします。
    ubuntu@node0:~/go/src/github.com/nmatsui/fabric-payment-sample-chaincode$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" cli peer chaincode upgrade -o ${ORDERER_ADDRESS} -C ${CHANNEL_NAME} -n ${CHAINCODE_NAME} -v ${CHAINCODE_VERSION} -c '{"Args":[""]}' -P "OR ('Org1MSP.member')"
    
    • upgradeに成功すると、新たなバージョン番号でchaincodeコンテナが起動しているはずです。
    ubuntu@node0:~/go/src/github.com/nmatsui/fabric-payment-sample-chaincode$ docker ps
    CONTAINER ID        IMAGE                                                                                                              COMMAND                  CREATED              STATUS              PORTS                                              NAMES
    d4aeb1cade49        dev-peer0.org1.example.com-fabric-payment-0.1.1-874aea1ca4ce200d18b066ef104cb747da2d4780a3a96664ef8c724d21c25ec5   "chaincode -peer.add…"   About a minute ago   Up About a minute                                                      dev-peer0.org1.example.com-fabric-payment-0.1.1
    6e4dd2c7baff        dev-peer0.org1.example.com-fabric-payment-0.1-5937be8eee46b8bca564d23f7316cb6fd253fb536b7aece7268323db087b8073     "chaincode -peer.add…"   44 minutes ago       Up 44 minutes                                                          dev-peer0.org1.example.com-fabric-payment-0.1
    41b3370d272c        fabric-payment/api                                                                                                 "npm start"              About an hour ago    Up About an hour    0.0.0.0:3000->3000/tcp                             api
    070fe3e78f64        hyperledger/fabric-tools                                                                                           "/bin/bash"              About an hour ago    Up About an hour                                                       cli
    23efcf53f8c1        hyperledger/fabric-peer                                                                                            "peer node start"        About an hour ago    Up About an hour    0.0.0.0:32772->7051/tcp, 0.0.0.0:32771->7053/tcp   peer
    b13b50dcffc8        hyperledger/fabric-couchdb                                                                                         "tini -- /docker-ent…"   About an hour ago    Up About an hour    4369/tcp, 9100/tcp, 0.0.0.0:32769->5984/tcp        couchdb
    bf9770e74b1e        hyperledger/fabric-orderer                                                                                         "orderer"                About an hour ago    Up About an hour    0.0.0.0:32770->7050/tcp                            orderer
    85d6c89d3596        hyperledger/fabric-ca                                                                                              "sh -c 'fabric-ca-se…"   About an hour ago    Up About an hour    0.0.0.0:32768->7054/tcp                            ca
    
  5. 更新したchaincodeを実行する

    ubuntu@node0:~/go/src/github.com/nmatsui/fabric-payment-sample-chaincode$ docker exec -e "CORE_PEER_LOCALMSPID=${LOCALMSPID}" -e "CORE_PEER_MSPCONFIGPATH=${PEER_MSPCONFIGPATH}" -e "CORE_PEER_ADDRESS=${PEER_ADDRESS}" cli peer chaincode query -C ${CHANNEL_NAME} -n ${CHAINCODE_NAME} -c '{"Args":["listAccount"]}'
    2018-03-07 08:32:21.665 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
    2018-03-07 08:32:21.666 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
    2018-03-07 08:32:21.666 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
    2018-03-07 08:32:21.666 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
    2018-03-07 08:32:21.666 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
    2018-03-07 08:32:21.667 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0AB9070A7508031A0C0895C7FED40510...741A0D0A0B6C6973744163636F756E74
    2018-03-07 08:32:21.667 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: F7B0A9B235CE8CDAD556F79B9626C805BB59B95ACBB8C34E46BA99D6734CDAE8
    Error: Error endorsing query: rpc error: code = Unknown desc = chaincode error (status: 500, message: ERROR!!!!) - <nil>
    ...
    
    • 修正したように、エラーが発生していることが確認できます。

次回は

次回は、docker swarmクラスタ上でHyperledger/fabricを分散動作させてみます。

8
5
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
8
5