Amazon Managed Blockchain 〜WorldStateとPrivateDataの利用方法〜

by 仲尾 和祥

はじめに

AdFraud情報共有プラットフォームは、データマーケットの商品選択として、実データを抽象化したデータジャケットの考えを利用しています。また、コンソーシアムの参加者間の情報共有の手段としてAmazon Managed Blockchainを利用するため、このデータジャケット自体もChaincodeで実装しています。

本記事では、このデータジャケットのChaincodeを実装する際に試したWorldStateとPrivateDataの利用方法をサンプルを含めて紹介したいと思います。また、WorldStateは、1つのテーブルしか持てないので、複数の情報をキーで分けて管理する際に便利な複合キー(CompositeKey)の使い方についても紹介します。

Hyperledger Fabricの仕様

データベースアクセス

Hyperledger Fabricにおけるデータベースのアクセスは下記の図のような経路になっています。また、それぞれのコンポーネントの役割は下記のようになっています。

  • ServerSide Application: ユーザーがChaincodeにアクセスするためのアプリケーション
  • Peer Node: ChaincodeやCouchDB/LevelDBへのプロキシ
  • Chaincode: WorldStateやPrivateDataを操作するためのプログラム
  • CouchDB/LevelDB: Peer Nodeが管理するブロックチェーン上のデータを全て保存するデータベース
    • WorldState: ブロックチェーンネットワーク上のチャンネル参加者全員が同じ状態で保持するテーブル
    • PrivateData: 各参加者、もしくは一部の参加者でのみ同じ状態で保持するテーブル

AccessToDatabase

APIインターフェイス

ChaincodeからCouchDB/LevelDBへアクセスするためのAPIインターフェイスは、下記が用意されています。なお、このインターフェイスについては、SDKが用意されている全ての言語で共通になっています。

  • WorldState
    • 登録/更新系
      • ChaincodeStub.putState()
    • 参照系
      • 標準
        • ChaincodeStub.getState()
        • ChaincodeStub.getStateByPartialCompositeKey()
        • ChaincodeStub.getStateByPartialCompositeKeyWithPagination()
        • ChaincodeStub.getStateByRange()
        • ChaincodeStub.getStateByRangeWithPagination()
      • Rich Query(DBがCouchDBの時だけ使用可能)
        • ChaincodeStub.getQueryResult()
        • ChaincodeStub.getQueryResultWithPagination()
      • 削除系
        • ChaincodeStub.deleteState()
  • PrivateData
    • 登録/更新系
      • ChaincodeStub.putPrivateData()
    • 参照系
      • 標準
        • ChaincodeStub.getPrivateData()
        • ChaincodeStub.getPrivateDataHash()
        • ChaincodeStub.getPrivateDataByPartialCompositeKey()
      • Rich Query(DBがCouchDBの時だけ使用可能)
        • ChaincodeStub.getPrivateDataByRange()
        • ChaincodeStub.getPrivateDataQueryResult()
    • 削除系
      • ChaincodeStub.deletePrivateData()

また、引数等の具体的なインターフェイス仕様については下記で参照できます。(バージョンによる違いがあるので、指定バージョンを確認したい場合は、この記事で紹介した環境を起動する必要があります。)

  • JavaScript: https://fabric-shim.github.io/release-1.4/fabric-shim.ChaincodeStub.html
  • Go: https://godoc.org/github.com/hyperledger/fabric/core/chaincode/shim

Private Dataの使用方法

Hyperledger FabricでPrivateDataを利用するには、Chaincodeをデプロイ時に下記の手順を追加で行う必要があります。

追加手順1: Channel作成時にPrivateDataの利用設定を実施

Hyperledger Fabricでは、デフォルトの設定ではPrivate Dataを利用することができないように設定されています。このため、configtxgenコマンドを用いてchannelを作成するトランザクションファイルを生成する際に、下記の設定を追加する必要があります。

  Capabilities:

      # Application capabilities apply only to the peer network, and may be safely
      # manipulated without concern for upgrading orderers.  Set the value of the
      # capability to true to require it.
      Application: &ApplicationCapabilities
+         # V1.2 for Application is a catchall flag for behavior which has been
+         # determined to be desired for all peers running v1.0.x, but the
+         # modification of which would cause incompatibilities.  Users should
+         # leave this flag set to true.
+         V1_2: true

追加手順2: Instantiate/Update時に、PrivateDataのアクセス設定を実施

Hyperledger Fabricでは、Chaincodeをデプロイする際に、PrivateDataのテーブル設定、およびアクセス権限を予め設定する必要があります。このため、下記のようなPrivateDataのアクセス権限の設定ファイルを用意し、InstantiateやUpdate時に追加選択して実行する必要があります。

[
    {
        "name": "privateCollectionOrg1MSP",
        "policy": "OR('Org1MSP.member')",
        "requiredPeerCount": 0,
        "maxPeerCount": 3,
        "blockToLive": 1000000
    }
]
$ peer chaincode instantiate -C myc -n mycc -v 0 -c '{"Args":["init"]}' --collections-config /usr/local/app/config/collections_config.json -P "OR('Org1MSP.member')"

サンプルコード

AdFraud情報共有プラットフォームでは、登録時点では公開しないデータジャケットを一時的に保存できるようにしています。このため、参加している各組織ごとにPrivateDataを用意しています。
今回は、3つの組織ごとにPrivateDataを用意し、共通のインターフェイスで、それぞれの公開しないデータジャケットを取得するようなChaincodeのサンプルコードを載せています。

const shim = require('fabric-shim');

class SampleCode {
    constructor() {
    }

    async Init(stub) {
        return shim.success();
    }

    async Invoke(stub) {
        const clientIdentity = new shim.ClientIdentity(stub);
        const mspId = clientIdentity.getMSPID();
        const privateCollectionName = `privateCollection${mspId}`;

        const args = stub.getFunctionAndParameters();
        switch(args.fcn) {
            case 'put':
                const privateData = {
                    data: `private data ${mspId}`,
                };
                const putPrivateDataResult = await stub.putPrivateData(privateCollectionName, mspId, Buffer.from(JSON.stringify(privateData)))
                    .catch(err => err);
                if (putPrivateDataResult instanceof Error) {
                    return shim.error();
                }
                return shim.success();
            case 'get':
                    const key = args.params[0];
                    const privateDataBuffer = await stub.getPrivateData(privateCollectionName, key)
                        .catch(err => err);
                    if (privateDataBuffer instanceof Error) {
                        return shim.error()
                    }
                    return shim.success(privateDataBuffer);
        }
    }
}

shim.start(new SampleCode);

複合キーの使用

Hyperledger Fabricでは、WorldStateが、デプロイされたChaincode1つに対して1つのWorldStateしか存在しないので、複数のデータを管理するために複合キーを利用することができます。ただ、この複合キーを利用した場合、データベースへの保存時は、複合キーに指定したそれぞれの値が連結して鍵になっている仕様のため、注意が必要です。

今回は、この複合キーの生成、および、データジャケットを一括で取得するサンプルコードを下記に載せておきます。

class DataJacketManager {
    constructor(stub) {
        this.stub;
        this.KeyName = 'DataJacketKey'
    }

    /*
     * id: データジャケットのID
     * value: データジャケットの情報
     */
    async _putDataJacket(id, value) {
        const stub = this.stub;
        // 複合キーの生成
        const compositeKey = stub.createCompositeKey(this.KeyName, [ id ]);
        const putStateResult = await stub.putState(compositeKey, Buffer.from(JSON.stringify(value)))
            .catch(err => err);
        if (putStateResult instanceof Error) {
            throw putStateResult;
        }
        return putStateResult;
    }

    /*
     * id: データジャケットのID
     */
    async _getDataJacket(id) {
        const stub = this.stub;
        // 取得するデータジャケットの鍵生成
        const compositeKey = stub.createCompositeKey(this.KeyName, [ id ]);
        const dataJacketBuffer = await stub.getState(compositeKey)
            .catch(err => err);
        if (dataJacketBuffer instanceof Error) {
            throw dataJacketBuffer;
        }
        if (dataJacketBuffer.length === 0) {
            return;
        }
        return JSON.parse(dataJacketBuffer.toString());
    }

    async _getDataJacketList() {
        const stub = this.stub;
        // 一覧を取得するため、キー名だけ指定
        const dataJacketIterator = await stub.getStateByPartialCompositeKey(this.KeyName, [])
            .catch(err => err);
        if (dataJacketIterator instanceof Error) {
            throw dataJacketIterator;
        }

        const dataJacketList = [];
        while (true) {
            const queryResult = await dataJacketIterator.next();
            if (queryResult.value) {
                const dataJacket = JSON.parse(queryResult.value.value.toString('utf8'));
                // 複合キーをパースし、データジャケットのIDを返却する配列に含める
                const parseCompositeKey = stub.splitCompositeKey(queryResult.value.key);
                dataJacket.JacketID = parseCompositeKey.attributes[0];
                dataJacketList.push(dataJacket);
            }
            if (queryResult.done) {
                await dataJacketIterator.close();
                break;
            }
        }
        return dataJacketList;
    }

    // 以下、省略
}

おわりに

今回は、Hyperledger Fabric上でPrivateDataやWorldStateを利用する方法、および複合キーを利用したデータベース操作について紹介しました。Hyperledger Fabricについては、少し離れたことをしようとすると、公式ドキュメントからサンプルコードが少なくなる傾向があるので、こういうTipsが増えて欲しいと思います。今後も、開発時にハマったような知見があればブログに公開する予定のため、今後もよろしくお願いします。

参考

Contact

Contact