mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
624 lines
21 KiB
Go
624 lines
21 KiB
Go
/*
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
|
"github.com/hyperledger/fabric-chaincode-go/pkg/statebased"
|
|
"github.com/hyperledger/fabric-chaincode-go/shim"
|
|
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
|
)
|
|
|
|
const (
|
|
typeAssetForSale = "S"
|
|
typeAssetBid = "B"
|
|
typeAssetSaleReceipt = "SR"
|
|
typeAssetBuyReceipt = "BR"
|
|
)
|
|
|
|
type SmartContract struct {
|
|
contractapi.Contract
|
|
}
|
|
|
|
// Asset struct and properties must be exported (start with capitals) to work with contract api metadata
|
|
type Asset struct {
|
|
ObjectType string `json:"objectType"` // ObjectType is used to distinguish different object types in the same chaincode namespace
|
|
ID string `json:"assetID"`
|
|
OwnerOrg string `json:"ownerOrg"`
|
|
PublicDescription string `json:"publicDescription"`
|
|
}
|
|
|
|
type receipt struct {
|
|
price int
|
|
timestamp time.Time
|
|
}
|
|
|
|
// CreateAsset creates an asset, sets it as owned by the client's org and returns its id
|
|
// the id of the asset corresponds to the hash of the properties of the asset that are passed by transiet field
|
|
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, publicDescription string) (string, error) {
|
|
transientMap, err := ctx.GetStub().GetTransient()
|
|
if err != nil {
|
|
return "", fmt.Errorf("error getting transient: %v", err)
|
|
}
|
|
|
|
// Asset properties must be retrieved from the transient field as they are private
|
|
immutablePropertiesJSON, ok := transientMap["asset_properties"]
|
|
if !ok {
|
|
return "", fmt.Errorf("asset_properties key not found in the transient map")
|
|
}
|
|
|
|
hash := sha256.New()
|
|
hash.Write(immutablePropertiesJSON)
|
|
assetID := hex.EncodeToString(hash.Sum(nil))
|
|
|
|
// 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)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
asset := Asset{
|
|
ObjectType: "asset",
|
|
ID: assetID,
|
|
OwnerOrg: clientOrgID,
|
|
PublicDescription: publicDescription,
|
|
}
|
|
assetBytes, err := json.Marshal(asset)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create asset JSON: %v", err)
|
|
}
|
|
|
|
err = ctx.GetStub().PutState(assetID, assetBytes)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to put asset in public data: %v", err)
|
|
}
|
|
|
|
// Set the endorsement policy such that an owner org peer is required to endorse future updates
|
|
endorsingOrgs := []string{clientOrgID}
|
|
err = setAssetStateBasedEndorsement(ctx, asset.ID, endorsingOrgs)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed setting state based endorsement for buyer and seller: %v", err)
|
|
}
|
|
|
|
// Persist private immutable asset properties to owner's private data collection
|
|
collection := buildCollectionName(clientOrgID)
|
|
err = ctx.GetStub().PutPrivateData(collection, assetID, immutablePropertiesJSON)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to put Asset private details: %v", err)
|
|
}
|
|
|
|
return assetID, nil
|
|
}
|
|
|
|
// ChangePublicDescription updates the assets public description. Only the current owner can update the public description
|
|
func (s *SmartContract) ChangePublicDescription(ctx contractapi.TransactionContextInterface, assetID string, newDescription string) error {
|
|
// No need to check client org id matches peer org id, rely on the asset ownership check instead.
|
|
clientOrgID, err := getClientOrgID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
asset, err := s.ReadAsset(ctx, assetID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get asset: %v", err)
|
|
}
|
|
|
|
// Auth check to ensure that client's org actually owns the asset
|
|
if clientOrgID != asset.OwnerOrg {
|
|
return fmt.Errorf("a client from %s cannot update the description of a asset owned by %s", clientOrgID, asset.OwnerOrg)
|
|
}
|
|
|
|
asset.PublicDescription = newDescription
|
|
updatedAssetJSON, err := json.Marshal(asset)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal asset: %v", err)
|
|
}
|
|
|
|
return ctx.GetStub().PutState(assetID, updatedAssetJSON)
|
|
}
|
|
|
|
// AgreeToSell adds seller's asking price to seller's implicit private data collection and requires to specify the next possible buyer
|
|
// Set the endorsement policy such that seller org and passed target buyer org peers are both required to endorse the tranfer
|
|
func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface, assetID string, buyerOrgID string) error {
|
|
asset, err := s.ReadAsset(ctx, assetID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
clientOrgID, err := getClientOrgID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Verify that this clientOrgId actually owns the asset.
|
|
if clientOrgID != asset.OwnerOrg {
|
|
return fmt.Errorf("a client from %s cannot sell an asset owned by %s", clientOrgID, asset.OwnerOrg)
|
|
}
|
|
|
|
// Set the endorsement policy such that owner org and seller org peers are both required to endorse the tranfer
|
|
endorsingOrgs := []string{clientOrgID, buyerOrgID}
|
|
err = setAssetStateBasedEndorsement(ctx, asset.ID, endorsingOrgs)
|
|
if err != nil {
|
|
return fmt.Errorf("failed setting state based endorsement for buyer and future seller: %v", err)
|
|
}
|
|
|
|
return agreeToPrice(ctx, assetID, typeAssetForSale)
|
|
}
|
|
|
|
// AgreeToBuy adds buyer's bid price and asset properties to buyer's implicit private data collection
|
|
func (s *SmartContract) AgreeToBuy(ctx contractapi.TransactionContextInterface, assetID string) error {
|
|
transientMap, err := ctx.GetStub().GetTransient()
|
|
if err != nil {
|
|
return fmt.Errorf("error getting transient: %v", err)
|
|
}
|
|
|
|
clientOrgID, err := getClientOrgID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Asset properties must be retrieved from the transient field as they are private
|
|
immutablePropertiesJSON, ok := transientMap["asset_properties"]
|
|
if !ok {
|
|
return fmt.Errorf("asset_properties key not found in the transient map")
|
|
}
|
|
|
|
// Persist private immutable asset properties to seller's private data collection
|
|
collection := buildCollectionName(clientOrgID)
|
|
err = ctx.GetStub().PutPrivateData(collection, assetID, immutablePropertiesJSON)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to put Asset private details: %v", err)
|
|
}
|
|
|
|
return agreeToPrice(ctx, assetID, typeAssetBid)
|
|
}
|
|
|
|
// agreeToPrice adds a bid or ask price to caller's implicit private data collection
|
|
func agreeToPrice(ctx contractapi.TransactionContextInterface, assetID string, priceType string) error {
|
|
// In this scenario, both buyer and seller are authoried to read/write private about transfer after seller agrees to sell.
|
|
clientOrgID, err := getClientOrgID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
transMap, err := ctx.GetStub().GetTransient()
|
|
if err != nil {
|
|
return fmt.Errorf("error getting transient: %v", err)
|
|
}
|
|
|
|
// Asset price must be retrieved from the transient field as they are private
|
|
price, ok := transMap["asset_price"]
|
|
if !ok {
|
|
return fmt.Errorf("asset_price key not found in the transient map")
|
|
}
|
|
|
|
collection := buildCollectionName(clientOrgID)
|
|
|
|
// Persist the agreed to price in a collection sub-namespace based on priceType key prefix,
|
|
// to avoid collisions between private asset properties, sell price, and buy price
|
|
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{assetID})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create composite key: %v", err)
|
|
}
|
|
|
|
// The Price hash will be verified later, therefore always pass and persist price bytes as is,
|
|
// so that there is no risk of nondeterministic marshaling.
|
|
err = ctx.GetStub().PutPrivateData(collection, assetPriceKey, price)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to put asset bid: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// VerifyAssetProperties Allows a buyer to validate the properties of
|
|
// an asset against the owner's implicit private data collection
|
|
// and verifies that the asset properties never changed from the origin of the asset by checking their hash against the assetID
|
|
func (s *SmartContract) VerifyAssetProperties(ctx contractapi.TransactionContextInterface, assetID string) (bool, error) {
|
|
transMap, err := ctx.GetStub().GetTransient()
|
|
if err != nil {
|
|
return false, fmt.Errorf("error getting transient: %v", err)
|
|
}
|
|
|
|
/// Asset properties must be retrieved from the transient field as they are private
|
|
immutablePropertiesJSON, ok := transMap["asset_properties"]
|
|
if !ok {
|
|
return false, fmt.Errorf("asset_properties key not found in the transient map")
|
|
}
|
|
|
|
asset, err := s.ReadAsset(ctx, assetID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to get asset: %v", err)
|
|
}
|
|
|
|
collectionOwner := buildCollectionName(asset.OwnerOrg)
|
|
immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionOwner, assetID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err)
|
|
}
|
|
if immutablePropertiesOnChainHash == nil {
|
|
return false, fmt.Errorf("asset private properties hash does not exist: %s", assetID)
|
|
}
|
|
|
|
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 false, fmt.Errorf("hash %x for passed immutable properties %s does not match on-chain hash %x",
|
|
calculatedPropertiesHash,
|
|
immutablePropertiesJSON,
|
|
immutablePropertiesOnChainHash,
|
|
)
|
|
}
|
|
|
|
// verify that the hash of the passed immutable properties and on chain hash matches the assetID
|
|
if !(hex.EncodeToString(immutablePropertiesOnChainHash) == assetID) {
|
|
return false, fmt.Errorf("hash %x for passed immutable properties %s does match on-chain hash %x but do not match assetID %s: asset was altered from its initial form",
|
|
calculatedPropertiesHash,
|
|
immutablePropertiesJSON,
|
|
immutablePropertiesOnChainHash,
|
|
assetID)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// TransferAsset checks transfer conditions and then transfers asset state to buyer.
|
|
// TransferAsset can only be called by current owner
|
|
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, assetID string, buyerOrgID string) error {
|
|
clientOrgID, err := getClientOrgID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
transMap, err := ctx.GetStub().GetTransient()
|
|
if err != nil {
|
|
return fmt.Errorf("error getting transient data: %v", err)
|
|
}
|
|
|
|
priceJSON, ok := transMap["asset_price"]
|
|
if !ok {
|
|
return fmt.Errorf("asset_price key not found in the transient map")
|
|
}
|
|
|
|
var agreement Agreement
|
|
err = json.Unmarshal(priceJSON, &agreement)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unmarshal price JSON: %v", err)
|
|
}
|
|
|
|
asset, err := s.ReadAsset(ctx, assetID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get asset: %v", err)
|
|
}
|
|
|
|
err = verifyTransferConditions(ctx, asset, clientOrgID, buyerOrgID, priceJSON)
|
|
if err != nil {
|
|
return fmt.Errorf("failed transfer verification: %v", err)
|
|
}
|
|
|
|
err = transferAssetState(ctx, asset, clientOrgID, buyerOrgID, agreement.Price)
|
|
if err != nil {
|
|
return fmt.Errorf("failed asset transfer: %v", err)
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// verifyTransferConditions checks that client org currently owns asset and that both parties have agreed on price
|
|
func verifyTransferConditions(ctx contractapi.TransactionContextInterface,
|
|
asset *Asset,
|
|
clientOrgID string,
|
|
buyerOrgID string,
|
|
priceJSON []byte) error {
|
|
|
|
// CHECK1: Auth check to ensure that client's org actually owns the asset
|
|
|
|
if clientOrgID != asset.OwnerOrg {
|
|
return fmt.Errorf("a client from %s cannot transfer a asset owned by %s", clientOrgID, asset.OwnerOrg)
|
|
}
|
|
|
|
// CHECK2: Verify that both buyers and seller on-chain asset defintion hash matches
|
|
|
|
collectionSeller := buildCollectionName(clientOrgID)
|
|
collectionBuyer := buildCollectionName(buyerOrgID)
|
|
sellerPropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, asset.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err)
|
|
}
|
|
if sellerPropertiesOnChainHash == nil {
|
|
return fmt.Errorf("asset private properties hash does not exist: %s", asset.ID)
|
|
}
|
|
buyerPropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionBuyer, asset.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err)
|
|
}
|
|
if buyerPropertiesOnChainHash == nil {
|
|
return fmt.Errorf("asset private properties hash does not exist: %s", asset.ID)
|
|
}
|
|
|
|
// verify that the hash of the passed immutable properties matches the on-chain hash
|
|
if !bytes.Equal(sellerPropertiesOnChainHash, buyerPropertiesOnChainHash) {
|
|
return fmt.Errorf("on chain hash of seller %x does not match on-chain hash of buyer %x",
|
|
sellerPropertiesOnChainHash,
|
|
buyerPropertiesOnChainHash,
|
|
)
|
|
}
|
|
|
|
// CHECK3: Verify that seller and buyer agreed on the same price
|
|
|
|
// Get sellers asking price
|
|
assetForSaleKey, err := ctx.GetStub().CreateCompositeKey(typeAssetForSale, []string{asset.ID})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create composite key: %v", err)
|
|
}
|
|
sellerPriceHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, assetForSaleKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get seller price hash: %v", err)
|
|
}
|
|
if sellerPriceHash == nil {
|
|
return fmt.Errorf("seller price for %s does not exist", asset.ID)
|
|
}
|
|
|
|
// Get buyers bid price
|
|
assetBidKey, err := ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create composite key: %v", err)
|
|
}
|
|
buyerPriceHash, err := ctx.GetStub().GetPrivateDataHash(collectionBuyer, assetBidKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get buyer price hash: %v", err)
|
|
}
|
|
if buyerPriceHash == nil {
|
|
return fmt.Errorf("buyer price for %s does not exist", asset.ID)
|
|
}
|
|
|
|
hash := sha256.New()
|
|
hash.Write(priceJSON)
|
|
calculatedPriceHash := hash.Sum(nil)
|
|
|
|
// Verify that the hash of the passed price matches the on-chain sellers 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,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// transferAssetState performs the public and private state updates for the transferred asset
|
|
// changes the endorsement for the transferred asset sbe to the new owner org
|
|
func transferAssetState(ctx contractapi.TransactionContextInterface, asset *Asset, clientOrgID string, buyerOrgID string, price int) error {
|
|
asset.OwnerOrg = buyerOrgID
|
|
updatedAsset, err := json.Marshal(asset)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ctx.GetStub().PutState(asset.ID, updatedAsset)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write asset for buyer: %v", err)
|
|
}
|
|
|
|
// Changes the endorsement policy to the new owner org
|
|
endorsingOrgs := []string{buyerOrgID}
|
|
err = setAssetStateBasedEndorsement(ctx, asset.ID, endorsingOrgs)
|
|
if err != nil {
|
|
return fmt.Errorf("failed setting state based endorsement for new owner: %v", err)
|
|
}
|
|
|
|
// Transfer the private properties (delete from seller collection, create in buyer collection)
|
|
collectionSeller := buildCollectionName(clientOrgID)
|
|
err = ctx.GetStub().DelPrivateData(collectionSeller, asset.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete Asset private details from seller: %v", err)
|
|
}
|
|
|
|
collectionBuyer := buildCollectionName(buyerOrgID)
|
|
|
|
// Delete the price records for seller
|
|
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(typeAssetForSale, []string{asset.ID})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create composite key for seller: %v", err)
|
|
}
|
|
|
|
err = ctx.GetStub().DelPrivateData(collectionSeller, assetPriceKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete asset price from implicit private data collection for seller: %v", err)
|
|
}
|
|
|
|
// Delete the price records for buyer
|
|
assetPriceKey, err = ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create composite key for buyer: %v", err)
|
|
}
|
|
|
|
err = ctx.GetStub().DelPrivateData(collectionBuyer, assetPriceKey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete asset price from implicit private data collection for buyer: %v", err)
|
|
}
|
|
|
|
// Keep record for a 'receipt' in both buyers and sellers private data collection to record the sale price and date.
|
|
// Persist the agreed to price in a collection sub-namespace based on receipt key prefix.
|
|
receiptBuyKey, err := ctx.GetStub().CreateCompositeKey(typeAssetBuyReceipt, []string{asset.ID, ctx.GetStub().GetTxID()})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create composite key for receipt: %v", err)
|
|
}
|
|
|
|
txTimestamp, err := ctx.GetStub().GetTxTimestamp()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create timestamp for receipt: %v", err)
|
|
}
|
|
|
|
timestamp, err := ptypes.Timestamp(txTimestamp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
assetReceipt := receipt{
|
|
price: price,
|
|
timestamp: timestamp,
|
|
}
|
|
receipt, err := json.Marshal(assetReceipt)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal receipt: %v", err)
|
|
}
|
|
|
|
err = ctx.GetStub().PutPrivateData(collectionBuyer, receiptBuyKey, receipt)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to put private asset receipt for buyer: %v", err)
|
|
}
|
|
|
|
receiptSaleKey, err := ctx.GetStub().CreateCompositeKey(typeAssetSaleReceipt, []string{ctx.GetStub().GetTxID(), asset.ID})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create composite key for receipt: %v", err)
|
|
}
|
|
|
|
err = ctx.GetStub().PutPrivateData(collectionSeller, receiptSaleKey, receipt)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to put private asset receipt for seller: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getClientOrgID gets the client org ID.
|
|
func getClientOrgID(ctx contractapi.TransactionContextInterface) (string, error) {
|
|
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed getting client's orgID: %v", err)
|
|
}
|
|
|
|
return clientOrgID, nil
|
|
}
|
|
|
|
// verifyClientOrgMatchesPeerOrg checks the client org id matches the peer org id.
|
|
func verifyClientOrgMatchesPeerOrg(clientOrgID string) error {
|
|
peerOrgID, err := shim.GetMSPID()
|
|
if err != nil {
|
|
return fmt.Errorf("failed getting peer's orgID: %v", err)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// setAssetStateBasedEndorsement adds an endorsement policy to an asset so that the passed orgs need to agree upon transfer
|
|
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetID string, orgsToEndorse []string) error {
|
|
endorsementPolicy, err := statebased.NewStateEP(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgsToEndorse...)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to add org to endorsement policy: %v", err)
|
|
}
|
|
policy, err := endorsementPolicy.Policy()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create endorsement policy bytes from org: %v", err)
|
|
}
|
|
err = ctx.GetStub().SetStateValidationParameter(assetID, policy)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to set validation parameter on asset: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAssetHash Allows a buyer to validate the properties of
|
|
// an asset against the asset Id and return the hash
|
|
func (s *SmartContract) GetAssetHashId(ctx contractapi.TransactionContextInterface) (string, error) {
|
|
transientMap, err := ctx.GetStub().GetTransient()
|
|
if err != nil {
|
|
return "", fmt.Errorf("error getting transient: %v", err)
|
|
}
|
|
|
|
// Asset properties must be retrieved from the transient field as they are private
|
|
propertiesJSON, ok := transientMap["asset_properties"]
|
|
if !ok {
|
|
return "", fmt.Errorf("asset_properties key not found in the transient map")
|
|
}
|
|
|
|
hash := sha256.New()
|
|
hash.Write(propertiesJSON)
|
|
assetID := hex.EncodeToString(hash.Sum(nil))
|
|
|
|
asset, err := s.ReadAsset(ctx, assetID)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get asset: %v, asset properies provided do not represent any on chain asset", err)
|
|
}
|
|
if asset.ID != assetID {
|
|
return "", fmt.Errorf("Asset properies provided do not correpond to any on chain asset")
|
|
}
|
|
return asset.ID, nil
|
|
}
|
|
|
|
func buildCollectionName(clientOrgID string) string {
|
|
return fmt.Sprintf("_implicit_org_%s", clientOrgID)
|
|
}
|
|
|
|
func getClientImplicitCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {
|
|
clientOrgID, err := getClientOrgID(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return buildCollectionName(clientOrgID), nil
|
|
}
|
|
|
|
func main() {
|
|
chaincode, err := contractapi.NewChaincode(new(SmartContract))
|
|
if err != nil {
|
|
log.Panicf("Error create transfer asset chaincode: %v", err)
|
|
}
|
|
|
|
if err := chaincode.Start(); err != nil {
|
|
log.Panicf("Error starting asset chaincode: %v", err)
|
|
}
|
|
}
|