Add asset transfer (marbles transfer) sample chaincode

Signed-off-by: NIKHIL E GUPTA <negupta@us.ibm.com>
This commit is contained in:
NIKHIL E GUPTA 2020-05-05 16:11:00 -04:00 committed by denyeart
parent c750e9db6a
commit 72c4bbaff0
8 changed files with 1018 additions and 0 deletions

View file

@ -0,0 +1,336 @@
# Marbles private asset transfer scenario
The marbles transfer smart contract demonstrates how an asset can be represented and traded between organizations in a Hyperledger Fabric blockchain channel, while keeping details of the asset and transaction private.
Each on-chain marble is a non-fungible token (NFT) that represents a specific marble having certain immutable metadata properties (such as size and color) with a unique owner. When the owner wants to sell the marble, both parties need to agree to the same price before the marble is transferred. The marbles transfer smart contract enforces that only the owner of the marble can transfer the marble. In the course of this tutorial, you will learn how Fabric features such as state based endorsement, private data, and access control come together to provide secured transactions that are both private and verifiable.
## Scenario requirements
The marbles transfer scenario is bound by the following requirements:
- A marble may be issued by the first owner's organization (in the real world issuance may be restricted to some authority that certifies a marble's properties).
- Ownership is managed at the organization level (the Fabric permissioning scheme would equally support ownership at an individual identity level within an organization).
- The marble identifier and owner is stored as public channel data for all channel members to see.
- The marble metadata properties however are private information known only to the asset owner (and prior owners).
- An interested buyer will want to verify a marble's private properties.
- An interested buyer will want to verify a marble's provenance, specifically the marble's origin and chain of custody. They will also want to verify that the marble has not changed since issuance, and that all prior transfers have been legitimate.
- To transfer a marble, a buyer and seller must first agree on a sales price.
- Only the current owner may transfer their marble to another organization.
- The actual marble transfer must verify that the legitimate marble is being transferred, and verify that the price has been agreed to. Both buyer and seller must endorse the transfer.
## How privacy is maintained
The smart contract uses the following techniques to ensure that the marble properties remain private:
- The marble metadata properties are stored in the current owning organization's implicit private data collection on the organization's peers only. Each organization on a Fabric channel has a private data collection that their own organization can use. This collection is *implicit* because it does not need to be explicitly defined in the chaincode.
- Although a hash of the private properties is automatically stored on-chain for all channel members to see, a random salt is included in the private properties so that other channel members cannot guess the private data preimage through a dictionary attack.
- Smart contract requests utilize the transient field for private data so that private data does not get included in the final on-chain transaction.
- Private data queries must originate from a client whose org id matches the peer's org id, which must be the same as the marble owner's org id.
## How the transfer is implemented
Before we start using the marbles transfer smart contract we will provide an overview of the transaction flow and how Fabric features are used to protect the asset created on the blockchain:
**Step 1: Creating the marble**
The marbles transfer smart contract is deployed with an endorsement policy that requires an endorsement from any channel member. This allows any organization to create a marble that they own without requiring an endorsement from other channel members. The creation of the marble is the only transaction that uses the chaincode level endorsement policy. Transactions that update or transfer existing marbles will be governed by state based endorsement policies or the endorsement policies of private data collections. Note that in other scenarios, you may want an issuing authority to also endorse create transactions.
The smart contract uses the following Fabric features to ensure that the Marble can only be updated or transferred by the organization that owns the marble:
- When the marble is created, the smart contract gets the MSP ID of the organization that submitted the request, and stores the MSP ID as the owner in the marble key/value in the public chaincode world state. Subsequent smart contract requests to update or transfer the marble will use access control logic to verify that the requesting client is from the same organization. Note that in other scenarios, the ownership could be based on a specific client identity within an organization, rather than an organization itself.
- Also when the marble is created, the smart contract sets a state based endorsement policy for the marble key. The state based policy specifies that a peer from the organization that owns the marble must endorse a subsequent request to update or transfer the marble. This prevents any other organization from updating or transferring the marble using a smart contract that has been maliciously altered on their own peers.
**Step 2: Agreeing to the transfer.**
After a marble is created, channel members can use the smart contract to agree to transfer the marble:
- The owner of the marble can change the description in the public ownership record, for example to advertise that the marble is for sale. Smart contract access control enforces that this change needs to be submitted from a member of the marble owner organization. The state based endorsement policy enforces that this description change must be endorsed by a peer from the owner's organization.
<!--
- Interested buyers can ask the current owner for the private details, and then verify those details against the on-chain hash on their own trusted peer.
- An interested buyer can query the history of the public key. The response includes all transactions that updated the key. Each of those transactions can be inspected to verify the marble's origination, the chain of custody, and that the marble private details hash has not changed since issuance.
-->
The marble owner and the marble buyer agree to transfer the marble for a certain price:
- The price agreed to by the buyer and the seller is stored in each organization's implicit private data collection. The private data collection keeps the agreed price secret from other members of the channel. The endorsement policy of the private data collection ensures that the respective organization's peer endorsed the price agreement, and the smart contract access control logic ensures that the price agreement was submitted by a client of the associated organization.
- A hash of each price agreement is stored on the ledger. The two hashes will match only if the two organizations have agreed to the same price. This allows the organizations to verify that they have come to agreement on the transfer details before the transfer takes place. A random trade id is added to the price agreement, which serves as a *salt* to ensure that other channel members can not use the hash on the ledger to guess the price.
**Step 3: Transferring the marble**
After the two organizations have agreed to the same price, the marble owner can use the transfer function to transfer the marble to the buyer:
- Smart contract access control ensures that the transfer must be initiated by a member of the organization that owns the marble.
- The transfer function verifies that the marble details passed to the transfer function matches the on chain hash, to ensure that the marble owner is *selling* the same marble that they own.
- The transfer function uses the hash of the price agreement on the ledger to ensure that both organizations have agreed to the same price.
- If the transfer conditions are met, the transfer function adds the marble to the implicit private data collection of the buyer, and deletes the marble from the collection of the seller. The transfer also updates the owner in the public ownership record.
- Because of the endorsement policies of the seller and buyer implicit data collections, and the state based endorsement policy of the public record (requiring the seller to endorse), the transfer needs to be endorsed by peers from both buyer and seller.
- The state based endorsement policy of the public marble record is updated so that only a peer of the new owner of the marble can update or sell their new marble.
## Running the marbles transfer smart contract
You can use the Fabric test network to run the marbles transfer smart contract. The test network contains two peer organizations, Org1 and Org1, that operate one peer each. In this tutorial, we will deploy the smart contract to a channel of the test network joined by both organizations. We will first create a marble that is owned by Org1. After the two organizations agree on a marble price, we will transfer the marble from Org1 to Org2.
## Deploy the test network
We need to deploy an instance of the Fabric test network to run the smart contract. Open a command terminal and navigate to test network directory in your local clone of the `fabric-samples`. We will operate from the `test-network` directory for the remainder of the tutorial.
```
cd fabric-samples/test-network
```
You can then deploy the network with the following command:
```
./network.sh up createChannel
```
The script will deploy the nodes of the network create a single channel named `mychannel` with Org1 and Org2 as channel members. We will use this channel to deploy the smart contract and trade our marble.
### Set the environment variables to operate as Org1
In the course of running this sample, you need to interact with the network as both Org1 and Org2. To make the tutorial easier to use, we will use separate terminals for each organization. Open a new terminal and make sure that you are operating from the `test-network` directory. Set the following environment variables to operate the `peer` CLI as the Org1 admin:
```
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@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 environment variables also specify the endpoint information of the Org1 peer to submit requests.
### Set the environment variables to operate as Org2
Now that we have one terminal that we can operate as Org1, open a new terminal for Org2. Make sure that this terminal is also operating from the `test-network` directory. Set the following environment variables to operate as the Org2 admin:
```
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:9051
```
You will need switch between the two terminals as you go through the tutorial.
## Deploy the chaincode
Now that we can operate as both organizations, we need install the marble transfer smart contract on the peers of Org1 and Org2, and deploy the chaincode to the channel approving and committing the chaincode definition.
### Install and approve the chaincode as Org1
Open the Org1 terminal. Run the following command to package the marbles transfer chaincode:
```
peer lifecycle chaincode package marbles_transfer.tar.gz --path ../chaincode/marbles_transfer --lang golang --label marbles_transfer_1
```
The command creates a chaincode package named `marbles_transfer.tar.gz`. We can now install this package on the Org1 peer:
```
peer lifecycle chaincode install marbles_transfer.tar.gz
```
You will need the chaincode package ID in order to approve the chaincode definition. You can find the package ID by querying your peer:
```
peer lifecycle chaincode queryinstalled
```
Save the package ID as an environment variable. The package ID will not be the same for all users, so need to use the result that was returned by the previous command:
```
export PACKAGE_ID=marbles_transfer_1:2a585633baa0a6ba0019965ac40d6f188194c50df1015010b080ef6ba426d266
```
You can now approve the chaincode as Org1:
```
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name marbles_transfer --version 1 --package-id $PACKAGE_ID --sequence 1 --tls true --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --signature-policy "OR('Org1MSP.peer','Org2MSP.peer')"
```
Note we are approving a chaincode endorsement policy of `"OR('Org1MSP.peer','Org2MSP.peer')"`. This allows either organization to create a marble without receiving an endorsement from the other organization.
### Install and approve the chaincode as Org2
We can now install and approve the chaincode as Org2. Open the Org2 terminal. Because the chaincode is already packaged on our local machine, we can go ahead and install the chaincode on the Org2 peer:`
```
peer lifecycle chaincode install marbles_transfer.tar.gz
```
Query the package ID of the chaincode:
```
peer lifecycle chaincode queryinstalled
```
Save the result of the command as an environment variable in the Org2 command window:
```
export PACKAGE_ID=marbles_transfer_1:2a585633baa0a6ba0019965ac40d6f188194c50df1015010b080ef6ba426d266
```
We can now approve the chaincode as the Org2 admin:
```
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name marbles_transfer --version 1 --package-id $PACKAGE_ID --sequence 1 --tls true --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --signature-policy "OR('Org1MSP.peer','Org2MSP.peer')"
```
Now that a majority (2 out of 2) of channel members have approved the chaincode definition, Org2 can commit the chaincode definition to deploy the chaincode to the channel:
```
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name marbles_transfer --version 1 --sequence 1 --tls true --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --signature-policy "OR('Org1MSP.peer','Org2MSP.peer')"
```
We are now ready use the marbles transfer smart contract.
## Create a Marble
Any channel member can use the smart contract to create a marble that is owned by their organization. The details of the marble will be stored in a private data collection, and can only accessed by the organization that owns the marble. A public record of the marble, its owner, and a public description is stored on the channel ledger. Any channel member can access the public ownership record to see who owns the marble, and can read the description to see if the marble is for sale.
### Operate from the Org1 terminal
Before we create the marble, we need to specify the details of what our marble will be. Issue the following command to create a JSON that will describe the marble. The `"salt"` parameter is a random string that would prevent another member of the channel from guessing the marble using the hash on the ledger. If there was no salt, a user could theoretically guess marble parameters until the hash of the of the guess and the hash on the ledger matched (this is known as a dictionary attack). This string is encoded in Base64 format so that it can be passed to the creation transaction as transient data.
```
export MARBLE_PROPERTIES=$(echo -n "{\"object_type\":\"marble_properties\",\"marble_id\":\"marble1\",\"color\":\"blue\",\"size\":35,\"salt\":\"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3\"}" | base64)
```
We can now use the following command to create a marble that belongs to Org1:
```
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 marbles_transfer -c '{"function":"CreateMarble","Args":["marble1"]}' --transient "{\"marble_properties\":\"$MARBLE_PROPERTIES\"}"
```
We can can query the Org1 implicit data collection to see the marble that was created:
```
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 marbles_transfer -c '{"function":"QueryMarblePrivateImmutableProperties","Args":["marble1"]}'
```
When successful, the command will return the following result:
```
{"object_type":"marble_properties","marble_id":"marble1","color":"blue","size":35,"salt":"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"}
```
We can also query the ledger to see the public ownership record:
```
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 marbles_transfer -c '{"function":"QueryMarble","Args":["marble1"]}'
```
The command will return the record that the marble1 is owned by Org1:
```
{"object_type":"marble","marble_id":"marble1","owner_org":"Org1MSP","public_description":"A new marble for Org1MSP"}
```
Because the market for marbles is hot, Org1 wants to flip this marble and put it up for sale. As the marble owner, Org1 can update the public description to advertise that the marble is for sale. Run the following command to change the marble description:
```
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 marbles_transfer -c '{"function":"ChangePublicDescription","Args":["marble1","This marble is for sale"]}'
```
Query the ledger again to see the updated description:
```
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 marbles_transfer -c '{"function":"QueryMarble","Args":["marble1"]}'
```
We can now see that the marble is for sale:
```
{"object_type":"marble","marble_id":"marble1","owner_org":"Org1MSP","public_description":"This marble is for sale"}
```
![Org1 creates a Marble](images/transfer_marbles_1.png)
*Figure 1: When Org1 creates a marble that they own, the marble details are stored in the Org1 implicit data collection on the Org1 peer. The public ownership record is stored in the channel world state, and is stored on both the Org1 and Org2 peers. A hash of the marble details is also visible in the channel world state and is stored on the peers of both organizations.*
### Operate from the Org2 terminal
If we operate from the Org2 terminal, we can use the smart contract query the public marble data:
```
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 marbles_transfer -c '{"function":"QueryMarble","Args":["marble1"]}'
```
From this query, Org2 learns that marble1 is for sale:
```
{"object_type":"marble","marble_id":"marble1","owner_org":"Org1MSP","public_description":"This marble is for sale"}
```
Any changes to the public description of the Marble owned by Org1 needs to be endorsed by Org1. The endorsement policy is reinforced by an access control policy within the chaincode that any updated need to be submitted by the organization that owns the marble. Lets see what happens if Org2 tried to change the public description as a prank:
```
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 marbles_transfer -c '{"function":"ChangePublicDescription","Args":["marble1","the worst marble"]}'
```
The smart contract does not allow Org2 to access the public description of the Marble.
```
Error: endorsement failure during invoke. response: status:500 message:"a client from Org2MSP cannot update the description of a marble owned by Org1MSP"
```
## Agree to sell the marble
To sell a marble, both the buyer and the seller must agree on a marble price. Each party stores the price that they agree to in their own private data collection. The marbles transfer smart contract enforces that both parties need to agree to the same price before the marble can be transferred.
## Agree to sell as Org1
Operate from the Org1 terminal. Org1 will agree to set the marble price as 110 dollars. The `trade_id` is used as salt to prevent a channel member that is not a buyer or a seller from guessing the price. This value needs to be passed out of band, through email or other communication, between the buyer and the seller.
```
export MARBLE_PRICE=$(echo -n "{\"marble_id\":\"marble1\",\"trade_id\":\"109f4b3c50d7b0df729d299bc6f8e9ef9066971f\",\"price\":110}" | base64)
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 marbles_transfer -c '{"function":"AgreeToSell","Args":["marble1"]}' --transient "{\"marble_price\":\"$MARBLE_PRICE\"}"
```
We can query the Org1 private data collection to read the agreed to selling price:
```
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 marbles_transfer -c '{"function":"QueryMarbleSalesPrice","Args":["marble1"]}'
```
## Agree to buy as Org2
Operate from the Org2 terminal. Run the following command to agree to buy marble1 for 100 dollars. As of now, Org2 will agree to a different price than Org2. Don't worry, the two organizations will agree to the same price in a future step. However, we we can use this temporary disagreement as a test of what happens if the buyer and the seller agree to a different price. Org2 needs to use the same `trade_id` as Org1.
```
export MARBLE_PRICE=$(echo -n "{\"marble_id\":\"marble1\",\"trade_id\":\"109f4b3c50d7b0df729d299bc6f8e9ef9066971f\",\"price\":100}" | base64)
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 marbles_transfer -c '{"function":"AgreeToBuy","Args":["marble1"]}' --transient "{\"marble_price\":\"$MARBLE_PRICE\"}"
```
You can read the agreed purchase price from the Org2 implicit data collection:
```
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 marbles_transfer -c '{"function":"QueryMarbleBidPrice","Args":["marble1"]}'
```
![Org1 and Org2 agree on transfer](images/transfer_marbles_2.png)
*Figure 2: After Org1 and Org2 agree to transfer the marble, the price agreed to by each organization is stored in their private data collections. A composite key of "MarbleforSale" is used to prevent a collision with the marble details and marble ownership record. The price that is agreed to is only stored on the peers of each organization. However, the hash of both agreements is stored in the channel world state on every peer joined to the channel.*
## Transfer the marble from to Org2
After both organizations have agreed to their price, Org1 can attempt to transfer the marble to Org2. The marbles transfer function in the smart contract uses the hash on the ledger to check that both organizations have agreed to the same price. The function will also use the hash of the private marble details to check that the marble that is transferred is the same marble that Org1 owns.
### Transfer the marble as Org1
Operate from the Org1 terminal. The owner of the marble needs to initiate the transfer. Note that the command below uses the `--peerAddresses` flag to target the peers of both Org1 and Org2. Both organizations need to endorse the transfer.
```
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 marbles_transfer -c '{"function":"TransferMarble","Args":["marble1","Org2MSP"]}' --transient "{\"marble_properties\":\"$MARBLE_PROPERTIES\",\"marble_price\":\"$MARBLE_PRICE\"}" --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
```
Because the two organizations have not agreed to the same price, the transfer cannot be completed:
```
Error: endorsement failure during invoke. response: status:500 message:"failed transfer verification: hash cf74b8ce092b637bd28f98f7cdd490534c102a0665e7c985d4f2ab9810e30b1c for passed price JSON {\"marble_id\":\"marble1\",\"trade_id\":\"109f4b3c50d7b0df729d299bc6f8e9ef9066971f\",\"price\":110} does not match on-chain hash 09341dbb39e81fb50ccb3a81770254525318f777fad217ae49777487116cceb4, buyer hasn't agreed to the passed trade id and price"
```
As a result, Org1 and Org2 come to a new agreement on the price at which the marble will be purchased. Org1 drops the price of the marble to 100:
```
export MARBLE_PRICE=$(echo -n "{\"marble_id\":\"marble1\",\"trade_id\":\"109f4b3c50d7b0df729d299bc6f8e9ef9066971f\",\"price\":100}" | base64)
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 marbles_transfer -c '{"function":"AgreeToSell","Args":["marble1"]}' --transient "{\"marble_price\":\"$MARBLE_PRICE\"}"
```
Now that the buyer and seller have agreed to the same price, Org1 can transfer the marble to Org2.
```
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 marbles_transfer -c '{"function":"TransferMarble","Args":["marble1","Org2MSP"]}' --transient "{\"marble_properties\":\"$MARBLE_PROPERTIES\",\"marble_price\":\"$MARBLE_PRICE\"}" --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
```
You can query the marble ownership record to verify that the transfer was successful.
```
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 marbles_transfer -c '{"function":"QueryMarble","Args":["marble1"]}'
```
The record now lists Org2 as the Marble owner:
```
{"object_type":"marble","marble_id":"marble1","owner_org":"Org2MSP","public_description":"This marble is for sale"}
```
![Org1 transfers the marble to Org2](images/transfer_marbles_3.png)
*Figure 3: After the marble is transferred, the marble details are placed in the Org2 implicit data collection and deleted from the Org1 implicit data collection. As a result, the marble details are now only stored on the Org2 peer. The marble ownership record on the ledger is updated to reflect that the marble is owned by Org1.*
### Update the marble description as Org2
Operate from the Org2 terminal. Now that Org2 owns the marble, we can read the marble details from the Org2 implicit data collection:
```
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 marbles_transfer -c '{"function":"QueryMarblePrivateImmutableProperties","Args":["marble1"]}'
```
Org2 can now update the marble public description:
```
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 marbles_transfer -c '{"function":"ChangePublicDescription","Args":["marble1","This marble is not for sale"]}'
```
Query the ledger to verify that the marble is no longer for sale:
```
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 marbles_transfer -c '{"function":"QueryMarble","Args":["marble1"]}'
```
## Clean up
When you are finished transferring marbles, you can bring down the test network. The command will remove all the nodes of the test network, and delete any ledger data that you created:
```
./network down
```

View file

@ -0,0 +1,8 @@
module github.com/hyperledger/fabric-samples/chaincode/tradingMarbles
go 1.14
require (
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200128192331-2d899240a7ed
github.com/hyperledger/fabric-contract-api-go v1.0.0
)

View file

@ -0,0 +1,136 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/godog v0.7.13/go.mod h1:z2OZ6a3X0/YAKVqLfVzYBwFt3j6uSt3Xrqa7XTtcQE0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200128192331-2d899240a7ed h1:VNnrD/ilIUO9DDHQP/uioYSy1309rYy0Z1jf3GLNRIc=
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200128192331-2d899240a7ed/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc=
github.com/hyperledger/fabric-contract-api-go v1.0.0 h1:ma1nQX1S/a3zDkfkTb0QXQHNGgJUmEfqHA9/CWmz8Y0=
github.com/hyperledger/fabric-contract-api-go v1.0.0/go.mod h1:PHF7I0hYI0cZF2j7cdyNHaY5FJD3Q49qnnNgsmxEPbM=
github.com/hyperledger/fabric-protos-go v0.0.0-20190919234611-2a87503ac7c9/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
github.com/hyperledger/fabric-protos-go v0.0.0-20200124220212-e9cfc186ba7b h1:rZ3Vro68vStzLYfcSrQlprjjCf5UmFk7QjKGgHL8IQg=
github.com/hyperledger/fabric-protos-go v0.0.0-20200124220212-e9cfc186ba7b/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ=
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

View file

@ -0,0 +1,417 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package main
import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/pkg/statebased"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
const (
typeMarbleForSale = "S"
typeMarbleBid = "B"
)
type SmartContract struct {
contractapi.Contract
}
// Marble struct and properties must be exported (start with capitals) to work with contract api metadata
type Marble struct {
ObjectType string `json:"object_type"` // ObjectType is used to distinguish different object types in the same chaincode namespace
ID string `json:"marble_id"`
OwnerOrg string `json:"owner_org"`
PublicDescription string `json:"public_description"`
}
// CreateMarble creates a marble and sets it as owned by the client's org
func (s *SmartContract) CreateMarble(ctx contractapi.TransactionContextInterface, marbleID string) error {
transMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("Error getting transient: " + err.Error())
}
// Marble properties are private, therefore they get passed in transient field
immutablePropertiesJSON, ok := transMap["marble_properties"]
if !ok {
return fmt.Errorf("marble_properties key not found in the transient map")
}
// Get client org id and verify it matches peer org id.
// In this scenario, client is only authorized to read/write private data from its own peer.
clientOrgID, err := getClientOrgID(ctx, true)
if err != nil {
return fmt.Errorf("failed to get verified OrgID: %s", err.Error())
}
// Create and persit marble
marble := Marble{
ObjectType: "marble",
ID: marbleID,
OwnerOrg: clientOrgID,
PublicDescription: "A new marble for " + clientOrgID,
}
marbleJSON, err := json.Marshal(marble)
if err != nil {
return fmt.Errorf("failed to create marble JSON: %s", err.Error())
}
err = ctx.GetStub().PutState(marble.ID, marbleJSON)
if err != nil {
return fmt.Errorf("failed to put Marble in public data: %s", err.Error())
}
// Set the endorsement policy such that an owner org peer is required to endorse future updates
err = setMarbleStateBasedEndorsement(ctx, marble.ID, clientOrgID)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for owner: %s", err.Error())
}
// Persist private immutable marble properties to owner's private data collection
collection := "_implicit_org_" + clientOrgID
err = ctx.GetStub().PutPrivateData(collection, marble.ID, []byte(immutablePropertiesJSON))
if err != nil {
return fmt.Errorf("failed to put Marble private details: %s", err.Error())
}
return nil
}
// ChangePublicDescription updates the marble public description. Only the current owner can update the public description
func (s *SmartContract) ChangePublicDescription(ctx contractapi.TransactionContextInterface, marbleID string, newDescription string) error {
// Get client org id
// No need to check client org id matches peer org id, rely on the marble ownership check instead.
clientOrgID, err := getClientOrgID(ctx, false)
if err != nil {
return fmt.Errorf("failed to get verified OrgID: %s", err.Error())
}
marble, err := s.QueryMarble(ctx, marbleID)
if err != nil {
return fmt.Errorf("failed to get marble: %s", err.Error())
}
// auth check to ensure that client's org actually owns the marble
if clientOrgID != marble.OwnerOrg {
return fmt.Errorf("a client from %s cannot update the description of a marble owned by %s", clientOrgID, marble.OwnerOrg)
}
marble.PublicDescription = newDescription
updatedMarbleJSON, err := json.Marshal(marble)
if err != nil {
return fmt.Errorf("failed to marshal marble: %s", err.Error())
}
return ctx.GetStub().PutState(marbleID, updatedMarbleJSON)
}
// AgreeToSell adds seller's asking price to seller's implicit private data collection
func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface, marbleID string) error {
return agreeToMarblePrice(ctx, marbleID, typeMarbleForSale)
}
// AgreeToBuy adds buyer's bid price to buyer's implicit private data collection
func (s *SmartContract) AgreeToBuy(ctx contractapi.TransactionContextInterface, marbleID string) error {
return agreeToMarblePrice(ctx, marbleID, typeMarbleBid)
}
// agreeToMarblePrice adds a bid or ask price to caller's implicit private data collection
func agreeToMarblePrice(ctx contractapi.TransactionContextInterface, marbleID string, priceType string) error {
// Get client org id and verify it matches peer org id.
// In this scenario, client is only authorized to read/write private data from its own peer.
clientOrgID, err := getClientOrgID(ctx, true)
if err != nil {
return fmt.Errorf("failed to get verified OrgID: %s", err.Error())
}
// TODO query marble and verify that this clientOrgId actually owns the marble.
// That is, You can only put the marble for sale if you own it.
// price is private, therefore it gets passed in transient field
transMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("Error getting transient: " + err.Error())
}
// Price hash will get verfied later, therefore always pass and persist the JSON bytes as-is,
// so that there is no risk of nondeterministic marshaling.
priceJSON, ok := transMap["marble_price"]
if !ok {
return fmt.Errorf("marble_price key not found in the transient map")
}
collection := "_implicit_org_" + clientOrgID
// Persist the agreed to price in a collection sub-namespace based on priceType key prefix,
// to avoid collisions between private marble properties, sell price, and buy price
marblePriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{marbleID})
if err != nil {
return fmt.Errorf("failed to create composite key: %s", err.Error())
}
err = ctx.GetStub().PutPrivateData(collection, marblePriceKey, priceJSON)
if err != nil {
return fmt.Errorf("failed to put marble bid: %s", err.Error())
}
return nil
}
// TODO implement function to verify marble properties
// For example, Org1 may tell Org2 about the properties and salt.
// Org2 would want to verify the properties before agreeing to buy.
// Org2 would call a verify function on his peer.
// The properties and salt would passed in, get hashed in the chaincode, and compared with the on-chain hash of the marble properties (queried via GetPrivateDataHash).
// TransferMarble checks transfer conditions and then transfers marble state to buyer.
// TransferMarble can only be called by current owner
func (s *SmartContract) TransferMarble(ctx contractapi.TransactionContextInterface, marbleID string, buyerOrgID string) error {
// Get client org id and verify it matches peer org id.
// For a transfer, selling client must get endorsement from their own peer and from buyer peer, therefore don't verify client org id matches peer org id
clientOrgID, err := getClientOrgID(ctx, false)
if err != nil {
return fmt.Errorf("failed to get verified OrgID: %s", err.Error())
}
transMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("Error getting transient: " + err.Error())
}
immutablePropertiesJSON, ok := transMap["marble_properties"]
if !ok {
return fmt.Errorf("marble_properties key not found in the transient map")
}
priceJSON, ok := transMap["marble_price"]
if !ok {
return fmt.Errorf("marble_price key not found in the transient map")
}
marble, err := s.QueryMarble(ctx, marbleID)
if err != nil {
return fmt.Errorf("failed to get marble: %s", err.Error())
}
err = verifyTransferConditions(ctx, marble, immutablePropertiesJSON, clientOrgID, buyerOrgID, priceJSON)
if err != nil {
return fmt.Errorf("failed transfer verification: %s", err.Error())
}
err = transferMarbleState(ctx, marble, immutablePropertiesJSON, clientOrgID, buyerOrgID)
if err != nil {
return fmt.Errorf("failed marble transfer: %s", err.Error())
}
return nil
}
// verifyTransferConditions checks that client org currently owns marble and that both parties have agreed on price
func verifyTransferConditions(ctx contractapi.TransactionContextInterface, marble *Marble, immutablePropertiesJSON []byte, clientOrgID string, buyerOrgID string, priceJSON []byte) error {
// CHECK1: auth check to ensure that client's org actually owns the marble
if clientOrgID != marble.OwnerOrg {
return fmt.Errorf("a client from %s cannot transfer a marble owned by %s", clientOrgID, marble.OwnerOrg)
}
// CHECK2: verify that the hash of the passed immutable properties matches the on-chain hash
// get on chain hash
collectionSeller := "_implicit_org_" + clientOrgID
immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, marble.ID)
if err != nil {
return fmt.Errorf("failed to read marble private properties hash from seller's collection: %s", err.Error())
}
if immutablePropertiesOnChainHash == nil {
return fmt.Errorf("marble private properties hash does not exist: %s", marble.ID)
}
// get sha256 hash of passed immutable properties
hash := sha256.New()
hash.Write(immutablePropertiesJSON)
calculatedPropertiesHash := hash.Sum(nil)
// verify that the hash of the passed immutable properties matches the on-chain hash
if !bytes.Equal(immutablePropertiesOnChainHash, calculatedPropertiesHash) {
return fmt.Errorf("hash %x for passed immutable properties %s does not match on-chain hash %x", calculatedPropertiesHash, immutablePropertiesJSON, immutablePropertiesOnChainHash)
}
// CHECK3: verify that seller and buyer agreed on the same price
// get seller (current owner) asking price
marbleForSaleKey, err := ctx.GetStub().CreateCompositeKey(typeMarbleForSale, []string{marble.ID})
if err != nil {
return fmt.Errorf("failed to create composite key: %s", err.Error())
}
sellerPriceHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, marbleForSaleKey)
if err != nil {
return fmt.Errorf("failed to get seller price hash: %s", err.Error())
}
if sellerPriceHash == nil {
return fmt.Errorf("seller price for %s does not exist", marble.ID)
}
// get buyer bid price
collectionBuyer := "_implicit_org_" + buyerOrgID
marbleBidKey, err := ctx.GetStub().CreateCompositeKey(typeMarbleBid, []string{marble.ID})
if err != nil {
return fmt.Errorf("failed to create composite key: %s", err.Error())
}
buyerPriceHash, err := ctx.GetStub().GetPrivateDataHash(collectionBuyer, marbleBidKey)
if err != nil {
return fmt.Errorf("failed to get buyer price hash: %s", err.Error())
}
if buyerPriceHash == nil {
return fmt.Errorf("buyer price for %s does not exist", marble.ID)
}
// get sha256 hash of passed price
hash = sha256.New()
hash.Write(priceJSON)
calculatedPriceHash := hash.Sum(nil)
// verify that the hash of the passed price matches the on-chain seller price hash
if !bytes.Equal(calculatedPriceHash, sellerPriceHash) {
return fmt.Errorf("hash %x for passed price JSON %s does not match on-chain hash %x, seller hasn't agreed to the passed trade id and price", calculatedPriceHash, priceJSON, sellerPriceHash)
}
// verify that the hash of the passed price matches the on-chain buyer price hash
if !bytes.Equal(calculatedPriceHash, buyerPriceHash) {
return fmt.Errorf("hash %x for passed price JSON %s does not match on-chain hash %x, buyer hasn't agreed to the passed trade id and price", calculatedPriceHash, priceJSON, buyerPriceHash)
}
// since all checks passed, return without an error
return nil
}
// transferMarbleState makes the public and private state updates for the transferred marble
func transferMarbleState(ctx contractapi.TransactionContextInterface, marble *Marble, immutablePropertiesJSON []byte, clientOrgID string, buyerOrgID string) error {
// save the marble with the new owner
marble.OwnerOrg = buyerOrgID
updatedMarbleJSON, _ := json.Marshal(marble)
err := ctx.GetStub().PutState(marble.ID, updatedMarbleJSON)
if err != nil {
return fmt.Errorf("failed to write marble for buyer: %s", err.Error())
}
// Change the endorsement policy to the new owner
err = setMarbleStateBasedEndorsement(ctx, marble.ID, buyerOrgID)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for new owner: %s", err.Error())
}
// Transfer the private properties (delete from seller collection, create in buyer collection)
collectionSeller := "_implicit_org_" + clientOrgID
err = ctx.GetStub().DelPrivateData(collectionSeller, marble.ID)
if err != nil {
return fmt.Errorf("failed to delete Marble private details from seller: %s", err.Error())
}
collectionBuyer := "_implicit_org_" + buyerOrgID
err = ctx.GetStub().PutPrivateData(collectionBuyer, marble.ID, immutablePropertiesJSON)
if err != nil {
return fmt.Errorf("failed to put Marble private properties for buyer: %s", err.Error())
}
// TODO delete the price records for buyer and seller
// TODO add a state record for a 'receipt' in both buyer and seller private data collection to record the sales price and date
return nil
}
// getClientOrgID gets the client org ID.
// The client org ID can optionally be verified against the peer org ID, to ensure that a client from another org doesn't attempt to read or write private data from this peer.
// The only exception in this scenario is for TransferMarble, since the current owner needs to get an endorsement from the buyer's peer.
func getClientOrgID(ctx contractapi.TransactionContextInterface, verifyClientOrgMatchesPeerOrg bool) (string, error) {
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return "", fmt.Errorf("failed getting client's orgID: %s", err.Error())
}
if verifyClientOrgMatchesPeerOrg {
peerOrgID, err := shim.GetMSPID()
if err != nil {
return "", fmt.Errorf("failed getting peer's orgID: %s", err.Error())
}
if clientOrgID != peerOrgID {
return "", fmt.Errorf("client from org %s is not authorized to read or write private data from an org %s peer", clientOrgID, peerOrgID)
}
}
return clientOrgID, nil
}
// setMarbleStateBasedEndorsement adds an endorsement policy to a marble so that only a peer from an owning org can update or transfer the marble.
func setMarbleStateBasedEndorsement(ctx contractapi.TransactionContextInterface, marbleID string, orgToEndorse string) error {
endorsementPolicy, err := statebased.NewStateEP(nil)
err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgToEndorse)
if err != nil {
return fmt.Errorf("failed to add org to endorsement policy: %s", err.Error())
}
epBytes, err := endorsementPolicy.Policy()
if err != nil {
return fmt.Errorf("failed to create endorsement policy bytes from org: %s", err.Error())
}
err = ctx.GetStub().SetStateValidationParameter(marbleID, epBytes)
if err != nil {
return fmt.Errorf("failed to set validation parameter on marble: %s", err.Error())
}
return nil
}
func main() {
chaincode, err := contractapi.NewChaincode(new(SmartContract))
if err != nil {
fmt.Printf("Error create transfer marble chaincode: %s", err.Error())
return
}
if err := chaincode.Start(); err != nil {
fmt.Printf("Error starting marble chaincode: %s", err.Error())
}
}

View file

@ -0,0 +1,121 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package main
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// QueryMarble returns the public marble data
func (s *SmartContract) QueryMarble(ctx contractapi.TransactionContextInterface, marbleID string) (*Marble, error) {
// since only public data is accessed in this function, no access control is required
marbleJSON, err := ctx.GetStub().GetState(marbleID)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %s", err.Error())
}
if marbleJSON == nil {
return nil, fmt.Errorf("%s does not exist", marbleID)
}
marble := new(Marble)
_ = json.Unmarshal(marbleJSON, marble)
return marble, nil
}
// QueryMarblePrivateImmutableProperties returns the immutable marble properties from owner's private data collection
func (s *SmartContract) QueryMarblePrivateImmutableProperties(ctx contractapi.TransactionContextInterface, marbleID string) (string, error) {
// Get client org id and verify it matches peer org id.
// In this scenario, client is only authorized to read/write private data from its own peer.
clientOrgID, err := getClientOrgID(ctx, true)
if err != nil {
return "", fmt.Errorf("failed to get verified OrgID: %s", err.Error())
}
collection := "_implicit_org_" + clientOrgID
immutableProperties, err := ctx.GetStub().GetPrivateData(collection, marbleID)
if err != nil {
return "", fmt.Errorf("failed to read marble private properties from client org's collection: %s", err.Error())
}
if immutableProperties == nil {
return "", fmt.Errorf("marble private details does not exist in client org's collection: %s", marbleID)
}
return string(immutableProperties), nil
}
// QueryMarbleSalesPrice returns the sales price as an integer
func (s *SmartContract) QueryMarbleSalesPrice(ctx contractapi.TransactionContextInterface, marbleID string) (string, error) {
return queryMarblePrice(ctx, marbleID, typeMarbleForSale)
}
// QueryMarbleBidPrice returns the bid price as an integer
func (s *SmartContract) QueryMarbleBidPrice(ctx contractapi.TransactionContextInterface, marbleID string) (string, error) {
return queryMarblePrice(ctx, marbleID, typeMarbleBid)
}
// queryMarblePrice gets the bid or ask price from caller's implicit private data collection
func queryMarblePrice(ctx contractapi.TransactionContextInterface, marbleID string, priceType string) (string, error) {
// Get client org id and verify it matches peer org id.
// In this scenario, client is only authorized to read/write private data from its own peer.
clientOrgID, err := getClientOrgID(ctx, true)
if err != nil {
return "", fmt.Errorf("failed to get verified OrgID: %s", err.Error())
}
collection := "_implicit_org_" + clientOrgID
marblePriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{marbleID})
if err != nil {
return "", fmt.Errorf("failed to create composite key: %s", err.Error())
}
marblePriceJSON, err := ctx.GetStub().GetPrivateData(collection, marblePriceKey)
if err != nil {
return "", fmt.Errorf("failed to read marble price from implicit private data collection: %s", err.Error())
}
if marblePriceJSON == nil {
return "", fmt.Errorf("marble price does not exist: %s", marbleID)
}
return string(marblePriceJSON), nil
}
// TODO add a query to get all of an organization's proposed sales
// Use GetPrivateDataByPartialCompositeKey to find all keys starting with typeMarbleForSale
// hint: see sample at https://github.com/hyperledger/fabric-samples/blob/master/chaincode/marbles02/go/marbles_chaincode.go#L458
// TODO add a query to get all of an organization's proposed buys
// Use GetPrivateDataByPartialCompositeKey to find all keys starting with typeMarbleBid
// TODO add a JSON index and query to return all of an organization's marbles larger than a certain size (only works when using CouchDB state database)
// hint: see sample index at https://github.com/hyperledger/fabric-samples/blob/master/chaincode/marbles02/go/META-INF/statedb/couchdb/indexes/indexOwner.json
// hint: see sample query at https://github.com/hyperledger/fabric-samples/blob/master/chaincode/marbles02/go/marbles_chaincode.go#L515
// TODO add a history query so that users can see the chain of custody for a marble since issuance
// hint: see sample at https://github.com/hyperledger/fabric-samples/blob/master/chaincode/marbles02/go/marbles_chaincode.go#L692