fabric-samples/asset-transfer-secured-agreement/chaincode-go/asset_transfer_queries.go
Dave Enyeart f32f77b129
Update secured asset transfer sample (#781)
A recent commit added the potential buyer to an asset's state based endorsement policy.
That change was problematic because if the transfer fell through, the buyer lost control of the asset,
in that they could no longer update the asset or change the sell price or sell to somebody else.

The asset state based endorsement policy is now based on the seller only, and we document
that additional parties could be added such as a trusted third party (although no
such party exists in test network at this time).

This commit also re-adds some necessary verifications, and make other minor edits and
comments to help users understand the sample.

Signed-off-by: David Enyeart <enyeart@us.ibm.com>
2022-07-13 14:09:14 -04:00

177 lines
5.2 KiB
Go

/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"encoding/json"
"fmt"
"time"
"github.com/golang/protobuf/ptypes"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// QueryResult structure used for handling result of query
type QueryResult struct {
Record *Asset
TxId string `json:"txId"`
Timestamp time.Time `json:"timestamp"`
}
type Agreement struct {
ID string `json:"asset_id"`
Price int `json:"price"`
TradeID string `json:"trade_id"`
}
// ReadAsset returns the public asset data
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) {
// Since only public data is accessed in this function, no access control is required
assetJSON, err := ctx.GetStub().GetState(assetID)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %v", err)
}
if assetJSON == nil {
return nil, fmt.Errorf("%s does not exist", assetID)
}
var asset *Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
return asset, nil
}
// GetAssetPrivateProperties returns the immutable asset properties from owner's private data collection
func (s *SmartContract) GetAssetPrivateProperties(ctx contractapi.TransactionContextInterface, assetID string) (string, error) {
collection, err := getClientImplicitCollectionNameAndVerifyClientOrg(ctx)
if err != nil {
return "", err
}
immutableProperties, err := ctx.GetStub().GetPrivateData(collection, assetID)
if err != nil {
return "", fmt.Errorf("failed to read asset private properties from client org's collection: %v", err)
}
if immutableProperties == nil {
return "", fmt.Errorf("asset private details does not exist in client org's collection: %s", assetID)
}
return string(immutableProperties), nil
}
// GetAssetSalesPrice returns the sales price
func (s *SmartContract) GetAssetSalesPrice(ctx contractapi.TransactionContextInterface, assetID string) (string, error) {
return getAssetPrice(ctx, assetID, typeAssetForSale)
}
// GetAssetBidPrice returns the bid price
func (s *SmartContract) GetAssetBidPrice(ctx contractapi.TransactionContextInterface, assetID string) (string, error) {
return getAssetPrice(ctx, assetID, typeAssetBid)
}
// getAssetPrice gets the bid or ask price from caller's implicit private data collection
func getAssetPrice(ctx contractapi.TransactionContextInterface, assetID string, priceType string) (string, error) {
collection, err := getClientImplicitCollectionNameAndVerifyClientOrg(ctx)
if err != nil {
return "", err
}
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{assetID})
if err != nil {
return "", fmt.Errorf("failed to create composite key: %v", err)
}
price, err := ctx.GetStub().GetPrivateData(collection, assetPriceKey)
if err != nil {
return "", fmt.Errorf("failed to read asset price from implicit private data collection: %v", err)
}
if price == nil {
return "", fmt.Errorf("asset price does not exist: %s", assetID)
}
return string(price), nil
}
// QueryAssetSaleAgreements returns all of an organization's proposed sales
func (s *SmartContract) QueryAssetSaleAgreements(ctx contractapi.TransactionContextInterface) ([]Agreement, error) {
return queryAgreementsByType(ctx, typeAssetForSale)
}
// QueryAssetBuyAgreements returns all of an organization's proposed bids
func (s *SmartContract) QueryAssetBuyAgreements(ctx contractapi.TransactionContextInterface) ([]Agreement, error) {
return queryAgreementsByType(ctx, typeAssetBid)
}
func queryAgreementsByType(ctx contractapi.TransactionContextInterface, agreeType string) ([]Agreement, error) {
collection, err := getClientImplicitCollectionNameAndVerifyClientOrg(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: %v", err)
}
defer agreementsIterator.Close()
var agreements []Agreement
for agreementsIterator.HasNext() {
resp, err := agreementsIterator.Next()
if err != nil {
return nil, err
}
var agreement Agreement
err = json.Unmarshal(resp.Value, &agreement)
if err != nil {
return nil, err
}
agreements = append(agreements, agreement)
}
return agreements, nil
}
// QueryAssetHistory returns the chain of custody for a asset since issuance
func (s *SmartContract) QueryAssetHistory(ctx contractapi.TransactionContextInterface, assetID string) ([]QueryResult, error) {
resultsIterator, err := ctx.GetStub().GetHistoryForKey(assetID)
if err != nil {
return nil, err
}
defer resultsIterator.Close()
var results []QueryResult
for resultsIterator.HasNext() {
response, err := resultsIterator.Next()
if err != nil {
return nil, err
}
var asset *Asset
err = json.Unmarshal(response.Value, &asset)
if err != nil {
return nil, err
}
timestamp, err := ptypes.Timestamp(response.Timestamp)
if err != nil {
return nil, err
}
record := QueryResult{
TxId: response.TxId,
Timestamp: timestamp,
Record: asset,
}
results = append(results, record)
}
return results, nil
}