diff --git a/chaincode/marbles_transfer/README.md b/chaincode/marbles_transfer/README.md index a5f57f25..dae90f6f 100644 --- a/chaincode/marbles_transfer/README.md +++ b/chaincode/marbles_transfer/README.md @@ -180,12 +180,12 @@ export MARBLE_PROPERTIES=$(echo -n "{\"object_type\":\"marble_properties\",\"mar ``` 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\"}" +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":"IssueAsset","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"]}' +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":"GetAssetPrivateProperties","Args":["marble1"]}' ``` When successful, the command will return the following result: @@ -195,7 +195,7 @@ When successful, the command will return the following result: 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"]}' +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":"GetAsset","Args":["marble1"]}' ``` The command will return the record that the marble1 is owned by Org1: ``` @@ -207,7 +207,7 @@ peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.exa ``` 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"]}' +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":"GetAsset","Args":["marble1"]}' ``` We can now see that the marble is for sale: ``` @@ -221,7 +221,7 @@ We can now see that the marble is for sale: 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"]}' +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":"GetAsset","Args":["marble1"]}' ``` From this query, Org2 learns that marble1 is for sale: ``` @@ -251,7 +251,7 @@ peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.exa 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"]}' +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":"GetAssetSalesPrice","Args":["marble1"]}' ``` ## Agree to buy as Org2 @@ -263,7 +263,7 @@ peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.exa ``` 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"]}' +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":"GetAssetBidPrice","Args":["marble1"]}' ``` ![Org1 and Org2 agree on transfer](images/transfer_marbles_2.png) @@ -278,7 +278,7 @@ After both organizations have agreed to their price, Org1 can attempt to transfe 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 +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":"TransferAsset","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: ``` @@ -293,13 +293,13 @@ peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.exa 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 +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":"TransferAsset","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"]}' +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":"GetAsset","Args":["marble1"]}' ``` The record now lists Org2 as the Marble owner: @@ -314,7 +314,7 @@ The record now lists Org2 as the Marble owner: 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"]}' +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":"GetAssetPrivateProperties","Args":["marble1"]}' ``` Org2 can now update the marble public description: @@ -324,7 +324,7 @@ peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.exa 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"]}' +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":"GetAsset","Args":["marble1"]}' ``` ## Clean up diff --git a/chaincode/marbles_transfer/marbles_transfer.go b/chaincode/marbles_transfer/marbles_transfer.go index eaafec4b..88d23531 100644 --- a/chaincode/marbles_transfer/marbles_transfer.go +++ b/chaincode/marbles_transfer/marbles_transfer.go @@ -47,8 +47,8 @@ type Marble struct { 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 { +// IssueAsset creates a marble and sets it as owned by the client's org +func (s *SmartContract) IssueAsset(ctx contractapi.TransactionContextInterface, marbleID string) error { transMap, err := ctx.GetStub().GetTransient() if err != nil { @@ -113,7 +113,7 @@ func (s *SmartContract) ChangePublicDescription(ctx contractapi.TransactionConte return fmt.Errorf("failed to get verified OrgID: %s", err.Error()) } - marble, err := s.QueryMarble(ctx, marbleID) + marble, err := s.GetAsset(ctx, marbleID) if err != nil { return fmt.Errorf("failed to get marble: %s", err.Error()) } @@ -135,17 +135,31 @@ func (s *SmartContract) ChangePublicDescription(ctx contractapi.TransactionConte // AgreeToSell adds seller's asking price to seller's implicit private data collection func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface, marbleID string) error { + // Query marble and verify that this clientOrgId actually owns the marble. + marble, err := s.GetAsset(ctx, marbleID) + if err != nil { + return err + } - return agreeToMarblePrice(ctx, marbleID, typeMarbleForSale) + clientOrgID, err := getClientOrgID(ctx, true) + if err != nil { + return fmt.Errorf("failed to get verified OrgID: %s", err.Error()) + } + + if clientOrgID != marble.OwnerOrg { + return fmt.Errorf("a client from %s cannot sell a marble owned by %s", clientOrgID, marble.OwnerOrg) + } + + return agreeToPrice(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) + return agreeToPrice(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 { +// agreeToPrice adds a bid or ask price to caller's implicit private data collection +func agreeToPrice(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. @@ -154,9 +168,6 @@ func agreeToMarblePrice(ctx contractapi.TransactionContextInterface, marbleID st 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 { @@ -193,9 +204,9 @@ func agreeToMarblePrice(ctx contractapi.TransactionContextInterface, marbleID st // 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 { +// TransferAsset checks transfer conditions and then transfers marble state to buyer. +// TransferAsset can only be called by current owner +func (s *SmartContract) TransferAsset(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 @@ -219,7 +230,7 @@ func (s *SmartContract) TransferMarble(ctx contractapi.TransactionContextInterfa return fmt.Errorf("marble_price key not found in the transient map") } - marble, err := s.QueryMarble(ctx, marbleID) + marble, err := s.GetAsset(ctx, marbleID) if err != nil { return fmt.Errorf("failed to get marble: %s", err.Error()) } @@ -360,27 +371,37 @@ func transferMarbleState(ctx contractapi.TransactionContextInterface, marble *Ma // 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) { +func getClientOrgID(ctx contractapi.TransactionContextInterface, verifyOrg 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 verifyOrg { + err = verifyClientOrgMatchesPeerOrg(clientOrgID) 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 "", err } } return clientOrgID, nil } +// verify client org id and matches peer org id. +func verifyClientOrgMatchesPeerOrg(clientOrgID string) error { + 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 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 { @@ -402,6 +423,21 @@ func setMarbleStateBasedEndorsement(ctx contractapi.TransactionContextInterface, return nil } +func getClientImplicitCollectionName(ctx contractapi.TransactionContextInterface) (string, error) { + clientOrgID, err := getClientOrgID(ctx, true) + if err != nil { + return "", fmt.Errorf("failed to get verified OrgID: %s", err.Error()) + } + + err = verifyClientOrgMatchesPeerOrg(clientOrgID) + if err != nil { + return "", err + } + + collection := "_implicit_org_" + clientOrgID + return collection, nil +} + func main() { chaincode, err := contractapi.NewChaincode(new(SmartContract)) diff --git a/chaincode/marbles_transfer/marbles_transfer_queries.go b/chaincode/marbles_transfer/marbles_transfer_queries.go index 27714bc5..3b5e1d0e 100644 --- a/chaincode/marbles_transfer/marbles_transfer_queries.go +++ b/chaincode/marbles_transfer/marbles_transfer_queries.go @@ -22,12 +22,26 @@ package main import ( "encoding/json" "fmt" + "time" "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) { +// QueryResult structure used for handling result of query +type QueryResult struct { + Record *Marble + TxId string `json:"txId"` + Timestamp time.Time `json:"timestamp"` +} + +type Agreement struct { + ID string `json:"marble_id"` + Price int `json:"price"` + TradeID string `json:"trade_id"` +} + +// GetAsset returns the public marble data +func (s *SmartContract) GetAsset(ctx contractapi.TransactionContextInterface, marbleID string) (*Marble, error) { // since only public data is accessed in this function, no access control is required @@ -45,18 +59,16 @@ func (s *SmartContract) QueryMarble(ctx contractapi.TransactionContextInterface, 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) { +// GetAssetPrivateProperties returns the immutable marble properties from owner's private data collection +func (s *SmartContract) GetAssetPrivateProperties(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) + collection, err := getClientImplicitCollectionName(ctx) if err != nil { - return "", fmt.Errorf("failed to get verified OrgID: %s", err.Error()) + return "", err } - 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()) @@ -68,28 +80,24 @@ func (s *SmartContract) QueryMarblePrivateImmutableProperties(ctx contractapi.Tr 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) +// GetAssetSalesPrice returns the sales price as an integer +func (s *SmartContract) GetAssetSalesPrice(ctx contractapi.TransactionContextInterface, marbleID string) (string, error) { + return getAssetPrice(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) +// GetAssetBidPrice returns the bid price as an integer +func (s *SmartContract) GetAssetBidPrice(ctx contractapi.TransactionContextInterface, marbleID string) (string, error) { + return getAssetPrice(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) { +// getAssetPrice gets the bid or ask price from caller's implicit private data collection +func getAssetPrice(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) + collection, err := getClientImplicitCollectionName(ctx) if err != nil { - return "", fmt.Errorf("failed to get verified OrgID: %s", err.Error()) + return "", err } - 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()) @@ -106,16 +114,82 @@ func queryMarblePrice(ctx contractapi.TransactionContextInterface, marbleID stri 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 +// QueryAssetSaleAgreements returns all of an organization's proposed sales +func (s *SmartContract) QueryAssetSaleAgreements(ctx contractapi.TransactionContextInterface) ([]Agreement, error) { + return queryAgreementsByType(ctx, typeMarbleForSale) +} -// TODO add a query to get all of an organization's proposed buys -// Use GetPrivateDataByPartialCompositeKey to find all keys starting with typeMarbleBid +// QueryAssetBuyAgreements returns all of an organization's proposed buys +func (s *SmartContract) QueryAssetBuyAgreements(ctx contractapi.TransactionContextInterface) ([]Agreement, error) { + return queryAgreementsByType(ctx, typeMarbleBid) +} + +func queryAgreementsByType(ctx contractapi.TransactionContextInterface, agreeType string) ([]Agreement, error) { + collection, err := getClientImplicitCollectionName(ctx) + if err != nil { + return nil, err + } + + // Query for any object type starting with `agreeType` + agreementsIterator, err := ctx.GetStub().GetPrivateDataByPartialCompositeKey(collection, agreeType, []string{}) + if err != nil { + return nil, fmt.Errorf("failed to read from private data collection: %s", err.Error()) + } + defer agreementsIterator.Close() + + agreements := []Agreement{} + + for agreementsIterator.HasNext() { + resp, err := agreementsIterator.Next() + if err != nil { + return nil, err + } + + newAgree := new(Agreement) + err = json.Unmarshal(resp.Value, newAgree) + if err != nil { + return nil, err + } + + agreements = append(agreements, *newAgree) + } + + return agreements, nil +} // 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 +// QueryAssetHistory returns the chain of custody for a marble since issuance +func (s *SmartContract) QueryAssetHistory(ctx contractapi.TransactionContextInterface, marbleID string) ([]QueryResult, error) { + resultsIterator, err := ctx.GetStub().GetHistoryForKey(marbleID) + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + records := []QueryResult{} + + for resultsIterator.HasNext() { + response, err := resultsIterator.Next() + if err != nil { + return nil, err + } + + marble := new(Marble) + err = json.Unmarshal(response.Value, marble) + if err != nil { + return nil, err + } + + record := QueryResult{ + TxId: response.TxId, + Timestamp: time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)), + Record: marble, + } + records = append(records, record) + } + + return records, nil +}