diff --git a/asset-transfer-secured-agreement/chaincode-go/asset_transfer.go b/asset-transfer-secured-agreement/chaincode-go/asset_transfer.go index 7ea397ae..cce1c2cd 100644 --- a/asset-transfer-secured-agreement/chaincode-go/asset_transfer.go +++ b/asset-transfer-secured-agreement/chaincode-go/asset_transfer.go @@ -1,21 +1,6 @@ /* - * 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. - */ + SPDX-License-Identifier: Apache-2.0 +*/ package main @@ -24,8 +9,10 @@ import ( "crypto/sha256" "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" @@ -44,10 +31,10 @@ type SmartContract struct { // Asset struct and properties must be exported (start with capitals) to work with contract api metadata type Asset struct { - ObjectType string `json:"object_type"` // ObjectType is used to distinguish different object types in the same chaincode namespace - ID string `json:"asset_id"` - OwnerOrg string `json:"owner_org"` - PublicDescription string `json:"public_description"` + 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 { @@ -55,16 +42,15 @@ type receipt struct { timestamp time.Time } -// CreateAsset creates a asset and sets it as owned by the client's org +// CreateAsset creates an asset and sets it as owned by the client's org func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, assetID, publicDescription string) error { - - transMap, err := ctx.GetStub().GetTransient() + transientMap, err := ctx.GetStub().GetTransient() if err != nil { - return fmt.Errorf("Error getting transient: " + err.Error()) + return fmt.Errorf("error getting transient: %v", err) } - // Asset properties are private, therefore they get passed in transient field - immutablePropertiesJSON, ok := transMap["asset_properties"] + // 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") } @@ -73,68 +59,63 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, // 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()) + return fmt.Errorf("failed to get verified OrgID: %v", err) } - // Create and persist asset asset := Asset{ ObjectType: "asset", ID: assetID, OwnerOrg: clientOrgID, PublicDescription: publicDescription, } - - assetJSON, err := json.Marshal(asset) + assetBytes, err := json.Marshal(asset) if err != nil { - return fmt.Errorf("failed to create asset JSON: %s", err.Error()) + return fmt.Errorf("failed to create asset JSON: %v", err) } - err = ctx.GetStub().PutState(asset.ID, assetJSON) + err = ctx.GetStub().PutState(asset.ID, assetBytes) if err != nil { - return fmt.Errorf("failed to put Asset in public data: %s", err.Error()) + 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 err = setAssetStateBasedEndorsement(ctx, asset.ID, clientOrgID) if err != nil { - return fmt.Errorf("failed setting state based endorsement for owner: %s", err.Error()) + return fmt.Errorf("failed setting state based endorsement for owner: %v", err) } // Persist private immutable asset properties to owner's private data collection collection := buildCollectionName(clientOrgID) - err = ctx.GetStub().PutPrivateData(collection, asset.ID, []byte(immutablePropertiesJSON)) + err = ctx.GetStub().PutPrivateData(collection, asset.ID, immutablePropertiesJSON) if err != nil { - return fmt.Errorf("failed to put Asset private details: %s", err.Error()) + return fmt.Errorf("failed to put Asset private details: %v", err) } return nil } -// ChangePublicDescription updates the asset public description. Only the current owner can update the public description +// 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 { - - // Get client org id // No need to check client org id matches peer org id, rely on the asset ownership check instead. clientOrgID, err := getClientOrgID(ctx, false) if err != nil { - return fmt.Errorf("failed to get verified OrgID: %s", err.Error()) + return fmt.Errorf("failed to get verified OrgID: %v", err) } asset, err := s.ReadAsset(ctx, assetID) if err != nil { - return fmt.Errorf("failed to get asset: %s", err.Error()) + return fmt.Errorf("failed to get asset: %v", err) } - // auth check to ensure that client's org actually owns the asset + // 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: %s", err.Error()) + return fmt.Errorf("failed to marshal asset: %v", err) } return ctx.GetStub().PutState(assetID, updatedAssetJSON) @@ -142,7 +123,6 @@ 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, assetID string) error { - // Query asset and verify that this clientOrgId actually owns the asset. asset, err := s.ReadAsset(ctx, assetID) if err != nil { return err @@ -150,11 +130,12 @@ func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface, clientOrgID, err := getClientOrgID(ctx, true) if err != nil { - return fmt.Errorf("failed to get verified OrgID: %s", err.Error()) + return fmt.Errorf("failed to get verified OrgID: %v", err) } + // Verify that this clientOrgId actually owns the asset. if clientOrgID != asset.OwnerOrg { - return fmt.Errorf("a client from %s cannot sell a asset owned by %s", clientOrgID, asset.OwnerOrg) + return fmt.Errorf("a client from %s cannot sell an asset owned by %s", clientOrgID, asset.OwnerOrg) } return agreeToPrice(ctx, assetID, typeAssetForSale) @@ -167,23 +148,19 @@ func (s *SmartContract) AgreeToBuy(ctx contractapi.TransactionContextInterface, // agreeToPrice adds a bid or ask price to caller's implicit private data collection func agreeToPrice(ctx contractapi.TransactionContextInterface, assetID 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()) + return fmt.Errorf("failed to get verified OrgID: %v", err) } - // 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()) + return fmt.Errorf("error getting transient: %v", err) } - // 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["asset_price"] + // 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") } @@ -194,26 +171,28 @@ func agreeToPrice(ctx contractapi.TransactionContextInterface, assetID string, p // 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: %s", err.Error()) + return fmt.Errorf("failed to create composite key: %v", err) } - err = ctx.GetStub().PutPrivateData(collection, assetPriceKey, priceJSON) + // 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: %s", err.Error()) + return fmt.Errorf("failed to put asset bid: %v", err) } return nil } -// VerifyAssetProperties implement function to verify asset properties using the hash -// Allows a buyer to validate the properties of an asset against the owner's implicit private data collection +// VerifyAssetProperties Allows a buyer to validate the properties of +// an asset against the owner's implicit private data collection 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: " + err.Error()) + return false, fmt.Errorf("error getting transient: %v", err) } - // Asset properties are private, therefore they get passed in transient field + /// 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") @@ -221,26 +200,29 @@ func (s *SmartContract) VerifyAssetProperties(ctx contractapi.TransactionContext asset, err := s.ReadAsset(ctx, assetID) if err != nil { - return false, fmt.Errorf("failed to get asset: %s", err.Error()) + 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: %s", err.Error()) + 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) } - // 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 false, fmt.Errorf("hash %x for passed immutable properties %s does not match on-chain hash %x", calculatedPropertiesHash, immutablePropertiesJSON, immutablePropertiesOnChainHash) + return false, fmt.Errorf("hash %x for passed immutable properties %s does not match on-chain hash %x", + calculatedPropertiesHash, + immutablePropertiesJSON, + immutablePropertiesOnChainHash, + ) } return true, nil @@ -249,17 +231,14 @@ func (s *SmartContract) VerifyAssetProperties(ctx contractapi.TransactionContext // 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 { - - // 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()) + return fmt.Errorf("failed to get verified OrgID: %v", err) } transMap, err := ctx.GetStub().GetTransient() if err != nil { - return fmt.Errorf("Error getting transient: " + err.Error()) + return fmt.Errorf("error getting transient data: %v", err) } immutablePropertiesJSON, ok := transMap["asset_properties"] @@ -273,24 +252,24 @@ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterfac } var agreement Agreement - err = json.Unmarshal([]byte(priceJSON), &agreement) + err = json.Unmarshal(priceJSON, &agreement) if err != nil { - return fmt.Errorf("failed to unmarshal price JSON: %s", err.Error()) + 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: %s", err.Error()) + return fmt.Errorf("failed to get asset: %v", err) } err = verifyTransferConditions(ctx, asset, immutablePropertiesJSON, clientOrgID, buyerOrgID, priceJSON) if err != nil { - return fmt.Errorf("failed transfer verification: %s", err.Error()) + return fmt.Errorf("failed transfer verification: %v", err) } err = transferAssetState(ctx, asset, immutablePropertiesJSON, clientOrgID, buyerOrgID, agreement.Price) if err != nil { - return fmt.Errorf("failed asset transfer: %s", err.Error()) + return fmt.Errorf("failed asset transfer: %v", err) } return nil @@ -298,186 +277,203 @@ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterfac } // verifyTransferConditions checks that client org currently owns asset and that both parties have agreed on price -func verifyTransferConditions(ctx contractapi.TransactionContextInterface, asset *Asset, immutablePropertiesJSON []byte, clientOrgID string, buyerOrgID string, priceJSON []byte) error { +func verifyTransferConditions(ctx contractapi.TransactionContextInterface, + asset *Asset, + immutablePropertiesJSON []byte, + clientOrgID string, + buyerOrgID string, + priceJSON []byte) error { - // CHECK1: auth check to ensure that client's org actually owns the asset + // 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 the hash of the passed immutable properties matches the on-chain hash + // CHECK2: Verify that the hash of the passed immutable properties matches the on-chain hash - // get on chain hash collectionSeller := buildCollectionName(clientOrgID) immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, asset.ID) if err != nil { - return fmt.Errorf("failed to read asset private properties hash from seller's collection: %s", err.Error()) + return fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err) } if immutablePropertiesOnChainHash == nil { return fmt.Errorf("asset private properties hash does not exist: %s", asset.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) + 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 + // CHECK3: Verify that seller and buyer agreed on the same price - // get seller (current owner) asking 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: %s", err.Error()) + 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: %s", err.Error()) + 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 buyer bid price + // Get buyers bid price collectionBuyer := buildCollectionName(buyerOrgID) assetBidKey, err := ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID}) if err != nil { - return fmt.Errorf("failed to create composite key: %s", err.Error()) + 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: %s", err.Error()) + 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) } - // 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 + // 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) + 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 + // 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 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 } -// transferAssetState makes the public and private state updates for the transferred asset +// transferAssetState performs the public and private state updates for the transferred asset func transferAssetState(ctx contractapi.TransactionContextInterface, asset *Asset, immutablePropertiesJSON []byte, clientOrgID string, buyerOrgID string, price int) error { - - // save the asset with the new owner asset.OwnerOrg = buyerOrgID - - updatedAssetJSON, _ := json.Marshal(asset) - - err := ctx.GetStub().PutState(asset.ID, updatedAssetJSON) + updatedAsset, err := json.Marshal(asset) if err != nil { - return fmt.Errorf("failed to write asset for buyer: %s", err.Error()) + return err + } + + err = ctx.GetStub().PutState(asset.ID, updatedAsset) + if err != nil { + return fmt.Errorf("failed to write asset for buyer: %v", err) } // Change the endorsement policy to the new owner err = setAssetStateBasedEndorsement(ctx, asset.ID, buyerOrgID) if err != nil { - return fmt.Errorf("failed setting state based endorsement for new owner: %s", err.Error()) + 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: %s", err.Error()) + return fmt.Errorf("failed to delete Asset private details from seller: %v", err) } collectionBuyer := buildCollectionName(buyerOrgID) err = ctx.GetStub().PutPrivateData(collectionBuyer, asset.ID, immutablePropertiesJSON) if err != nil { - return fmt.Errorf("failed to put Asset private properties for buyer: %s", err.Error()) + return fmt.Errorf("failed to put Asset private properties for buyer: %v", err) } // 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: %s", err.Error()) + 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: %s", err.Error()) + 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: %s", err.Error()) + 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: %s", err.Error()) + return fmt.Errorf("failed to delete asset price from implicit private data collection for buyer: %v", err) } - // Keep record for a 'receipt' in both buyer and seller private data collection to record the sales price and date - // Persist the agreed to price in a collection sub-namespace based on receipt key prefix + // 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: %s", err.Error()) + return fmt.Errorf("failed to create composite key for receipt: %v", err) } - timestmp, err := ctx.GetStub().GetTxTimestamp() + txTimestamp, err := ctx.GetStub().GetTxTimestamp() if err != nil { - return fmt.Errorf("failed to create timestamp for receipt: %s", err.Error()) + 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: time.Unix(timestmp.Seconds, int64(timestmp.Nanos)), + timestamp: timestamp, + } + receipt, err := json.Marshal(assetReceipt) + if err != nil { + return fmt.Errorf("failed to marshal receipt: %v", err) } - receiptJSON, err := json.Marshal(assetReceipt) + err = ctx.GetStub().PutPrivateData(collectionBuyer, receiptBuyKey, receipt) if err != nil { - return fmt.Errorf("failed to marshal receipt: %s", err.Error()) - } - - err = ctx.GetStub().PutPrivateData(collectionBuyer, receiptBuyKey, receiptJSON) - if err != nil { - return fmt.Errorf("failed to put private asset receipt for buyer: %s", err.Error()) + 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: %s", err.Error()) + return fmt.Errorf("failed to create composite key for receipt: %v", err) } - err = ctx.GetStub().PutPrivateData(collectionSeller, receiptSaleKey, receiptJSON) + err = ctx.GetStub().PutPrivateData(collectionSeller, receiptSaleKey, receipt) if err != nil { - return fmt.Errorf("failed to put private asset receipt for seller: %s", err.Error()) + return fmt.Errorf("failed to put private asset receipt for seller: %v", err) } 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 TransferAsset, since the current owner needs to get an endorsement from the buyer's peer. +// 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 TransferAsset, since the current owner +// needs to get an endorsement from the buyer's peer. 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()) + return "", fmt.Errorf("failed getting client's orgID: %v", err) } if verifyOrg { @@ -490,36 +486,41 @@ func getClientOrgID(ctx contractapi.TransactionContextInterface, verifyOrg bool) return clientOrgID, nil } -// verify client org id and matches peer org id. +// 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: %s", err.Error()) + 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 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 a asset so that only a peer from an owning org can update or transfer the asset. +// setAssetStateBasedEndorsement adds an endorsement policy to a asset so that only a peer from an owning org +// can update or transfer the asset. func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetID string, orgToEndorse string) error { - endorsementPolicy, err := statebased.NewStateEP(nil) - + if err != nil { + return err + } err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgToEndorse) if err != nil { - return fmt.Errorf("failed to add org to endorsement policy: %s", err.Error()) + return fmt.Errorf("failed to add org to endorsement policy: %v", err) } - epBytes, err := endorsementPolicy.Policy() + policy, err := endorsementPolicy.Policy() if err != nil { - return fmt.Errorf("failed to create endorsement policy bytes from org: %s", err.Error()) + return fmt.Errorf("failed to create endorsement policy bytes from org: %v", err) } - err = ctx.GetStub().SetStateValidationParameter(assetID, epBytes) + err = ctx.GetStub().SetStateValidationParameter(assetID, policy) if err != nil { - return fmt.Errorf("failed to set validation parameter on asset: %s", err.Error()) + return fmt.Errorf("failed to set validation parameter on asset: %v", err) } return nil @@ -532,7 +533,7 @@ func buildCollectionName(clientOrgID string) string { 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()) + return "", fmt.Errorf("failed to get verified OrgID: %v", err) } err = verifyClientOrgMatchesPeerOrg(clientOrgID) @@ -544,15 +545,12 @@ func getClientImplicitCollectionName(ctx contractapi.TransactionContextInterface } func main() { - chaincode, err := contractapi.NewChaincode(new(SmartContract)) - if err != nil { - fmt.Printf("Error create transfer asset chaincode: %s", err.Error()) - return + log.Panicf("Error create transfer asset chaincode: %v", err) } if err := chaincode.Start(); err != nil { - fmt.Printf("Error starting asset chaincode: %s", err.Error()) + log.Panicf("Error starting asset chaincode: %v", err) } } diff --git a/asset-transfer-secured-agreement/chaincode-go/asset_transfer_queries.go b/asset-transfer-secured-agreement/chaincode-go/asset_transfer_queries.go index a52486fd..0c01c058 100644 --- a/asset-transfer-secured-agreement/chaincode-go/asset_transfer_queries.go +++ b/asset-transfer-secured-agreement/chaincode-go/asset_transfer_queries.go @@ -1,21 +1,6 @@ /* - * 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. - */ + SPDX-License-Identifier: Apache-2.0 +*/ package main @@ -24,6 +9,7 @@ import ( "fmt" "time" + "github.com/golang/protobuf/ptypes" "github.com/hyperledger/fabric-contract-api-go/contractapi" ) @@ -42,27 +28,25 @@ type Agreement struct { // 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 - + // 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: %s", err.Error()) + return nil, fmt.Errorf("failed to read from world state: %v", err) } if assetJSON == nil { return nil, fmt.Errorf("%s does not exist", assetID) } - asset := new(Asset) - _ = json.Unmarshal(assetJSON, asset) - + 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) { - - // 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. collection, err := getClientImplicitCollectionName(ctx) if err != nil { @@ -71,7 +55,7 @@ func (s *SmartContract) GetAssetPrivateProperties(ctx contractapi.TransactionCon immutableProperties, err := ctx.GetStub().GetPrivateData(collection, assetID) if err != nil { - return "", fmt.Errorf("failed to read asset private properties from client org's collection: %s", err.Error()) + 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) @@ -80,19 +64,18 @@ func (s *SmartContract) GetAssetPrivateProperties(ctx contractapi.TransactionCon return string(immutableProperties), nil } -// GetAssetSalesPrice returns the sales price as an integer +// 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 as an integer +// 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 := getClientImplicitCollectionName(ctx) if err != nil { return "", err @@ -100,18 +83,18 @@ func getAssetPrice(ctx contractapi.TransactionContextInterface, assetID string, assetPriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{assetID}) if err != nil { - return "", fmt.Errorf("failed to create composite key: %s", err.Error()) + return "", fmt.Errorf("failed to create composite key: %v", err) } - assetPriceJSON, err := ctx.GetStub().GetPrivateData(collection, assetPriceKey) + price, err := ctx.GetStub().GetPrivateData(collection, assetPriceKey) if err != nil { - return "", fmt.Errorf("failed to read asset price from implicit private data collection: %s", err.Error()) + return "", fmt.Errorf("failed to read asset price from implicit private data collection: %v", err) } - if assetPriceJSON == nil { + if price == nil { return "", fmt.Errorf("asset price does not exist: %s", assetID) } - return string(assetPriceJSON), nil + return string(price), nil } // QueryAssetSaleAgreements returns all of an organization's proposed sales @@ -119,7 +102,7 @@ func (s *SmartContract) QueryAssetSaleAgreements(ctx contractapi.TransactionCont return queryAgreementsByType(ctx, typeAssetForSale) } -// QueryAssetBuyAgreements returns all of an organization's proposed buys +// QueryAssetBuyAgreements returns all of an organization's proposed bids func (s *SmartContract) QueryAssetBuyAgreements(ctx contractapi.TransactionContextInterface) ([]Agreement, error) { return queryAgreementsByType(ctx, typeAssetBid) } @@ -133,25 +116,24 @@ func queryAgreementsByType(ctx contractapi.TransactionContextInterface, agreeTyp // 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()) + return nil, fmt.Errorf("failed to read from private data collection: %v", err) } defer agreementsIterator.Close() - agreements := []Agreement{} - + var agreements []Agreement for agreementsIterator.HasNext() { resp, err := agreementsIterator.Next() if err != nil { return nil, err } - newAgree := new(Agreement) - err = json.Unmarshal(resp.Value, newAgree) + var agreement Agreement + err = json.Unmarshal(resp.Value, &agreement) if err != nil { return nil, err } - agreements = append(agreements, *newAgree) + agreements = append(agreements, agreement) } return agreements, nil @@ -165,27 +147,30 @@ func (s *SmartContract) QueryAssetHistory(ctx contractapi.TransactionContextInte } defer resultsIterator.Close() - records := []QueryResult{} - + var results []QueryResult for resultsIterator.HasNext() { response, err := resultsIterator.Next() if err != nil { return nil, err } - asset := new(Asset) - err = json.Unmarshal(response.Value, asset) + 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: time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)), + Timestamp: timestamp, Record: asset, } - records = append(records, record) + results = append(results, record) } - return records, nil + return results, nil }