github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/docs/source/private_data_tutorial.rst (about) 1 Using Private Data in Fabric 2 ============================ 3 4 This tutorial will demonstrate the use of Private Data Collections (PDC) to provide storage 5 and retrieval of private data on the blockchain network for authorized peers 6 of organizations. The collection is specified using a collection definition file containing the policies governing that collection. 7 8 The information in this tutorial assumes knowledge of private data 9 stores and their use cases. For more information, check out :doc:`private-data/private-data`. 10 11 .. note:: These instructions use the new Fabric chaincode lifecycle introduced 12 in the Fabric v2.0 release. If you would like to use the previous 13 lifecycle model to use private data with chaincode, visit the v1.4 14 version of the `Using Private Data in Fabric tutorial <https://hyperledger-fabric.readthedocs.io/en/release-1.4/private_data_tutorial.html>`__. 15 16 The tutorial will take you through the following steps to practice defining, 17 configuring and using private data with Fabric: 18 19 #. :ref:`pd-use-case` 20 #. :ref:`pd-build-json` 21 #. :ref:`pd-read-write-private-data` 22 #. :ref:`pd-install-define_cc` 23 #. :ref:`pd-register-identities` 24 #. :ref:`pd-store-private-data` 25 #. :ref:`pd-query-authorized` 26 #. :ref:`pd-query-unauthorized` 27 #. :ref:`pd-transfer-asset` 28 #. :ref:`pd-purge` 29 #. :ref:`pd-indexes` 30 #. :ref:`pd-ref-material` 31 32 This tutorial will deploy the `asset transfer private data sample <https://github.com/hyperledger/fabric-samples/tree/master/asset-transfer-private-data/chaincode-go>`__ 33 to the Fabric test network to demonstrate how to create, deploy, and use a collection of 34 private data. 35 You should have completed the task :doc:`install`. 36 37 .. _pd-use-case: 38 39 Asset transfer private data sample use case 40 ------------------------------------------- 41 42 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: 43 44 A member of Org1 creates a new asset, henceforth referred as owner. The public details of the asset, 45 including the identity of the owner, are stored in the private data collection named ``assetCollection``. The asset is also created with an appraised 46 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``. 47 48 To purchase the asset, the buyer needs to agree to the same appraised value as 49 the asset owner. In this step, the buyer (a member of Org2) creates an agreement 50 to trade and agree to an appraisal value using smart contract function ``'AgreeToTransfer'``. 51 This value is stored in ``Org2MSPPrivateCollection`` collection. Now, the asset 52 owner can transfer the asset to the buyer using smart contract function ``'TransferAsset'``. 53 The ``'TransferAsset'`` function uses the hash on the channel ledger to 54 confirm that the owner and the buyer have agreed to the same appraised value 55 before transferring the asset. 56 57 Before we go through the transfer scenario, we will discuss how organizations can use private data collections in Fabric. 58 59 .. _pd-build-json: 60 61 Build a collection definition JSON file 62 --------------------------------------- 63 64 Before a set of organizations can transact using private data, all organizations 65 on channel need to build a collection definition file that defines the private 66 data collections associated with each chaincode. Data that is stored in a private 67 data collection is only distributed to the peers of certain organizations instead 68 of all members of the channel. The collection definition file describes all of the 69 private data collections that organizations can read and write to from a chaincode. 70 71 Each collection is defined by the following properties: 72 73 .. _blockToLive: 74 75 - ``name``: Name of the collection. 76 77 - ``policy``: Defines the organization peers allowed to persist the collection data. 78 79 - ``requiredPeerCount``: Number of peers required to disseminate the private data as 80 a condition of the endorsement of the chaincode 81 82 - ``maxPeerCount``: For data redundancy purposes, the number of other peers 83 that the current endorsing peer will attempt to distribute the data to. 84 If an endorsing peer goes down, these other peers are available at commit time 85 if there are requests to pull the private data. 86 87 - ``blockToLive``: For very sensitive information such as pricing or personal information, 88 this value represents how long the data should live on the private database in terms 89 of blocks. The data will live for this specified number of blocks on the private database 90 and after that it will get purged, making this data obsolete from the network. 91 To keep private data indefinitely, that is, to never purge private data, set 92 the ``blockToLive`` property to ``0``. 93 94 - ``memberOnlyRead``: a value of ``true`` indicates that peers automatically 95 enforce that only clients belonging to one of the collection member organizations 96 are allowed read access to private data. 97 98 - ``memberOnlyWrite``: a value of ``true`` indicates that peers automatically 99 enforce that only clients belonging to one of the collection member organizations 100 are allowed write access to private data. 101 102 - ``endorsementPolicy``: defines the endorsement policy that needs to be met in 103 order to write to the private data collection. The collection level endorsement policy 104 overrides to chaincode level policy. For more information on building a policy 105 definition refer to the :doc:`endorsement-policies` topic. 106 107 The same collection definition file needs to be deployed by all organizations that 108 use the chaincode, even if the organization does not belong to any collections. In 109 addition to the collections that are explicitly defined in a collection file, 110 each organization has access to an implicit collection on their peers that can only 111 be read by their organization. For an example that uses implicit data collections, 112 see the :doc:`secured_asset_transfer/secured_private_asset_transfer_tutorial`. 113 114 The asset transfer private data example contains a `collections_config.json` file 115 that defines three private data collection definitions: ``assetCollection``, ``Org1MSPPrivateCollection``, 116 and ``Org2MSPPrivateCollection``. 117 118 .. code:: json 119 120 // collections_config.json 121 122 [ 123 { 124 "name": "assetCollection", 125 "policy": "OR('Org1MSP.member', 'Org2MSP.member')", 126 "requiredPeerCount": 1, 127 "maxPeerCount": 1, 128 "blockToLive":1000000, 129 "memberOnlyRead": true, 130 "memberOnlyWrite": true 131 }, 132 { 133 "name": "Org1MSPPrivateCollection", 134 "policy": "OR('Org1MSP.member')", 135 "requiredPeerCount": 0, 136 "maxPeerCount": 1, 137 "blockToLive":3, 138 "memberOnlyRead": true, 139 "memberOnlyWrite": false, 140 "endorsementPolicy": { 141 "signaturePolicy": "OR('Org1MSP.member')" 142 } 143 }, 144 { 145 "name": "Org2MSPPrivateCollection", 146 "policy": "OR('Org2MSP.member')", 147 "requiredPeerCount": 0, 148 "maxPeerCount": 1, 149 "blockToLive":3, 150 "memberOnlyRead": true, 151 "memberOnlyWrite": false, 152 "endorsementPolicy": { 153 "signaturePolicy": "OR('Org2MSP.member')" 154 } 155 } 156 ] 157 158 159 The ``policy`` property in the ``assetCollection`` definition specifies that both 160 Org1 and Org2 can store the collection on their peers. The ``memberOnlyRead`` 161 and ``memberOnlyWrite`` parameters are used to specify that only Org1 and 162 Org2 clients can read and write to this collection. 163 164 The ``Org1MSPPrivateCollection`` collection allows only peers of Org1 to have 165 the private data in their private database, while the ``Org2MSPPrivateCollection`` 166 collection can only be stored by the peers of Org2. The ``endorsementPolicy`` parameter 167 is used to create a collection specific endorsement policy. Each update to 168 ``Org1MSPPrivateCollection`` or ``Org2MSPPrivateCollection`` needs to be endorsed 169 by the organization that stores the collection on their peers. We will see how 170 these collections are used to transfer the asset in the course of the tutorial. 171 172 This collection definition file is deployed when the chaincode definition is 173 committed to the channel using the `peer lifecycle chaincode commit command <commands/peerlifecycle.html#peer-lifecycle-chaincode-commit>`__. 174 More details on this process are provided in Section 3 below. 175 176 .. _pd-read-write-private-data: 177 178 Read and Write private data using chaincode APIs 179 ------------------------------------------------ 180 181 The next step in understanding how to privatize data on a channel is to build 182 the data definition in the chaincode. The asset transfer private data sample divides 183 the private data into three separate data definitions according to how the data will 184 be accessed. 185 186 .. code-block:: GO 187 188 // Peers in Org1 and Org2 will have this private data in a side database 189 type Asset struct { 190 Type string `json:"objectType"` //Type is used to distinguish the various types of objects in state database 191 ID string `json:"assetID"` 192 Color string `json:"color"` 193 Size int `json:"size"` 194 Owner string `json:"owner"` 195 } 196 197 // AssetPrivateDetails describes details that are private to owners 198 199 // Only peers in Org1 will have this private data in a side database 200 type AssetPrivateDetails struct { 201 ID string `json:"assetID"` 202 AppraisedValue int `json:"appraisedValue"` 203 } 204 205 // Only peers in Org2 will have this private data in a side database 206 type AssetPrivateDetails struct { 207 ID string `json:"assetID"` 208 AppraisedValue int `json:"appraisedValue"` 209 } 210 211 Specifically, access to the private data will be restricted as follows: 212 213 - ``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). 214 - ``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. 215 216 217 All of the data that is created by the asset transfer private data sample smart 218 contract is stored in PDC. The smart contract uses the Fabric chaincode API 219 to read and write private data to private data collections using the ``GetPrivateData()`` 220 and ``PutPrivateData()`` functions. You can find more information about those functions `here <https://godoc.org/github.com/hyperledger/fabric-chaincode-go/shim#ChaincodeStub>`_. 221 This private data is stored in private state db on the peer (separate from public state db), and 222 is disseminated between authorized peers via gossip protocol. 223 224 The following diagram illustrates the private data model used by the private data 225 sample. Note that Org3 is only shown in the diagram to illustrate that if 226 there were any other organizations on the channel, they would not have access to *any* 227 of the private data collections that were defined in the configuration. 228 229 .. image:: images/SideDB-org1-org2.png 230 231 Reading collection data 232 ~~~~~~~~~~~~~~~~~~~~~~~~ 233 234 The smart contract uses the chaincode API ``GetPrivateData()`` to query private data in the 235 database. ``GetPrivateData()`` takes two arguments, the **collection name** 236 and the data key. Recall the collection ``assetCollection`` allows peers of 237 Org1 and Org2 to have the private data in a side database, and the collection 238 ``Org1MSPPrivateCollection`` allows only peers of Org1 to have their 239 private data in a side database and ``Org2MSPPrivateCollection`` allows peers 240 of Org2 to have their private data in a side database. 241 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>`__: 242 243 * **ReadAsset** for querying the values of the ``assetID, color, size and owner`` attributes. 244 * **ReadAssetPrivateDetails** for querying the values of the ``appraisedValue`` attribute. 245 246 When we issue the database queries using the peer commands later in this tutorial, 247 we will call these two functions. 248 249 Writing private data 250 ~~~~~~~~~~~~~~~~~~~~ 251 252 The smart contract uses the chaincode API ``PutPrivateData()`` to store the private data 253 into the private database. The API also requires the name of the collection. 254 Note that the asset transfer private data sample includes three different private data collections, but it is called 255 twice in the chaincode (in this scenario acting as Org1). 256 257 1. Write the private data ``assetID, color, size and owner`` using the 258 collection named ``assetCollection``. 259 2. Write the private data ``appraisedValue`` using the collection named 260 ``Org1MSPPrivateCollection``. 261 262 If we were acting as Org2, we would replace ``Org1MSPPrivateCollection`` with 263 ````Org2MSPPrivateCollection``. 264 265 For example, in the following snippet of the ``CreateAsset`` function, 266 ``PutPrivateData()`` is called twice, once for each set of private data. 267 268 .. code-block:: GO 269 270 // CreateAsset creates a new asset by placing the main asset details in the assetCollection 271 // that can be read by both organizations. The appraisal value is stored in the owners org specific collection. 272 func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface) error { 273 274 // Get new asset from transient map 275 transientMap, err := ctx.GetStub().GetTransient() 276 if err != nil { 277 return fmt.Errorf("error getting transient: %v", err) 278 } 279 280 // Asset properties are private, therefore they get passed in transient field, instead of func args 281 transientAssetJSON, ok := transientMap["asset_properties"] 282 if !ok { 283 //log error to stdout 284 return fmt.Errorf("asset not found in the transient map input") 285 } 286 287 type assetTransientInput struct { 288 Type string `json:"objectType"` //Type is used to distinguish the various types of objects in state database 289 ID string `json:"assetID"` 290 Color string `json:"color"` 291 Size int `json:"size"` 292 AppraisedValue int `json:"appraisedValue"` 293 } 294 295 var assetInput assetTransientInput 296 err = json.Unmarshal(transientAssetJSON, &assetInput) 297 if err != nil { 298 return fmt.Errorf("failed to unmarshal JSON: %v", err) 299 } 300 301 if len(assetInput.Type) == 0 { 302 return fmt.Errorf("objectType field must be a non-empty string") 303 } 304 if len(assetInput.ID) == 0 { 305 return fmt.Errorf("assetID field must be a non-empty string") 306 } 307 if len(assetInput.Color) == 0 { 308 return fmt.Errorf("color field must be a non-empty string") 309 } 310 if assetInput.Size <= 0 { 311 return fmt.Errorf("size field must be a positive integer") 312 } 313 if assetInput.AppraisedValue <= 0 { 314 return fmt.Errorf("appraisedValue field must be a positive integer") 315 } 316 317 // Check if asset already exists 318 assetAsBytes, err := ctx.GetStub().GetPrivateData(assetCollection, assetInput.ID) 319 if err != nil { 320 return fmt.Errorf("failed to get asset: %v", err) 321 } else if assetAsBytes != nil { 322 fmt.Println("Asset already exists: " + assetInput.ID) 323 return fmt.Errorf("this asset already exists: " + assetInput.ID) 324 } 325 326 // Get ID of submitting client identity 327 clientID, err := submittingClientIdentity(ctx) 328 if err != nil { 329 return err 330 } 331 332 // Verify that the client is submitting request to peer in their organization 333 // This is to ensure that a client from another org doesn't attempt to read or 334 // write private data from this peer. 335 err = verifyClientOrgMatchesPeerOrg(ctx) 336 if err != nil { 337 return fmt.Errorf("CreateAsset cannot be performed: Error %v", err) 338 } 339 340 // Make submitting client the owner 341 asset := Asset{ 342 Type: assetInput.Type, 343 ID: assetInput.ID, 344 Color: assetInput.Color, 345 Size: assetInput.Size, 346 Owner: clientID, 347 } 348 assetJSONasBytes, err := json.Marshal(asset) 349 if err != nil { 350 return fmt.Errorf("failed to marshal asset into JSON: %v", err) 351 } 352 353 // Save asset to private data collection 354 // Typical logger, logs to stdout/file in the fabric managed docker container, running this chaincode 355 // Look for container name like dev-peer0.org1.example.com-{chaincodename_version}-xyz 356 log.Printf("CreateAsset Put: collection %v, ID %v, owner %v", assetCollection, assetInput.ID, clientID) 357 358 err = ctx.GetStub().PutPrivateData(assetCollection, assetInput.ID, assetJSONasBytes) 359 if err != nil { 360 return fmt.Errorf("failed to put asset into private data collecton: %v", err) 361 } 362 363 // Save asset details to collection visible to owning organization 364 assetPrivateDetails := AssetPrivateDetails{ 365 ID: assetInput.ID, 366 AppraisedValue: assetInput.AppraisedValue, 367 } 368 369 assetPrivateDetailsAsBytes, err := json.Marshal(assetPrivateDetails) // marshal asset details to JSON 370 if err != nil { 371 return fmt.Errorf("failed to marshal into JSON: %v", err) 372 } 373 374 // Get collection name for this organization. 375 orgCollection, err := getCollectionName(ctx) 376 if err != nil { 377 return fmt.Errorf("failed to infer private collection name for the org: %v", err) 378 } 379 380 // Put asset appraised value into owners org specific private data collection 381 log.Printf("Put: collection %v, ID %v", orgCollection, assetInput.ID) 382 err = ctx.GetStub().PutPrivateData(orgCollection, assetInput.ID, assetPrivateDetailsAsBytes) 383 if err != nil { 384 return fmt.Errorf("failed to put asset private details: %v", err) 385 } 386 return nil 387 } 388 389 To summarize, the policy definition above for our ``collections_config.json`` 390 allows all peers in Org1 and Org2 to store and transact 391 with the asset transfer private data ``assetID, color, size, owner`` in their 392 private database. But only peers in Org1 can store and transact with 393 the ``appraisedValue`` key data in the Org1 collection ``Org1MSPPrivateCollection`` and only peers 394 in Org2 can store and transact with the ``appraisedValue`` key data in the Org2 collection ``Org2MSPPrivateCollection``. 395 396 As an additional data privacy benefit, since a collection is being used, 397 only the private data *hashes* go through orderer, not the private data itself, 398 keeping private data confidential from orderer. 399 400 Start the network 401 ----------------- 402 403 Now we are ready to step through some commands which demonstrate how to use 404 private data. 405 406 :guilabel:`Try it yourself` 407 408 Before installing, defining, and using the private data smart contract, 409 we need to start the Fabric test network. For the sake of this tutorial, we want 410 to operate from a known initial state. The following command will kill any active 411 or stale Docker containers and remove previously generated artifacts. 412 Therefore let's run the following command to clean up any previous 413 environments: 414 415 .. code:: bash 416 417 cd fabric-samples/test-network 418 ./network.sh down 419 420 From the ``test-network`` directory, you can use the following command to start 421 up the Fabric test network with Certificate Authorities and CouchDB: 422 423 .. code:: bash 424 425 ./network.sh up createChannel -ca -s couchdb 426 427 This command will deploy a Fabric network consisting of a single channel named 428 ``mychannel`` with two organizations (each maintaining one peer node), certificate authorities, and an 429 ordering service while using CouchDB as the state database. Either LevelDB or 430 CouchDB may be used with collections. CouchDB was chosen to demonstrate how to 431 use indexes with private data. 432 433 .. note:: For collections to work, it is important to have cross organizational 434 gossip configured correctly. Refer to our documentation on :doc:`gossip`, 435 paying particular attention to the section on "anchor peers". Our tutorial 436 does not focus on gossip given it is already configured in the test network, 437 but when configuring a channel, the gossip anchors peers are critical to 438 configure for collections to work properly. 439 440 .. _pd-install-define_cc: 441 442 Deploy the private data smart contract to the channel 443 ----------------------------------------------------- 444 445 We can now use the test network script to deploy the smart contract to the channel. 446 Run the following command from the test network directory. 447 448 .. code:: bash 449 450 ./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 451 452 Note that we need to pass the path to the private data collection definition file 453 to the command. As part of deploying the chaincode to the channel, both organizations 454 on the channel must pass identical private data collection definitions as part 455 of the :doc:`chaincode_lifecycle`. We are also deploying the smart contract 456 with a chaincode level endorsement policy of ``"OR('Org1MSP.peer','Org2MSP.peer')"``. 457 This allows Org1 and Org2 to create an asset without receiving an endorsement from 458 the other organization. You can see the steps required to deploy the chaincode 459 printed in your logs after you issue the command above. 460 461 When both organizations approve the chaincode defition using the 462 `peer lifecycle chaincode approveformyorg <commands/peerlifecycle.html#peer-lifecycle-chaincode-approveformyorg>`__ 463 command, the chaincode definition includes the path to the private data collection 464 definition using the ``--collections-config`` flag. You can see the following `approveformyorg` 465 command printed in your terminal: 466 467 .. code:: bash 468 469 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 470 471 After channel members agree to the private data collection as part of the chaincode 472 definition, the data collection is committed to the channel using the `peer lifecycle chaincode commit <commands/peerlifecycle.html#peer-lifecycle-chaincode-commit>`__ 473 command. If you look for the commit command in your logs, you can see that it uses 474 the same ``--collections-config`` flag to provide the path to the collection definition. 475 476 .. code:: bash 477 478 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 479 480 481 .. _pd-register-identities: 482 483 Register identities 484 ------------------- 485 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. 486 487 First, we need to set the following environment variables to use the Fabric CA client: 488 489 .. code :: bash 490 491 export PATH=${PWD}/../bin:${PWD}:$PATH 492 export FABRIC_CFG_PATH=$PWD/../config/ 493 494 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): 495 496 .. code:: bash 497 498 export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/ 499 500 You can register a new owner client identity using the `fabric-ca-client` tool: 501 502 .. code:: bash 503 504 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 505 506 507 You can now generate the identity certificates and MSP folder by providing the enroll name and secret to the enroll command: 508 509 .. code:: bash 510 511 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 512 513 514 Run the command below to copy the Node OU configuration file into the owner identity MSP folder. 515 516 .. code:: bash 517 518 cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp/config.yaml 519 520 521 We can now use the Org2 CA to create the buyer identity. Set the Fabric CA client home the Org2 CA admin: 522 523 .. code:: bash 524 525 export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/ 526 527 You can register a new owner client identity using the `fabric-ca-client` tool: 528 529 .. code:: bash 530 531 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 532 533 534 We can now enroll to generate the identity MSP folder: 535 536 .. code:: bash 537 538 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 539 540 541 Run the command below to copy the Node OU configuration file into the buyer identity MSP folder. 542 543 .. code:: bash 544 545 cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp/config.yaml 546 547 .. _pd-store-private-data: 548 549 Create an asset in private data 550 ------------------------------- 551 552 Now that we have created the identity of the asset owner, we can invoke the 553 private data smart contract to create a new asset. Copy and paste the following 554 set of commands into your terminal in the `test-network` directory: 555 556 :guilabel:`Try it yourself` 557 558 559 .. code :: bash 560 561 export PATH=${PWD}/../bin:$PATH 562 export FABRIC_CFG_PATH=$PWD/../config/ 563 export CORE_PEER_TLS_ENABLED=true 564 export CORE_PEER_LOCALMSPID="Org1MSP" 565 export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt 566 export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp 567 export CORE_PEER_ADDRESS=localhost:7051 568 569 570 We will use the ``CreateAsset`` function to create an asset that is stored in private 571 data --- assetID ``asset1`` with a color ``green``, size ``20`` and appraisedValue of ``100``. Recall that private data **appraisedValue** 572 will be stored separately from the private data **assetID, color, size**. 573 For this reason, the ``CreateAsset`` function calls the ``PutPrivateData()`` API 574 twice to persist the private data, once for each collection. Also note that 575 the private data is passed using the ``--transient`` flag. Inputs passed 576 as transient data will not be persisted in the transaction in order to keep 577 the data private. Transient data is passed as binary data and therefore when 578 using terminal it must be base64 encoded. We use an environment variable 579 to capture the base64 encoded value, and use ``tr`` command to strip off the 580 problematic newline characters that linux base64 command adds. 581 582 Run the following command to create the asset: 583 584 .. code:: bash 585 586 export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset1\",\"color\":\"green\",\"size\":20,\"appraisedValue\":100}" | base64 | tr -d \\n) 587 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\"}" 588 589 You should see results similar to: 590 591 .. code:: bash 592 593 [chaincodeCmd] chaincodeInvokeOrQuery->INFO 001 Chaincode invoke successful. result: status:200 594 595 Note that command above only targets the Org1 peer. The ``CreateAsset`` transaction writes to two collections, ``assetCollection`` and ``Org1MSPPrivateCollection``. 596 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')"``. 597 An endorsement from the Org1 peer can meet both endorsement policies and is able to create an asset without an endorsement from Org2. 598 599 .. _pd-query-authorized: 600 601 Query the private data as an authorized peer 602 -------------------------------------------- 603 604 Our collection definition allows all peers of Org1 and Org2 605 to have the ``assetID, color, size, and owner`` private data in their side database, 606 but only peers in Org1 can have Org1's opinion of their ``appraisedValue`` private data in their side 607 database. As an authorized peer in Org1, we will query both sets of private data. 608 609 The first ``query`` command calls the ``ReadAsset`` function which passes 610 ``assetCollection`` as an argument. 611 612 .. code-block:: GO 613 614 // ReadAsset reads the information from collection 615 func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) { 616 617 log.Printf("ReadAsset: collection %v, ID %v", assetCollection, assetID) 618 assetJSON, err := ctx.GetStub().GetPrivateData(assetCollection, assetID) //get the asset from chaincode state 619 if err != nil { 620 return nil, fmt.Errorf("failed to read asset: %v", err) 621 } 622 623 //No Asset found, return empty response 624 if assetJSON == nil { 625 log.Printf("%v does not exist in collection %v", assetID, assetCollection) 626 return nil, nil 627 } 628 629 var asset *Asset 630 err = json.Unmarshal(assetJSON, &asset) 631 if err != nil { 632 return nil, fmt.Errorf("failed to unmarshal JSON: %v", err) 633 } 634 635 return asset, nil 636 637 } 638 639 The second ``query`` command calls the ``ReadAssetPrivateDetails`` 640 function which passes ``Org1MSPPrivateDetails`` as an argument. 641 642 .. code-block:: GO 643 644 // ReadAssetPrivateDetails reads the asset private details in organization specific collection 645 func (s *SmartContract) ReadAssetPrivateDetails(ctx contractapi.TransactionContextInterface, collection string, assetID string) (*AssetPrivateDetails, error) { 646 log.Printf("ReadAssetPrivateDetails: collection %v, ID %v", collection, assetID) 647 assetDetailsJSON, err := ctx.GetStub().GetPrivateData(collection, assetID) // Get the asset from chaincode state 648 if err != nil { 649 return nil, fmt.Errorf("failed to read asset details: %v", err) 650 } 651 if assetDetailsJSON == nil { 652 log.Printf("AssetPrivateDetails for %v does not exist in collection %v", assetID, collection) 653 return nil, nil 654 } 655 656 var assetDetails *AssetPrivateDetails 657 err = json.Unmarshal(assetDetailsJSON, &assetDetails) 658 if err != nil { 659 return nil, fmt.Errorf("failed to unmarshal JSON: %v", err) 660 } 661 662 return assetDetails, nil 663 } 664 665 Now :guilabel:`Try it yourself` 666 667 We can read the main details of the asset that was created by using the `ReadAsset` function 668 to query the `assetCollection` collection as Org1: 669 670 .. code:: bash 671 672 peer chaincode query -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}' 673 674 When successful, the command will return the following result: 675 676 .. code:: bash 677 678 {"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"} 679 680 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. 681 682 683 Query for the ``appraisedValue`` private data of ``asset1`` as a member of Org1. 684 685 .. code:: bash 686 687 peer chaincode query -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}' 688 689 You should see the following result: 690 691 .. code:: bash 692 693 {"assetID":"asset1","appraisedValue":100} 694 695 .. _pd-query-unauthorized: 696 697 Query the private data as an unauthorized peer 698 ---------------------------------------------- 699 700 Now we will operate a user from Org2. Org2 has the asset transfer private data 701 ``assetID, color, size, owner`` in its side database as defined in the ``assetCollection`` policy, but does not store the 702 asset ``appraisedValue`` data for Org1. We will query for both sets of private data. 703 704 Switch to a peer in Org2 705 ~~~~~~~~~~~~~~~~~~~~~~~~ 706 707 Run the following commands to operate as an Org2 member and query the Org2 peer. 708 709 :guilabel:`Try it yourself` 710 711 .. code:: bash 712 713 export CORE_PEER_LOCALMSPID="Org2MSP" 714 export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt 715 export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp 716 export CORE_PEER_ADDRESS=localhost:9051 717 718 Query private data Org2 is authorized to 719 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 720 721 Peers in Org2 should have the first set of asset transfer private data (``assetID, 722 color, size and owner``) in their side database and can access it using the 723 ``ReadAsset()`` function which is called with the ``assetCollection`` 724 argument. 725 726 :guilabel:`Try it yourself` 727 728 .. code:: bash 729 730 peer chaincode query -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}' 731 732 When successful, should see something similar to the following result: 733 734 .. code:: json 735 736 {"objectType":"asset","assetID":"asset1","color":"green","size":20, 737 "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" } 738 739 Query private data Org2 is not authorized to 740 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 741 742 Because the asset was created by Org1, the ``appraisedValue`` associated with 743 ``asset1`` is stored in the ``Org1MSPPrivateCollection`` collection. The value is 744 not stored by peers in Org2. Run the following command to demonstrate that the 745 asset's ``appraisedValue`` is not stored in the ``Org2MSPPrivateCollection`` 746 on the Org2 peer: 747 748 :guilabel:`Try it yourself` 749 750 .. code:: bash 751 752 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"]}' 753 754 The empty response shows that the asset1 private details do not exist in buyer 755 (Org2) private collection. 756 757 Nor can a user from Org2 read the Org1 private data collection: 758 759 .. code:: bash 760 761 peer chaincode query -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}' 762 763 By setting ``"memberOnlyRead": true`` in the collection configuration file, we 764 specify that only clients from Org1 can read data from the collection. An Org2 client 765 who tries to read the collection would only get the following response: 766 767 .. code:: json 768 769 Error: endorsement failure during query. response: status:500 message:"failed to 770 read asset details: GET_STATE failed: transaction ID: d23e4bc0538c3abfb7a6bd4323fd5f52306e2723be56460fc6da0e5acaee6b23: tx 771 creator does not have read access permission on privatedata in chaincodeName:private collectionName: Org1MSPPrivateCollection" 772 773 Users from Org2 will only be able to see the public hash of the private data. 774 775 .. _pd-transfer-asset: 776 777 Transfer the Asset 778 ------------------ 779 780 Let's see what it takes to transfer ``asset1`` to Org2. In this case, Org2 needs to agree 781 to buy the asset from Org1, and they need to agree on the ``appraisedValue``. You may be wondering how they can 782 agree if Org1 keeps their opinion of the ``appraisedValue`` in their private side database. For the answer 783 to this, lets continue. 784 785 :guilabel:`Try it yourself` 786 787 Switch back to the terminal with our peer CLI. 788 789 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: 790 791 .. code:: bash 792 793 export ASSET_VALUE=$(echo -n "{\"assetID\":\"asset1\",\"appraisedValue\":100}" | base64 | tr -d \\n) 794 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\"}" 795 796 797 The buyer can now query the value they agreed to in the Org2 private data collection: 798 799 .. code:: bash 800 801 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"]}' 802 803 The invoke will return the following value: 804 805 .. code:: bash 806 807 {"assetID":"asset1","appraisedValue":100} 808 809 Now that buyer has agreed to buy the asset for the appraised value, the owner can transfer 810 the asset to Org2. The asset needs to be transferred by the identity that owns the asset, 811 so lets go acting as Org1: 812 813 .. code:: bash 814 815 export CORE_PEER_LOCALMSPID="Org1MSP" 816 export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp 817 export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt 818 export CORE_PEER_ADDRESS=localhost:7051 819 820 The owner from Org1 can read the data added by the `AgreeToTransfer` transaction to view the buyer identity: 821 822 .. code:: bash 823 824 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"]}' 825 826 .. code:: bash 827 828 {"assetID":"asset1","buyerID":"eDUwOTo6Q049YnV5ZXIsT1U9Y2xpZW50LE89SHlwZXJsZWRnZXIsU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUzo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"} 829 830 We now have all we need to transfer the asset. The smart contract uses the 831 ``GetPrivateDataHash()`` function to check that the hash of the asset appraisal 832 value in ``Org1MSPPrivateCollection`` matches the hash of the appraisal value in the 833 ``Org2MSPPrivateCollection``. If the hashes are the same, it confirms that the 834 owner and the interested buyer have agreed to the same asset value. If the 835 conditions are met, the transfer function will get the client ID of the buyer 836 from the transfer agreement and make the buyer the new owner of the asset. The transfer 837 function will also delete the asset appraisal value from the collection of the former owner, 838 as well as remove the transfer agreement from the ``assetCollection``. 839 840 Run the following commands to transfer the asset. The owner needs to provide the 841 assetID and the organization MSP ID of the buyer to the transfer transaction: 842 843 .. code:: bash 844 845 export ASSET_OWNER=$(echo -n "{\"assetID\":\"asset1\",\"buyerMSP\":\"Org2MSP\"}" | base64 | tr -d \\n) 846 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 847 848 You can query ``asset1`` to see the results of the transfer: 849 850 .. code:: bash 851 852 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"]}' 853 854 The results will show that the buyer identity now owns the asset: 855 856 .. code:: bash 857 858 {"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"} 859 860 The `"owner"` of the asset now has the buyer identity. 861 862 You can also confirm that transfer removed the private details from the Org1 collection: 863 864 .. code:: bash 865 866 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"]}' 867 868 Your query will return empty result, since the asset private data is removed from the Org1 private data collection. 869 870 871 .. _pd-purge: 872 873 Purge Private Data 874 ------------------ 875 876 For use cases where private data only needs to be persisted for a short period of time, 877 it is possible to "purge" the data after a certain set number of blocks, leaving 878 behind only a hash of the data that serves as immutable evidence of the transaction. 879 An organization could decide to purge private data if the data contained sensitive 880 information that was used by another transaction, but is not longer needed, or 881 if the data is being replicated into an off-chain database. 882 883 The ``appraisedValue`` data in our example contains a private agreement that 884 the organization may want to expire after a certain period of time. Thus, it 885 has a limited lifespan, and can be purged after existing unchanged on the 886 blockchain for a designated number of blocks using the ``blockToLive`` property 887 in the collection definition. 888 889 The ``Org2MSPPrivateCollection`` definition has a ``blockToLive`` 890 property value of ``3``, meaning this data will live on the side database for 891 three blocks and then after that it will get purged. If we create additional 892 blocks on the channel, the ``appraisedValue`` agreed to by Org2 will eventually 893 get purged. We can create 3 new blocks to demonstrate: 894 895 :guilabel:`Try it yourself` 896 897 Run the following commands in your terminal to switch back to operating as member 898 of Org2 and target the Org2 peer: 899 900 .. code:: bash 901 902 export CORE_PEER_LOCALMSPID="Org2MSP" 903 export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt 904 export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp 905 export CORE_PEER_ADDRESS=localhost:9051 906 907 We can still query the ``appraisedValue`` in the ``Org2MSPPrivateCollection``: 908 909 .. code:: bash 910 911 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"]}' 912 913 You should see the value printed in your logs: 914 915 .. code:: bash 916 917 {"assetID":"asset1","appraisedValue":100} 918 919 Since we need to keep track of how many blocks we are adding before the private data gets purged, 920 open a new terminal window and run the following command to view the private data logs for 921 the Org2 peer. Note the highest block number. 922 923 .. code:: bash 924 925 docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata' 926 927 Now return to the terminal where we are acting as a member of Org2 and run the following 928 commands to create three new assets. Each command will create a new block. 929 930 .. code:: bash 931 932 export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset2\",\"color\":\"blue\",\"size\":30,\"appraisedValue\":100}" | base64 | tr -d \\n) 933 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\"}" 934 935 .. code:: bash 936 937 export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset3\",\"color\":\"red\",\"size\":25,\"appraisedValue\":100}" | base64 | tr -d \\n) 938 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\"}" 939 940 .. code:: bash 941 942 export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset4\",\"color\":\"orange\",\"size\":15,\"appraisedValue\":100}" | base64 | tr -d \\n) 943 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\"}" 944 945 946 Return to the other terminal and run the following command to confirm that 947 the new assets resulted in the creation of three new blocks: 948 949 .. code:: bash 950 951 docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata' 952 953 The ``appraisedValue`` has now been purged from the ``Org2MSPDetailsCollection`` 954 private data collection. Issue the query again from the Org2 terminal to see that 955 the response is empty. 956 957 .. code:: bash 958 959 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"]}' 960 961 962 .. _pd-indexes: 963 964 Using indexes with private data 965 ------------------------------- 966 967 Indexes can also be applied to private data collections, by packaging indexes in 968 the ``META-INF/statedb/couchdb/collections/<collection_name>/indexes`` directory 969 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>`__ . 970 971 For deployment of chaincode to production environments, it is recommended 972 to define any indexes alongside chaincode so that the chaincode and supporting 973 indexes are deployed automatically as a unit, once the chaincode has been 974 installed on a peer and instantiated on a channel. The associated indexes are 975 automatically deployed upon chaincode instantiation on the channel when 976 the ``--collections-config`` flag is specified pointing to the location of 977 the collection JSON file. 978 979 Clean up 980 -------- 981 982 When you are finished using the private data smart contract, you can bring down the test 983 network using ``network.sh`` script. 984 985 986 .. code:: bash 987 988 ./network.sh down 989 990 This command will bring down the CAs, peers, and ordering node of the network 991 that we created. Note that all of the data on the ledger will be lost. 992 If you want to go through the tutorial again, you will start from a clean initial state. 993 994 .. _pd-ref-material: 995 996 Additional resources 997 -------------------- 998 999 For additional private data education, a video tutorial has been created. 1000 1001 .. note:: The video uses the previous lifecycle model to install private data 1002 collections with chaincode. 1003 1004 .. raw:: html 1005 1006 <br/><br/> 1007 <iframe width="560" height="315" src="https://www.youtube.com/embed/qyjDi93URJE" frameborder="0" allowfullscreen></iframe> 1008 <br/><br/> 1009 1010 1011 1012 .. Licensed under Creative Commons Attribution 4.0 International License 1013 https://creativecommons.org/licenses/by/4.0/