Using Private Data in Fabric

このチュートリアルでは、プライベートデータコレクション(Private Data Collections, PDC)を使用して、ブロックチェーンネットワーク上のプライベートデータを認可された組織のピア上に格納および取得する方法について説明します。 コレクションに対するポリシーが含まれるコレクション定義ファイルによって、コレクションを指定します。

このチュートリアルの情報は、プライベートデータストアとその使用例に関する知識を前提としています。詳細については、 Private data を参照してください。

注釈

この手順では、Fabric v2.0リリースで導入された新しいFabricチェーンコードライフサイクルを使用します。以前のライフサイクルモデルを使用したチェーンコードでプライベートデータを使用する場合は、v1.4バージョンのチュートリアル Using Private Data in Fabric tutorial を参照してください。

このチュートリアルでは、次の手順に従って、Fabricでプライベートデータを定義、構成、使用する方法について学習します:

  1. Asset transfer private data sample use case
  2. Build a collection definition JSON file
  3. Read and Write private data using chaincode APIs
  4. Deploy the private data smart contract to the channel
  5. Register identities
  6. Create an asset in private data
  7. Query the private data as an authorized peer
  8. Query the private data as an unauthorized peer
  9. Transfer the Asset
  10. Purge Private Data
  11. Using indexes with private data
  12. Additional resources

このチュートリアルでは、 asset transfer private data sample をFabricテストネットワークに展開し、プライベートデータのコレクションを作成、展開、および使用する方法を示します。タスク Install Fabric and Fabric Samples が完了している必要があります。

Asset transfer private data sample use case

このサンプルでは、 assetCollection, Org1MSPPrivateCollection, Org2MSPPrivateCollection の3つのプライベートデータコレクションを用いて、Org1とOrg2の間の資産の譲渡をデモします。サンプルでは、次のようなユースケースを用います。

Org1のメンバーが新たに資産を作成し、そのため、その所有者となります。所有者のアイデンティティなどの、資産の公開している詳細情報は、 assetCollection という名前のプライベートコレクションに格納されます。 資産は作成時に、所有者によって評価額の情報も与えられます。 評価額は、資産の譲渡の合意のために各参加者によって使用され、所有者の組織のコレクションにのみ格納されます。 今回のケースでは、所有者が合意した最初の評価額は、 Org1MSPPrivateCollection に格納されます。

この資産を購入するために、購入者は資産の所有者と同じ評価額に合意しなければなりません。 このステップでは、購入者 (Org2のメンバー) は、 'AgreeToTransfer' というスマートコントラクトの関数を利用して、評価額に合意し取引をする合意書を作成します。 この額は、 Org2MSPPrivateCollection に格納されます。 これで、 'TransferAsset' というスマートコントラクトの関数を利用して、資産の所有者は、購入者に資産を譲渡することができるようになります。 この 'TransferAsset' という関数は、チャネルの台帳にあるハッシュ値を使って、資産の譲渡の前に所有者と購入者が同じ評価額に合意していることを確認します。

譲渡のシナリオを詳しく見る前に、Fabricにおいて組織がどのようにプライベートデータコレクションを使うことができるかを議論しましょう。

Build a collection definition JSON file

プライベートデータを用いて複数の組織がやりとりできるようになるには、各チェーンコードに関連付けられたプライベートデータコレクションを定義するコレクション定義ファイルを、チャネル上のすべての組織が構築する必要があります。 プライベートデータコレクションに格納されたデータは、チャネルのすべてのメンバではなく、特定の組織のピアにのみ分散されます。 コレクション定義ファイルは、組織がチェーンコードから読み書きすることができるプライベートデータコレクションの全てを記述します。

各コレクションは、次のプロパティによって定義されます。

  • name : コレクションの名前。
  • policy : コレクションデータの保持を許可する組織ピアを定義します。
  • requiredPeerCount : チェーンコードのエンドースメントの条件としてプライベートデータを広めるために必要なピア数
  • maxPeerCount : データの冗長性のために、現在のエンドースピアがデータの配布を試行する他のピアの数。エンドースピアが停止した場合、プライベートデータのプル要求があると、コミット時にこれらの他のピアが使用可能になります。
  • blockToLive : 価格設定や個人情報などの機密性の高い情報の場合、この値は、データがプライベートデータベースでブロック単位で存続する期間を表します。データは、プライベートデータベースで指定したブロック数の間存続し、その後パージされて、このデータはネットワーク上からなくなります。プライベートデータを無期限に保持する、つまりプライベートデータをパージしないようにするには、 blockToLive プロパティを 0 に設定します。
  • memberOnlyRead : 値が true の場合、ピアは、コレクションメンバー組織の1つに属するクライアントだけがプライベートデータへの読み取りアクセスを許可されるように自動的に強制します。
  • memberOnlyWrite : 値が true の場合、ピアは、コレクションメンバー組織の1つに属するクライアントだけがプライベートデータへの書き込みアクセスを許可されるように自動的に強制します。
  • endorsementPolicy : このプライベートデータコレクションへの書き込みのために、満たさなければいけないエンドースメントポリシーを定義します。コレクションレベルのエンドースメントポリシーは、チェーンコードレベルのポリシーを上書きします。ポリシー定義の作成の詳細については、 Endorsement policies トピックを参照してください。

チェーンコードを使用するすべての組織は、たとえその組織がどのコレクションに属していなくとも、同じコレクション定義ファイルをデプロイする必要があります。 コレクションファイルに明示的に定義されたコレクションに加えて、各組織は、自組織だけが読むことのできる、それぞれのピアにある暗黙的なコレクションにアクセスすることができます。 暗黙的なデータコレクションを用いた例については、 Secured asset transfer in Fabric を参照してください。

asset transfer private dataの例には、 assetCollectionOrg1MSPPrivateCollectionOrg2MSPPrivateCollection の3つのデータコレクションを定義する collections_config.json ファイルがあります。

// collections_config.json

[
   {
   "name": "assetCollection",
   "policy": "OR('Org1MSP.member', 'Org2MSP.member')",
   "requiredPeerCount": 1,
   "maxPeerCount": 1,
   "blockToLive":1000000,
   "memberOnlyRead": true,
   "memberOnlyWrite": true
   },
   {
   "name": "Org1MSPPrivateCollection",
   "policy": "OR('Org1MSP.member')",
   "requiredPeerCount": 0,
   "maxPeerCount": 1,
   "blockToLive":3,
   "memberOnlyRead": true,
   "memberOnlyWrite": false,
   "endorsementPolicy": {
       "signaturePolicy": "OR('Org1MSP.member')"
   }
   },
   {
   "name": "Org2MSPPrivateCollection",
   "policy": "OR('Org2MSP.member')",
   "requiredPeerCount": 0,
   "maxPeerCount": 1,
   "blockToLive":3,
   "memberOnlyRead": true,
   "memberOnlyWrite": false,
   "endorsementPolicy": {
       "signaturePolicy": "OR('Org2MSP.member')"
   }
   }
]

assetCollection の定義の policy プロパティは、Org1とOrg2の両組織が、そのピアにコレクションを保持できることを指定しています。 memberOnlyReadmemberOnlyWrite パラメータは、 Org1とOrg2の組織のみがこのコレクションに読み書きをできることを指定するために使われています。

Org1MSPPrivateCollection コレクションは、Org1のピアのみに、そのピアのプライベートデータベースにこのプライベートデータを持つことを許可し、 Org2MSPPrivateCollection コレクションはOrg2のピアだけが格納することがでます。 endorsementPolicy パラメータは、コレクションごとのエンドースメントポリシーを作成するのに使われます。 Org1MSPPrivateCollectionOrg2MSPPrivateCollection への更新は、ピアがそのコレクションを保有している組織によるエンドースが必要となります。 チュートリアルの中で、資産の譲渡にこれらのコレクションがどのように使われているかをみていきます。

このコレクション定義ファイルは、チェーンコード定義が peer lifecycle chaincode commit command を使用してチャネルにコミットされるときに展開されます。このプロセスの詳細については、以下のセクション3で説明します。

Read and Write private data using chaincode APIs

チャネル上のデータをプライベート化する方法を理解するための次のステップは、チェーンコード内にデータ定義を構築することです。asset transfer private dataサンプルは、データへのアクセス方法に従って、プライベートデータを3つの個別のデータ定義に分割します。

// Org1とOrg2のピアは、サイドデータベースにこのプライベートデータを保有します
type Asset struct {
       Type  string `json:"objectType"` // Typeは、ステートデータベース内の様々なオブジェクトの種類を識別するのに使われます
       ID    string `json:"assetID"`
       Color string `json:"color"`
       Size  int    `json:"size"`
       Owner string `json:"owner"`
}

// AssetPrivateDetailsは、所有者のみが見られる詳細を保持します

// Org1のピアだけが、サイドデータベースにこのプライベートデータを持ちます
type AssetPrivateDetails struct {
       ID             string `json:"assetID"`
       AppraisedValue int    `json:"appraisedValue"`
}

// Org2のピアだけが、サイドデータベースにこのプライベートデータを持ちます
type AssetPrivateDetails struct {
       ID             string `json:"assetID"`
       AppraisedValue int    `json:"appraisedValue"`
}

具体的には、プライベートデータへのアクセスは以下のように制限されます:

  • objectType, color, size, owner は、 assetCollection に格納され、コレクションのポリシーの定義に従い、チャネルのメンバー(Org1とOrg2)が見ることができます
  • 資産の AppraisedValue は、資産の所有者によって、 Org1MSPPrivateCollection もしくは Org2MSPPrivateCollection というコレクションに格納されます。この値には、コレクションを保持できる組織に属するユーザーのみがアクセス可能です。

asset transfer private data サンプルのスマートコントラクトで作られるすべてのデータは、PDC(プライベートデータコレクション)に格納されます。 スマートコントラクトは、Fabric chaincode APIの GetPrivateData()PutPrivateData() 関数を用いてプライベートデータコレクションにプライベートデータを読み書きします。 これらの関数についての詳細は、ここ にあります。 プライベートデータは、(パブリックなステートデータベースとは別の)ピアのプライベートステートデータベースに格納され、許可されたピアの間でゴシッププロトコルによって配布されます。

次の図はプライベートデータサンプルで用いられているプライベートデータモデルを図解しています。 この図においてOrg3は、もしチャネルに他の組織が存在するとしたら、それは設定で定義されたプライベートデータコレクションの どれにもアクセスできない ということを示すためのものです。

_images/SideDB-org1-org2.png

Reading collection data

スマートコントラクトは、データベース内のプライベートデータをクエリするために、chaincode API GetPrivateData() を使用します。 GetPrivateData() は、 コレクション名 とデータキーの2つの引数を取ります。コレクション assetCollection では、Org1とOrg2のメンバーがサイドデータベース内にプライベートデータを持つことができ、 Org1MSPPrivateCollection は、Org1のピアのみがサイドデータベースにそのプライベートデータを持つことができ、 Org2MSPPrivateCollection は、Org2のピアのみがサイドデータベースにそのプライベートデータを持つことができることを思い出してください。 実装の詳細は、次の2つの asset transfer private data functions を参照してください:

  • assetID, color, size, owner 属性の値を問い合わせる ReadAsset
  • appraisedValue 属性の値を問い合わせる ReadAssetPrivateDetails

このチュートリアルの後半でpeerコマンドを使用してデータベースクエリーを発行する際、これらの2つの関数を呼び出します。

Writing private data

スマートコントラクトは、プライベートデータをプライベートデータベースに格納するために、chaincode API PutPrivateData() を使用します。 APIにはコレクションの名前も必要です。 asset transfer private data sampleには、3つの異なるプライベートデータを含んでいますが、チェーンコードから(Org1として動く下記のシナリオでは)2回このAPIが呼ばれることに注意してください。

  1. assetCollection というコレクションを使って、プライベートデータの assetID, color, size, owner を書き込む。
  2. Org1MSPPrivateCollection というコレクションを使って、プライベートデータの appraisedValue を書き込む。

もしOrg2として動くときには、 Org1MSPPrivateCollectionOrg2PrivateCollection に置き換えます。

例えば、以下に示す CreateAsset 関数の抜粋では、 PutPrivateData() は、それぞれのプライベートデータに対して、合計2回呼ばれています。

// CreateAsset creates a new asset by placing the main asset details in the assetCollection
// that can be read by both organizations. The appraisal value is stored in the owners org specific collection.
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface) error {

    // Get new asset from transient map
    transientMap, err := ctx.GetStub().GetTransient()
    if err != nil {
        return fmt.Errorf("error getting transient: %v", err)
    }

    // Asset properties are private, therefore they get passed in transient field, instead of func args
    transientAssetJSON, ok := transientMap["asset_properties"]
    if !ok {
        //log error to stdout
        return fmt.Errorf("asset not found in the transient map input")
    }

    type assetTransientInput struct {
        Type           string `json:"objectType"` //Type is used to distinguish the various types of objects in state database
        ID             string `json:"assetID"`
        Color          string `json:"color"`
        Size           int    `json:"size"`
        AppraisedValue int    `json:"appraisedValue"`
    }

    var assetInput assetTransientInput
    err = json.Unmarshal(transientAssetJSON, &assetInput)
    if err != nil {
        return fmt.Errorf("failed to unmarshal JSON: %v", err)
    }

    if len(assetInput.Type) == 0 {
        return fmt.Errorf("objectType field must be a non-empty string")
    }
    if len(assetInput.ID) == 0 {
        return fmt.Errorf("assetID field must be a non-empty string")
    }
    if len(assetInput.Color) == 0 {
        return fmt.Errorf("color field must be a non-empty string")
    }
    if assetInput.Size <= 0 {
        return fmt.Errorf("size field must be a positive integer")
    }
    if assetInput.AppraisedValue <= 0 {
        return fmt.Errorf("appraisedValue field must be a positive integer")
    }

    // 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
    }

    // Verify that the client is submitting request to peer in their organization
    // This is to ensure that a client from another org doesn't attempt to read or
    // write private data from this peer.
    err = verifyClientOrgMatchesPeerOrg(ctx)
    if err != nil {
        return fmt.Errorf("CreateAsset cannot be performed: Error %v", err)
    }

    // Make submitting client the owner
    asset := Asset{
        Type:  assetInput.Type,
        ID:    assetInput.ID,
        Color: assetInput.Color,
        Size:  assetInput.Size,
        Owner: clientID,
    }
    assetJSONasBytes, err := json.Marshal(asset)
    if err != nil {
        return fmt.Errorf("failed to marshal asset into JSON: %v", err)
    }

    // Save asset to private data collection
    // Typical logger, logs to stdout/file in the fabric managed docker container, running this chaincode
    // Look for container name like dev-peer0.org1.example.com-{chaincodename_version}-xyz
    log.Printf("CreateAsset Put: collection %v, ID %v, owner %v", assetCollection, assetInput.ID, clientID)

    err = ctx.GetStub().PutPrivateData(assetCollection, assetInput.ID, assetJSONasBytes)
    if err != nil {
        return fmt.Errorf("failed to put asset into private data collecton: %v", err)
    }

    // Save asset details to collection visible to owning organization
    assetPrivateDetails := AssetPrivateDetails{
        ID:             assetInput.ID,
        AppraisedValue: assetInput.AppraisedValue,
    }

    assetPrivateDetailsAsBytes, err := json.Marshal(assetPrivateDetails) // marshal asset details to JSON
    if err != nil {
        return fmt.Errorf("failed to marshal into JSON: %v", err)
    }

    // Get collection name for this organization.
    orgCollection, err := getCollectionName(ctx)
    if err != nil {
        return fmt.Errorf("failed to infer private collection name for the org: %v", err)
    }

    // Put asset appraised value into owners org specific private data collection
    log.Printf("Put: collection %v, ID %v", orgCollection, assetInput.ID)
    err = ctx.GetStub().PutPrivateData(orgCollection, assetInput.ID, assetPrivateDetailsAsBytes)
    if err != nil {
        return fmt.Errorf("failed to put asset private details: %v", err)
    }
    return nil
}

以上をまとめると、 collection_config.json のポリシー定義では、Org1とOrg2のすべてのピアが、それぞれのプライベートデータベースにasset transferプライベートデータの assetID, color, size, owner を格納してやりとりができます。しかし、Org1のピアのみがOrg1のコレクションである Org1MSPPrivateCollectionappraisedValue のキーのデータを格納してやりとりでき、Org2のピアのみがOrg2のコレクションである Org2MSPPrivateCollectionappraisedValue のキーのデータを格納してやりとりすることができます。

追加のデータプライバシーの利点として、コレクションが使用されるので、プライベートデータ ハッシュ のみがordererを通過し、プライベートデータ自体は通過せず、ordererからプライベートデータを秘密に保てます。

Start the network

これで、プライベートデータの使用方法を示すいくつかのコマンドについて説明する準備が整いました。

Try it yourself

プライベートデータチェーンコードをインストール、定義、使用する前に、Fabricテストネットワークを起動する必要があります。このチュートリアルでは、既知の初期状態から操作します。次のコマンドは、アクティブまたは古いDockerコンテナを削除し、以前に生成されたアーティファクトを削除します。したがって、次のコマンドを実行して、以前の環境をクリーンアップします:

cd fabric-samples/test-network
./network.sh down

test-network ディレクトリから、次のコマンドを使用して認証局(CA)とCouchDBを使用するFabricテストネットワークを起動できます:

./network.sh up createChannel -ca -s couchdb

このコマンドは、CouchDBをステートデータベースとして使用しながら、 mychannel という名前の単一チャネルと2つの組織(それぞれが1つのピアノードを持つ)と認証局と1つのorderingサービスで構成されるFabricネットワークをデプロイします。コレクションではLevelDBまたはCouchDBを使用できます。CouchDBは、プライベートデータでインデックスを使用する方法を示すために選択しています。

注釈

コレクションを機能させるためには、組織間のゴシップを正しく設定することが重要です。 Gossip data dissemination protocol に関するドキュメントを参照し、特に「アンカーピア」のセクションに注意してください。このチュートリアルではゴシップについては説明せず、ゴシップはテストネットワークですでに設定されているものとしますが、チャネルを設定する場合、ゴシップアンカーピアはコレクションが正しく機能するように設定するために重要です。

Deploy the private data smart contract to the channel

これで、テストネットワークのスクリプトを用いて、チャネルにスマートコントラクトをデプロイすることができます。 テストネットワークのディレクトリで、次のコマンドを実行してください。

./network.sh deployCC -ccn private -ccp ../asset-transfer-private-data/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-go/collections_config.json

コマンドには、プライベートデータコレクション定義ファイルへのパスを渡す必要があることに注意してください。 チャネルにチェーンコードをデプロイするときに、チャネルの両方の組織は、同一のプライベートデータコレクション定義を Fabric chaincode lifecycle の一部として渡さなくてなりません。 ここではスマートコントラクトのチェーンコードレベルのエンドースメントポリシーを "OR('Org1MSP.peer','Org2MSP.peer')" としてデプロイしています。 これにより、Org1とOrg2は、他の組織からエンドースメントを得ることなしに、資産を作成することができます。 チェーンコードをデプロイするのに必要な手順は、上記のコマンドを実行した後にログに表示されるのを確認することができます。

両方の組織が peer lifecycle chaincode approveformyorg コマンドによってチェーンコード定義を承認するときには、 --collections-config フラグによってプライベートデータコレクション定義へのパスがチェーンコード定義に含まれます。 次の approveformyorg コマンドがターミナルに表示されるのがわかるでしょう。

peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name private --version 1.0 --collections-config ../asset-transfer-private-data/chaincode-go/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')" --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA

チャネルメンバーがチェーンコード定義の一部としてプライベートデータコレクションに合意すると、データコレクションは、 peer lifecycle chaincode commit コマンドでチャネルへコミットされます。 ログからコミットコマンドを探すと、 --collections-config フラグによって、コレクション定義へのパスが与えられていることがわかるでしょう。

peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name private --version 1.0 --sequence 1 --collections-config ../asset-transfer-private-data/chaincode-go/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')" --tls --cafile $ORDERER_CA --peerAddresses localhost:7051 --tlsRootCertFiles $ORG1_CA --peerAddresses localhost:9051 --tlsRootCertFiles $ORG2_CA

Register identities

Private data transferスマートコントラクトは、ネットワークに属する個々のアイデンティによる所有関係をサポートしています。 ここでのシナリオでは、資産の所有者はOrg1のメンバーであり、購入者はOrg2に属すことになるでしょう。 GetClientIdentity().GetID() APIとユーザーの証明書内の情報の関連を強調するために、Org1とOrg2の証明局(CA)を用いて、新たに2つアイデンティティを登録し、それぞれのアイデンティティの証明書と秘密鍵を生成していきましょう。

まず、Fabric CAクライアントを使用するために、下記の環境変数を設定する必要があります。

export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/

資産の所有者のアイデンティティを作成するために、Org1のCAを使います。Fabric CAクライアントのホームをOrg1のCAのadmin(このアイデンティティはテストネットワークのスクリプトによって生成されています)のMSPに設定します。

export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/

fabric-ca-client ツールを使って新しい所有者のクライアントアイデンティティを登録できます。

fabric-ca-client register --caname ca-org1 --id.name owner --id.secret ownerpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem

これで、エンロール名とシークレットをenrollコマンドに指定することで、アイデンティティの証明書とMSPフォルダの生成することができます。

fabric-ca-client enroll -u https://owner:ownerpw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem

次のコマンドを実行して、Node OUの設定ファイルを所有者のアイデンティティMSPフォルダにコピーします。

cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp/config.yaml

次にOrg2 CAを使って、購入者のアイデンティティを作成します。Fabric CAクライアントのホームをOrg2 CAのadminに設定します。

export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/

fabric-ca-client ツールを使って新しい所有者のクライアントアイデンティティを登録できます。

fabric-ca-client register --caname ca-org2 --id.name buyer --id.secret buyerpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem

エンロールして、アイデンティティMSPフォルダを生成できます。

fabric-ca-client enroll -u https://buyer:buyerpw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem

次のコマンドを実行して、Node OUの設定ファイルを購入者のアイデンティティMSPフォルダにコピーします。

cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp/config.yaml

Create an asset in private data

資産の所有者のアイデンティティを作成したので、private dataスマートコントクラトを実行して、新しい資産を作成することができます。 test-network ディレクトリのターミナルに、次の複数のコマンドを貼り付けます。

Try it yourself

export PATH=${PWD}/../bin:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

CreateFunction 関数を使って、assetID(資産ID)が asset1 で、color(色)が green で、size(サイズ)が 20 でappraisedValue(評価額)が 100 となるプライベートデータに格納される資産を作成します。 プライベートデータの appraisedValue は、 assetID, color, size とは別に格納されることに注意してください。 このため、 CreateAsset 関数は PutPrivateData() APIをコレクションごとに1回ずつ2回呼び出してプライベートデータを保持します。また、プライベートデータは --transient フラグを使用して渡されます。一時データとして渡された入力は、データをプライベートに保つためにトランザクションで永続化されません。一時データはバイナリデータとして渡されるため、ターミナルを使用する場合はbase64でエンコードする必要があります。環境変数を使用してbase64でエンコードされた値をキャプチャし、 tr コマンドを使用して、linux base64コマンドで追加される問題のある改行文字を取り除きます。

次のコマンドを実行して資産を作成します。

export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset1\",\"color\":\"green\",\"size\":20,\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"

次のような結果が表示されます:

[chaincodeCmd] chaincodeInvokeOrQuery->INFO 001 Chaincode invoke successful. result: status:200

上記のコマンドはOrg1のピアのみを対象にしていることに注意してください。 CreateAsset トランザクションは、 assetCollectionOrg1MSPPrivateCollection という2つのコレクションに書き込みます。 Org1MSPPrivateCollection は、コレクションへの書き込みにOrg1のピアからのエンドースメントを必要とするいぽ欧で、 assetCollection はチェーンコードのエンドースメントポリシーである "OR('Org1MSP.peer','Org2MSP.peer')" を引き継ぎます。 Org1のピアからのエンドースメントは両方のエンドースメントポリシーを満たし、Org2からのエンドースメントなしに資産を作成することができます。

Query the private data as an authorized peer

このコレクション定義では、Org1とOrg2のすべてのピアがサイドデータベースに assetID, color, size, owner のプライベートデータを持つことができますが、Org1の考えである appraisedValue プライベートデータをサイドデータベースに持つことができるのはOrg1のピアのみです。Org1に認められたピアとして、両方のプライベートデータセットを問い合せます。

最初の query コマンドは、引数として assetCollection を渡す ReadAsset 関数を呼び出します。

// ReadAsset reads the information from collection
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) {

     log.Printf("ReadAsset: collection %v, ID %v", assetCollection, assetID)
     assetJSON, err := ctx.GetStub().GetPrivateData(assetCollection, assetID) //get the asset from chaincode state
     if err != nil {
         return nil, fmt.Errorf("failed to read asset: %v", err)
     }

     //No Asset found, return empty response
     if assetJSON == nil {
         log.Printf("%v does not exist in collection %v", assetID, assetCollection)
         return nil, nil
     }

     var asset *Asset
     err = json.Unmarshal(assetJSON, &asset)
     if err != nil {
         return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
     }

     return asset, nil

 }

2番目の query コマンドは、引数として Org1MSPPrivateDetails を渡す ReadAssetPrivateDetails 関数を呼び出します。

// ReadAssetPrivateDetails reads the asset private details in organization specific collection
func (s *SmartContract) ReadAssetPrivateDetails(ctx contractapi.TransactionContextInterface, collection string, assetID string) (*AssetPrivateDetails, error) {
     log.Printf("ReadAssetPrivateDetails: collection %v, ID %v", collection, assetID)
     assetDetailsJSON, err := ctx.GetStub().GetPrivateData(collection, assetID) // Get the asset from chaincode state
     if err != nil {
         return nil, fmt.Errorf("failed to read asset details: %v", err)
     }
     if assetDetailsJSON == nil {
         log.Printf("AssetPrivateDetails for %v does not exist in collection %v", assetID, collection)
         return nil, nil
     }

     var assetDetails *AssetPrivateDetails
     err = json.Unmarshal(assetDetailsJSON, &assetDetails)
     if err != nil {
         return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
     }

     return assetDetails, nil
 }

では Try it yourself

ReadAsset 関数を使って、作成された資産の主な詳細を読み取り、Org1として assetCollection コレクションにクエリを行います。

peer chaincode query -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'

成功すると、コマンドは次のような結果を返します。

{"objectType":"asset","assetID":"asset1","color":"green","size":20,"owner":"x509::CN=appUser1,OU=admin,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"}

資産の "owner" は、スマートコントラクトを実行して資産を作成したアイデンティティです。 private dataスマートコントラクトは、 GetClientIdentity().GetID() APIを使用して、アイデンティティの証明書の名前と発行者を読み取ります。 アイデンティティの証明書の名前と発行者がowner属性にあることを確認できます。

Org1のメンバーとして asset1appraisedValue プライベートデータを問い合せます。

peer chaincode query -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'

次のような結果が表示されます:

{"assetID":"asset1","appraisedValue":100}

Query the private data as an unauthorized peer

次に、Org2のユーザーを操作します。 Org2は、 assetCollection ポリシーで定義されるように、asset transferのプライベートデータの assetID, color, size, owner をOrg2のサイドデータベースに持ちますが、Org1の資産の appraisedValue データは持っていません。 両方のプライベートデータセットを問い合せます。

Switch to a peer in Org2

次のコマンドを実行して、Org2メンバーとして操作し、Org2ピアにクエリします。

Try it yourself

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

Query private data Org2 is authorized to

Org2のピアは、自分のサイドデータベースにasset transferのプライベートデータ( assetID, color, size, owner )の最初のセットを持っていて、それに assetCollection 引数で呼ばれる ReadAsset() 関数を使ってアクセスできるはずです。

Try it yourself

peer chaincode query -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'

次のような結果が表示されます:

{"objectType":"asset","assetID":"asset1","color":"green","size":20,
"owner":"x509::CN=appUser1,OU=admin,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US" }

Query private data Org2 is not authorized to

この資産はOrg1によって作られたので、 asset1 に関連付けられた appraisedValue は、 Org1MSPPrivateCollection コレクションに格納されています。 この値は、Org2のピアには格納されていません。 次のコマンドを実行して、この資産の appraisedValue がOrg2ピアの Org2MSPPrivateCollection に格納されていないことを実際に見てみます。

Try it yourself

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'

空の応答は、asset1のプライベートな詳細は、購入者(Org2)のプライベートコレクションに存在しないことを示しています。

また、Org2のユーザーは、Org1のプライベートデータコレクションから読むこともできません。

peer chaincode query -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'

"memberOnlyRead": true をコレクション設定ファイルで設定したことで、Org1のクライアントのみがコレクションからデータを読むことができると指定しています。 このコレクションから読み込みをしようとしたOrg2のクライアントは、次のような応答を得るだけでしょう。

Error: endorsement failure during query. response: status:500 message:"failed to
read asset details: GET_STATE failed: transaction ID: d23e4bc0538c3abfb7a6bd4323fd5f52306e2723be56460fc6da0e5acaee6b23: tx
creator does not have read access permission on privatedata in chaincodeName:private collectionName: Org1MSPPrivateCollection"

Org2のユーザーは、プライベートデータの公開ハッシュのみを見ることができます。

Transfer the Asset

asset1``をOrg2に譲渡するのに何が必要か見てみましょう。 この場合、Org2はOrg1から資産を購入することに同意し、ともに ``appraisedValue に合意しなければなりません。 Org1がOrg1の考える appraisedValue をプライベートデータベースにとどめているのに、どうやったら合意できるのかと思っているかもしれません。 この疑問に答えるために、続けましょう。

Try it yourself

ピアのCLIがある端末に戻りましょう。

資産を譲渡するには、購入者(受取人)が、 AgreeToTransfer チェーンコード関数を呼ぶことで、資産の所有者と同じ appraisedValue に同意する必要があります。 同意した値は、Org2のピアの Org2MSPDetailsCollection コレクションに格納されます。 Org2として次のコマンドを実行して、100の評価額に同意します。

export ASSET_VALUE=$(echo -n "{\"assetID\":\"asset1\",\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"AgreeToTransfer","Args":[]}' --transient "{\"asset_value\":\"$ASSET_VALUE\"}"

購入者は、Org2プライベートデータコレクションにある同意した値をクエリすることができます。

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'

この呼び出しで次の値が返ります。

{"assetID":"asset1","appraisedValue":100}

購入者がこの評価額で資産を購入することに合意したので、所有者は資産をOrg2に譲渡することができます。 資産は、資産を所有するアイデンティティによって譲渡する必要があるので、Org1として操作しましょう。

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051

Org1の所有者は AgreeToTransfer トランザクションで追加されたデータを読んで、購入者のアイデンティティを見ることができます。

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadTransferAgreement","Args":["asset1"]}'
{"assetID":"asset1","buyerID":"eDUwOTo6Q049YnV5ZXIsT1U9Y2xpZW50LE89SHlwZXJsZWRnZXIsU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUzo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"}

これで資産を譲渡するのに必要なものがすべてそろいました。 スマートコントラクトは、 GetPrivateDataHash() 関数を使って Org1MSPPrivateCollection の資産の評価額のハッシュが、 Org2MSPPrivateCollection の評価額のハッシュと一致することを確かめます。 もし、ハッシュが一致したら、所有者とこの購入者が同じ資産価値で合意したことが確認されます。 条件が満たされた場合、譲渡関数は、購入者のクライアントIDを譲渡の合意から読み取り、購入者を資産の新しい所有者にします。 譲渡の関数は、もとの所有者のコレクションから資産の評価額を削除も行い、さらに assetCollection から譲渡の合意を削除します。

次のコマンドを実行して、資産を譲渡します。 所有者は資産のIDと購入者の組織のMSP IDを譲渡のトランザクションに指定する必要があります。

export ASSET_OWNER=$(echo -n "{\"assetID\":\"asset1\",\"buyerMSP\":\"Org2MSP\"}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"TransferAsset","Args":[]}' --transient "{\"asset_owner\":\"$ASSET_OWNER\"}" --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt

譲渡の結果を見るために、 asset1 をクエリすることができます。

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'

結果は、購入者のアイデンティティが資産を所有していることを表すでしょう。

{"objectType":"asset","assetID":"asset1","color":"green","size":20,"owner":"x509::CN=appUser2, OU=client + OU=org2 + OU=department1::CN=ca.org2.example.com, O=org2.example.com, L=Hursley, ST=Hampshire, C=UK"}

資産の "owner" が購入者のアイデンティティになっています。

またプライベートな詳細情報がOrg1のコレクションから削除されていることも確かめることができます。

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'

資産のプライベートデータがOrg1のプライベートデータコレクションから削除されているため、このクエリは空の結果を返すでしょう。

Purge Private Data

プライベートデータが短い期間の間しか保持しなくてもよいようなユースケースのために、一定のブロック数の後にデータを"削除"することができます。このとき、トランザクションのイミュータブルな証拠として、データのハッシュ値のみが残されます。 もしデータが機密情報を含んでいて、あるトランザクションで過去に使われたが、今は必要がなくなったとき、あるいはデータがオフチェーンのデータベースに複製されとき、組織はプライベートデータを削除しようとすることがあるでしょう。

ここでの例における appraisedValue のデータはプライベートな合意を含んでおり、組織は一定時間後に消滅してほしいと思うかもしれません。 したがって、これは限られた生存期間を持つということであり、コレクション定義の blockToLive プロパティを用いて、ブロックチェーンに一定のブロック数存在したのちに削除することができます。

Org2MSPPrivateCollection の定義では、 blockToLive プロパティの値は 3 となっています。 これはこのデータがサイドデータベースで3ブロックの間だけ存在し、その後削除されるということを意味しています。 もし、チャネルでさらなるブロックを作成した場合、 Org2が同意した appraisedValue は最終的に削除されます。 3ブロックを新たに作成して、次のようにして実際に確かめることができます。

Try it yourself

ターミナルで次のコマンドを実行して、Org2のメンバーを操作しOrg2のピアを対象にします。

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

Org2MSPPrivateCollectionappraisedValue はまだクエリすることができます。

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'

ログに値が表示されるのがわかるはずです。

{"assetID":"asset1","appraisedValue":100}

プライベートデータが削除されるまでにどれだけのブロックを追加したのかを把握する必要があるので、新しいターミナルウィンドウを開き、次のコマンドを実行してOrg2ピアのプライベートデータのログを表示してください。 一番高いブロック番号に注目してください。

docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'

では、Org2のメンバーとして操作していたターミナルに戻り、3つ新しいアセットを作るために次のコマンドを実行してください。 各コマンドが1つずつブロックを作成します。

export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset2\",\"color\":\"blue\",\"size\":30,\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"
export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset3\",\"color\":\"red\",\"size\":25,\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"
export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset4\",\"color\":\"orange\",\"size\":15,\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"

もう一つのターミナルに戻り、次のコマンドを実行して、新しい資産によって3つのブロックが作成されたことを確認しましょう。

docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'

これで、 appriasedValueOrg2MSPDetailsCollection から削除されてしまいました。 クエリをもう一回発行して、応答が空であることを確認しましょう。

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'

Using indexes with private data

インデックスは、チェーンコードとともに META-INF/statedb/couchdb/collections/<collection_name>/indexes ディレクトリにインデックスをパッケージ化することで、プライベートデータコレクションにも適用できます。インデックスの例は ここ にあります。

実稼働環境へのチェーンコードの配置では、チェーンコードがピアにインストールされ、チャネル上でインスタンス化された後に、チェーンコードとサポートするインデックスが自動的に1つの単位として配置されるように、チェーンコードとともにインデックスを定義することをお勧めします。関連付けられたインデックスは、コレクションJSONファイルの場所を示す --collections-config フラグが指定されることによって、チャネル上でのチェーンコードのインスタンス化タイミングで自動的に配置されます。

注釈

暗黙的なプライベートデータコレクションに対してインデックスを作成することはできません。 暗黙的なコレクションは組織名に基づき自動的に作られます。 名前のフォーマットは、 _implicit_org_<OrgsMSPid> です。 詳細については、 FAB-17916 を参照してください。

Clean up

プライベートデータスマートコントラクトを使い終わったら、 network.sh スクリプトを用いてテストネットワークを削除することができます。

./network.sh down

このコマンドは、作成したCAやピアやオーダリングノードを削除します。 台帳のすべてのデータが失われることに注意しましょう。 もし、またチュートリアルをやり直したい場合には、きれいな初期状態から始めることになります。

Additional resources

その他のプライベートデータに関する教育については、ビデオチュートリアルが作成されています。

注釈

このビデオでは、以前のライフサイクルモデルを使用して、チェーンコードを含むプライベートデータコレクションをインストールしています。