go_study/fabric-main/docs/source/private_data_tutorial.rst

1019 lines
49 KiB
ReStructuredText

Using Private Data in Fabric
============================
This tutorial will demonstrate the use of Private Data Collections (PDC) to provide storage
and retrieval of private data on the blockchain network for authorized peers
of organizations. The collection is specified using a collection definition file containing the policies governing that collection.
The information in this tutorial assumes knowledge of private data
stores and their use cases. For more information, check out :doc:`private-data/private-data`.
.. note:: These instructions use the new Fabric chaincode lifecycle introduced
in the Fabric v2.0 release. If you would like to use the previous
lifecycle model to use private data with chaincode, visit the v1.4
version of the `Using Private Data in Fabric tutorial <https://hyperledger-fabric.readthedocs.io/en/release-1.4/private_data_tutorial.html>`__.
The tutorial will take you through the following steps to practice defining,
configuring and using private data with Fabric:
#. :ref:`pd-use-case`
#. :ref:`pd-build-json`
#. :ref:`pd-read-write-private-data`
#. :ref:`pd-install-define_cc`
#. :ref:`pd-register-identities`
#. :ref:`pd-store-private-data`
#. :ref:`pd-query-authorized`
#. :ref:`pd-query-unauthorized`
#. :ref:`pd-transfer-asset`
#. :ref:`pd-purge`
#. :ref:`pd-indexes`
#. :ref:`pd-ref-material`
This tutorial will deploy the `asset transfer private data sample <https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-private-data/chaincode-go>`__
to the Fabric test network to demonstrate how to create, deploy, and use a collection of
private data.
You should have completed the task :doc:`install`.
.. _pd-use-case:
Asset transfer private data sample use case
-------------------------------------------
This sample demonstrates the use of three private data collections, ``assetCollection``, ``Org1MSPPrivateCollection`` & ``Org2MSPPrivateCollection`` to transfer an asset between Org1 and Org2, using following use case:
A member of Org1 creates a new asset, henceforth referred as owner. The public details of the asset,
including the identity of the owner, are stored in the private data collection named ``assetCollection``. The asset is also created with an appraised
value supplied by the owner. The appraised value is used by each participant to agree to the transfer of the asset, and is only stored in owner organization's collection. In our case, the initial appraisal value agreed by the owner is stored in the ``Org1MSPPrivateCollection``.
To purchase the asset, the buyer needs to agree to the same appraised value as
the asset owner. In this step, the buyer (a member of Org2) creates an agreement
to trade and agree to an appraisal value using smart contract function ``'AgreeToTransfer'``.
This value is stored in ``Org2MSPPrivateCollection`` collection. Now, the asset
owner can transfer the asset to the buyer using smart contract function ``'TransferAsset'``.
The ``'TransferAsset'`` function uses the hash on the channel ledger to
confirm that the owner and the buyer have agreed to the same appraised value
before transferring the asset.
Before we go through the transfer scenario, we will discuss how organizations can use private data collections in Fabric.
.. _pd-build-json:
Build a collection definition JSON file
---------------------------------------
Before a set of organizations can transact using private data, all organizations
on channel need to build a collection definition file that defines the private
data collections associated with each chaincode. Data that is stored in a private
data collection is only distributed to the peers of certain organizations instead
of all members of the channel. The collection definition file describes all of the
private data collections that organizations can read and write to from a chaincode.
Each collection is defined by the following properties:
.. _blockToLive:
- ``name``: Name of the collection.
- ``policy``: Defines the organization peers allowed to persist the collection data.
- ``requiredPeerCount``: Number of peers required to disseminate the private data as
a condition of the endorsement of the chaincode
- ``maxPeerCount``: For data redundancy purposes, the number of other peers
that the current endorsing peer will attempt to distribute the data to.
If an endorsing peer goes down, these other peers are available at commit time
if there are requests to pull the private data.
- ``blockToLive``: For very sensitive information such as pricing or personal information,
this value represents how long the data should live on the private database in terms
of blocks. The data will live for this specified number of blocks on the private database
and after that it will get purged, making this data obsolete from the network.
To keep private data indefinitely, that is, to never purge private data, set
the ``blockToLive`` property to ``0``.
- ``memberOnlyRead``: a value of ``true`` indicates that peers automatically
enforce that only clients belonging to one of the collection member organizations
are allowed read access to private data.
- ``memberOnlyWrite``: a value of ``true`` indicates that peers automatically
enforce that only clients belonging to one of the collection member organizations
are allowed write access to private data.
- ``endorsementPolicy``: defines the endorsement policy that needs to be met in
order to write to the private data collection. The collection level endorsement policy
overrides to chaincode level policy. For more information on building a policy
definition refer to the :doc:`endorsement-policies` topic.
The same collection definition file needs to be deployed by all organizations that
use the chaincode, even if the organization does not belong to any collections. In
addition to the collections that are explicitly defined in a collection file,
each organization has access to an implicit collection on their peers that can only
be read by their organization. For an example that uses implicit data collections,
see the :doc:`secured_asset_transfer/secured_private_asset_transfer_tutorial`.
The asset transfer private data example contains a `collections_config.json` file
that defines three private data collection definitions: ``assetCollection``, ``Org1MSPPrivateCollection``,
and ``Org2MSPPrivateCollection``.
.. code:: 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')"
}
}
]
The ``policy`` property in the ``assetCollection`` definition specifies that both
Org1 and Org2 can store the collection on their peers. The ``memberOnlyRead``
and ``memberOnlyWrite`` parameters are used to specify that only Org1 and
Org2 clients can read and write to this collection.
The ``Org1MSPPrivateCollection`` collection allows only peers of Org1 to have
the private data in their private database, while the ``Org2MSPPrivateCollection``
collection can only be stored by the peers of Org2. The ``endorsementPolicy`` parameter
is used to create a collection specific endorsement policy. Each update to
``Org1MSPPrivateCollection`` or ``Org2MSPPrivateCollection`` needs to be endorsed
by the organization that stores the collection on their peers. We will see how
these collections are used to transfer the asset in the course of the tutorial.
This collection definition file is deployed when the chaincode definition is
committed to the channel using the `peer lifecycle chaincode commit command <commands/peerlifecycle.html#peer-lifecycle-chaincode-commit>`__.
More details on this process are provided in Section 3 below.
.. _pd-read-write-private-data:
Read and Write private data using chaincode APIs
------------------------------------------------
The next step in understanding how to privatize data on a channel is to build
the data definition in the chaincode. The asset transfer private data sample divides
the private data into three separate data definitions according to how the data will
be accessed.
.. code-block:: GO
// Peers in Org1 and Org2 will have this private data in a side database
type Asset 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"`
Owner string `json:"owner"`
}
// AssetPrivateDetails describes details that are private to owners
// Only peers in Org1 will have this private data in a side database
type AssetPrivateDetails struct {
ID string `json:"assetID"`
AppraisedValue int `json:"appraisedValue"`
}
// Only peers in Org2 will have this private data in a side database
type AssetPrivateDetails struct {
ID string `json:"assetID"`
AppraisedValue int `json:"appraisedValue"`
}
Specifically, access to the private data will be restricted as follows:
- ``objectType, color, size, and owner`` are stored in ``assetCollection`` and hence will be visible to members of the channel per the definition in the collection policy (Org1 and Org2).
- ``AppraisedValue`` of an asset is stored in collection ``Org1MSPPrivateCollection`` or ``Org2MSPPrivateCollection`` , depending on the owner of the asset. The value is only accessible to the users who belong to the organization that can store the collection.
All of the data that is created by the asset transfer private data sample smart
contract is stored in PDC. The smart contract uses the Fabric chaincode API
to read and write private data to private data collections using the ``GetPrivateData()``
and ``PutPrivateData()`` functions. You can find more information about those functions `here <https://godoc.org/github.com/hyperledger/fabric-chaincode-go/shim#ChaincodeStub>`_.
This private data is stored in private state db on the peer (separate from public state db), and
is disseminated between authorized peers via gossip protocol.
The following diagram illustrates the private data model used by the private data
sample. Note that Org3 is only shown in the diagram to illustrate that if
there were any other organizations on the channel, they would not have access to *any*
of the private data collections that were defined in the configuration.
.. image:: images/SideDB-org1-org2.png
Reading collection data
~~~~~~~~~~~~~~~~~~~~~~~~
The smart contract uses the chaincode API ``GetPrivateData()`` to query private data in the
database. ``GetPrivateData()`` takes two arguments, the **collection name**
and the data key. Recall the collection ``assetCollection`` allows peers of
Org1 and Org2 to have the private data in a side database, and the collection
``Org1MSPPrivateCollection`` allows only peers of Org1 to have their
private data in a side database and ``Org2MSPPrivateCollection`` allows peers
of Org2 to have their private data in a side database.
For implementation details refer to the following two `asset transfer private data functions <https://github.com/hyperledger/fabric-samples/blob/{BRANCH}/asset-transfer-private-data/chaincode-go/chaincode/asset_queries.go>`__:
* **ReadAsset** for querying the values of the ``assetID, color, size and owner`` attributes.
* **ReadAssetPrivateDetails** for querying the values of the ``appraisedValue`` attribute.
When we issue the database queries using the peer commands later in this tutorial,
we will call these two functions.
Writing private data
~~~~~~~~~~~~~~~~~~~~
The smart contract uses the chaincode API ``PutPrivateData()`` to store the private data
into the private database. The API also requires the name of the collection.
Note that the asset transfer private data sample includes three different private data collections, but it is called
twice in the chaincode (in this scenario acting as Org1).
1. Write the private data ``assetID, color, size and owner`` using the
collection named ``assetCollection``.
2. Write the private data ``appraisedValue`` using the collection named
``Org1MSPPrivateCollection``.
If we were acting as Org2, we would replace ``Org1MSPPrivateCollection`` with
``Org2MSPPrivateCollection``.
For example, in the following snippet of the ``CreateAsset`` function,
``PutPrivateData()`` is called twice, once for each set of private data.
.. code-block:: GO
// 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 collection: %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
}
To summarize, the policy definition above for our ``collections_config.json``
allows all peers in Org1 and Org2 to store and transact
with the asset transfer private data ``assetID, color, size, owner`` in their
private database. But only peers in Org1 can store and transact with
the ``appraisedValue`` key data in the Org1 collection ``Org1MSPPrivateCollection`` and only peers
in Org2 can store and transact with the ``appraisedValue`` key data in the Org2 collection ``Org2MSPPrivateCollection``.
As an additional data privacy benefit, since a collection is being used,
only the private data *hashes* go through orderer, not the private data itself,
keeping private data confidential from orderer.
Start the network
-----------------
Now we are ready to step through some commands which demonstrate how to use
private data.
:guilabel:`Try it yourself`
Before installing, defining, and using the private data smart contract,
we need to start the Fabric test network. For the sake of this tutorial, we want
to operate from a known initial state. The following command will kill any active
or stale Docker containers and remove previously generated artifacts.
Therefore let's run the following command to clean up any previous
environments:
.. code:: bash
cd fabric-samples/test-network
./network.sh down
From the ``test-network`` directory, you can use the following command to start
up the Fabric test network with Certificate Authorities and CouchDB:
.. code:: bash
./network.sh up createChannel -ca -s couchdb
This command will deploy a Fabric network consisting of a single channel named
``mychannel`` with two organizations (each maintaining one peer node), certificate authorities, and an
ordering service while using CouchDB as the state database. Either LevelDB or
CouchDB may be used with collections. CouchDB was chosen to demonstrate how to
use indexes with private data.
.. note:: For collections to work, it is important to have cross organizational
gossip configured correctly. Refer to our documentation on :doc:`gossip`,
paying particular attention to the section on "anchor peers". Our tutorial
does not focus on gossip given it is already configured in the test network,
but when configuring a channel, the gossip anchors peers are critical to
configure for collections to work properly.
.. _pd-install-define_cc:
Deploy the private data smart contract to the channel
-----------------------------------------------------
We can now use the test network script to deploy the smart contract to the channel.
Run the following command from the test network directory.
.. code:: bash
./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
Note that we need to pass the path to the private data collection definition file
to the command. As part of deploying the chaincode to the channel, both organizations
on the channel must pass identical private data collection definitions as part
of the :doc:`chaincode_lifecycle`. We are also deploying the smart contract
with a chaincode level endorsement policy of ``"OR('Org1MSP.peer','Org2MSP.peer')"``.
This allows Org1 and Org2 to create an asset without receiving an endorsement from
the other organization. You can see the steps required to deploy the chaincode
printed in your logs after you issue the command above.
When both organizations approve the chaincode definition using the
`peer lifecycle chaincode approveformyorg <commands/peerlifecycle.html#peer-lifecycle-chaincode-approveformyorg>`__
command, the chaincode definition includes the path to the private data collection
definition using the ``--collections-config`` flag. You can see the following `approveformyorg`
command printed in your terminal:
.. code:: bash
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
After channel members agree to the private data collection as part of the chaincode
definition, the data collection is committed to the channel using the `peer lifecycle chaincode commit <commands/peerlifecycle.html#peer-lifecycle-chaincode-commit>`__
command. If you look for the commit command in your logs, you can see that it uses
the same ``--collections-config`` flag to provide the path to the collection definition.
.. code:: bash
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
.. _pd-register-identities:
Register identities
-------------------
The private data transfer smart contract supports ownership by individual identities that belong to the network. In our scenario, the owner of the asset will be a member of Org1, while the buyer will belong to Org2. To highlight the connection between the ``GetClientIdentity().GetID()`` API and the information within a user's certificate, we will register two new identities using the Org1 and Org2 Certificate Authorities (CA's), and then use the CA's to generate each identity's certificate and private key.
First, we need to set the following environment variables to use the Fabric CA client:
.. code :: bash
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
We will use the Org1 CA to create the identity asset owner. Set the Fabric CA client home to the MSP of the Org1 CA admin (this identity was generated by the test network script):
.. code:: bash
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/
You can register a new owner client identity using the `fabric-ca-client` tool:
.. code:: bash
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"
You can now generate the identity certificates and MSP folder by providing the enroll name and secret to the enroll command:
.. code:: bash
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"
Run the command below to copy the Node OU configuration file into the owner identity MSP folder.
.. code:: bash
cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp/config.yaml"
We can now use the Org2 CA to create the buyer identity. Set the Fabric CA client home the Org2 CA admin:
.. code:: bash
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/
You can register a new owner client identity using the `fabric-ca-client` tool:
.. code:: bash
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"
We can now enroll to generate the identity MSP folder:
.. code:: bash
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"
Run the command below to copy the Node OU configuration file into the buyer identity MSP folder.
.. code:: bash
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp/config.yaml"
.. _pd-store-private-data:
Create an asset in private data
-------------------------------
Now that we have created the identity of the asset owner, we can invoke the
private data smart contract to create a new asset. Copy and paste the following
set of commands into your terminal in the `test-network` directory:
:guilabel:`Try it yourself`
.. code :: bash
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
We will use the ``CreateAsset`` function to create an asset that is stored in private
data --- assetID ``asset1`` with a color ``green``, size ``20`` and appraisedValue of ``100``. Recall that private data **appraisedValue**
will be stored separately from the private data **assetID, color, size**.
For this reason, the ``CreateAsset`` function calls the ``PutPrivateData()`` API
twice to persist the private data, once for each collection. Also note that
the private data is passed using the ``--transient`` flag. Inputs passed
as transient data will not be persisted in the transaction in order to keep
the data private. Transient data is passed as binary data and therefore when
using terminal it must be base64 encoded. We use an environment variable
to capture the base64 encoded value, and use ``tr`` command to strip off the
problematic newline characters that linux base64 command adds.
Run the following command to create the asset:
.. code:: bash
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\"}"
You should see results similar to:
.. code:: bash
[chaincodeCmd] chaincodeInvokeOrQuery->INFO 001 Chaincode invoke successful. result: status:200
Note that command above only targets the Org1 peer. The ``CreateAsset`` transaction writes to two collections, ``assetCollection`` and ``Org1MSPPrivateCollection``.
The ``Org1MSPPrivateCollection`` requires an endorsement from the Org1 peer in order to write to the collection, while the ``assetCollection`` inherits the endorsement policy of the chaincode, ``"OR('Org1MSP.peer','Org2MSP.peer')"``.
An endorsement from the Org1 peer can meet both endorsement policies and is able to create an asset without an endorsement from Org2.
.. _pd-query-authorized:
Query the private data as an authorized peer
--------------------------------------------
Our collection definition allows all peers of Org1 and Org2
to have the ``assetID, color, size, and owner`` private data in their side database,
but only peers in Org1 can have Org1's opinion of their ``appraisedValue`` private data in their side
database. As an authorized peer in Org1, we will query both sets of private data.
The first ``query`` command calls the ``ReadAsset`` function which passes
``assetCollection`` as an argument.
.. code-block:: GO
// 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
}
The second ``query`` command calls the ``ReadAssetPrivateDetails``
function which passes ``Org1MSPPrivateDetails`` as an argument.
.. code-block:: GO
// 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
}
Now :guilabel:`Try it yourself`
We can read the main details of the asset that was created by using the `ReadAsset` function
to query the `assetCollection` collection as Org1:
.. code:: bash
peer chaincode query -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'
When successful, the command will return the following result:
.. code:: bash
{"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"}
The `"owner"` of the asset is the identity that created the asset by invoking the smart contract. The private data smart contract uses the ``GetClientIdentity().GetID()`` API to read the name and issuer of the identity certificate. You can see the name and issuer of the identity certificate, in the owner attribute.
Query for the ``appraisedValue`` private data of ``asset1`` as a member of Org1.
.. code:: bash
peer chaincode query -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
You should see the following result:
.. code:: bash
{"assetID":"asset1","appraisedValue":100}
.. _pd-query-unauthorized:
Query the private data as an unauthorized peer
----------------------------------------------
Now we will operate a user from Org2. Org2 has the asset transfer private data
``assetID, color, size, owner`` in its side database as defined in the ``assetCollection`` policy, but does not store the
asset ``appraisedValue`` data for Org1. We will query for both sets of private data.
Switch to a peer in Org2
~~~~~~~~~~~~~~~~~~~~~~~~
Run the following commands to operate as an Org2 member and query the Org2 peer.
:guilabel:`Try it yourself`
.. code:: bash
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Peers in Org2 should have the first set of asset transfer private data (``assetID,
color, size and owner``) in their side database and can access it using the
``ReadAsset()`` function which is called with the ``assetCollection``
argument.
:guilabel:`Try it yourself`
.. code:: bash
peer chaincode query -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'
When successful, should see something similar to the following result:
.. code:: json
{"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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Because the asset was created by Org1, the ``appraisedValue`` associated with
``asset1`` is stored in the ``Org1MSPPrivateCollection`` collection. The value is
not stored by peers in Org2. Run the following command to demonstrate that the
asset's ``appraisedValue`` is not stored in the ``Org2MSPPrivateCollection``
on the Org2 peer:
:guilabel:`Try it yourself`
.. code:: bash
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"]}'
The empty response shows that the asset1 private details do not exist in buyer
(Org2) private collection.
Nor can a user from Org2 read the Org1 private data collection:
.. code:: bash
peer chaincode query -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
By setting ``"memberOnlyRead": true`` in the collection configuration file, we
specify that only clients from Org1 can read data from the collection. An Org2 client
who tries to read the collection would only get the following response:
.. code:: json
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"
Users from Org2 will only be able to see the public hash of the private data.
.. _pd-transfer-asset:
Transfer the Asset
------------------
Let's see what it takes to transfer ``asset1`` to Org2. In this case, Org2 needs to agree
to buy the asset from Org1, and they need to agree on the ``appraisedValue``. You may be wondering how they can
agree if Org1 keeps their opinion of the ``appraisedValue`` in their private side database. For the answer
to this, lets continue.
:guilabel:`Try it yourself`
Switch back to the terminal with our peer CLI.
To transfer an asset, the buyer (recipient) needs to agree to the same ``appraisedValue`` as the asset owner, by calling chaincode function ``AgreeToTransfer``. The agreed value will be stored in the ``Org2MSPDetailsCollection`` collection on the Org2 peer. Run the following commands to agree to the appraised value of 100 as Org2:
.. code:: bash
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\"}"
The buyer can now query the value they agreed to in the Org2 private data collection:
.. code:: bash
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"]}'
The invoke will return the following value:
.. code:: bash
{"assetID":"asset1","appraisedValue":100}
Now that buyer has agreed to buy the asset for the appraised value, the owner can transfer
the asset to Org2. The asset needs to be transferred by the identity that owns the asset,
so lets go acting as Org1:
.. code:: bash
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
The owner from Org1 can read the data added by the `AgreeToTransfer` transaction to view the buyer identity:
.. code:: bash
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"]}'
.. code:: bash
{"assetID":"asset1","buyerID":"eDUwOTo6Q049YnV5ZXIsT1U9Y2xpZW50LE89SHlwZXJsZWRnZXIsU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUzo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"}
We now have all we need to transfer the asset. The smart contract uses the
``GetPrivateDataHash()`` function to check that the hash of the asset appraisal
value in ``Org1MSPPrivateCollection`` matches the hash of the appraisal value in the
``Org2MSPPrivateCollection``. If the hashes are the same, it confirms that the
owner and the interested buyer have agreed to the same asset value. If the
conditions are met, the transfer function will get the client ID of the buyer
from the transfer agreement and make the buyer the new owner of the asset. The transfer
function will also delete the asset appraisal value from the collection of the former owner,
as well as remove the transfer agreement from the ``assetCollection``.
Run the following commands to transfer the asset. The owner needs to provide the
assetID and the organization MSP ID of the buyer to the transfer transaction:
.. code:: bash
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"
You can query ``asset1`` to see the results of the transfer:
.. code:: bash
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"]}'
The results will show that the buyer identity now owns the asset:
.. code:: bash
{"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"}
The `"owner"` of the asset now has the buyer identity.
You can also confirm that transfer removed the private details from the Org1 collection:
.. code:: bash
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"]}'
Your query will return empty result, since the asset private data is removed from the Org1 private data collection.
.. _pd-purge:
Purge Private Data
------------------
For use cases where private data only needs to be persisted for a short period of time,
it is possible to "purge" the data after a certain set number of blocks, leaving
behind only a hash of the data that serves as immutable evidence of the transaction.
An organization could decide to purge private data if the data contained sensitive
information that was used by another transaction, but is not longer needed, or
if the data is being replicated into an off-chain database.
The ``appraisedValue`` data in our example contains a private agreement that
the organization may want to expire after a certain period of time. Thus, it
has a limited lifespan, and can be purged after existing unchanged on the
blockchain for a designated number of blocks using the ``blockToLive`` property
in the collection definition.
The ``Org2MSPPrivateCollection`` definition has a ``blockToLive``
property value of ``3``, meaning this data will live on the side database for
three blocks and then after that it will get purged. If we create additional
blocks on the channel, the ``appraisedValue`` agreed to by Org2 will eventually
get purged. We can create 3 new blocks to demonstrate:
:guilabel:`Try it yourself`
Run the following commands in your terminal to switch back to operating as member
of Org2 and target the Org2 peer:
.. code:: bash
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
We can still query the ``appraisedValue`` in the ``Org2MSPPrivateCollection``:
.. code:: bash
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"]}'
You should see the value printed in your logs:
.. code:: bash
{"assetID":"asset1","appraisedValue":100}
Since we need to keep track of how many blocks we are adding before the private data gets purged,
open a new terminal window and run the following command to view the private data logs for
the Org2 peer. Note the highest block number.
.. code:: bash
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
Now return to the terminal where we are acting as a member of Org2 and run the following
commands to create three new assets. Each command will create a new block.
.. code:: bash
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\"}"
.. code:: bash
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\"}"
.. code:: bash
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\"}"
Return to the other terminal and run the following command to confirm that
the new assets resulted in the creation of three new blocks:
.. code:: bash
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
The ``appraisedValue`` has now been purged from the ``Org2MSPDetailsCollection``
private data collection. Issue the query again from the Org2 terminal to see that
the response is empty.
.. code:: bash
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"]}'
.. _pd-indexes:
Using indexes with private data
-------------------------------
Indexes can also be applied to private data collections, by packaging indexes in
the ``META-INF/statedb/couchdb/collections/<collection_name>/indexes`` directory
alongside the chaincode. An example index is available `here <https://github.com/hyperledger/fabric-samples/blob/{BRANCH}//asset-transfer-private-data/chaincode-go/META-INF/statedb/couchdb/collections/assetCollection/indexes/indexOwner.json>`__ .
For deployment of chaincode to production environments, it is recommended
to define any indexes alongside chaincode so that the chaincode and supporting
indexes are deployed automatically as a unit, once the chaincode has been
installed on a peer and instantiated on a channel. The associated indexes are
automatically deployed upon chaincode instantiation on the channel when
the ``--collections-config`` flag is specified pointing to the location of
the collection JSON file.
.. note:: It is not possible to create an index for use with an implicit private data collection.
An implicit collection is based on the organizations name and is created automatically. The format of the name
is ``_implicit_org_<OrgsMSPid>``
Please see `FAB-17916 <https://jira.hyperledger.org/browse/FAB-17916>`__ for more information.
Clean up
--------
When you are finished using the private data smart contract, you can bring down the test
network using ``network.sh`` script.
.. code:: bash
./network.sh down
This command will bring down the CAs, peers, and ordering node of the network
that we created. Note that all of the data on the ledger will be lost.
If you want to go through the tutorial again, you will start from a clean initial state.
.. _pd-ref-material:
Additional resources
--------------------
For additional private data education, a video tutorial has been created.
.. note:: The video uses the previous lifecycle model to install private data
collections with chaincode.
.. raw:: html
<br/><br/>
<iframe width="560" height="315" src="https://www.youtube.com/embed/qyjDi93URJE" frameborder="0" allowfullscreen></iframe>
<br/><br/>
.. Licensed under Creative Commons Attribution 4.0 International License
https://creativecommons.org/licenses/by/4.0/