mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-22 09:35:10 +00:00
Refactor Asset Secured Chaincode Into Idiomatic Go
Rewrites the chaincode in idiomatic Go and cleans up the general implementation. A future commit should push the chaincode logic itself into a separate package as chaincode cannot be tested when the logic is part of the main package. Signed-off-by: Brett Logan <brett.t.logan@ibm.com>
This commit is contained in:
parent
59acacbef1
commit
a401bc92bc
2 changed files with 181 additions and 198 deletions
|
|
@ -1,21 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
SPDX-License-Identifier: Apache-2.0
|
||||||
* or more contributor license agreements. See the NOTICE file
|
*/
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
@ -24,8 +9,10 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/ptypes"
|
||||||
"github.com/hyperledger/fabric-chaincode-go/pkg/statebased"
|
"github.com/hyperledger/fabric-chaincode-go/pkg/statebased"
|
||||||
"github.com/hyperledger/fabric-chaincode-go/shim"
|
"github.com/hyperledger/fabric-chaincode-go/shim"
|
||||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
"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
|
// Asset struct and properties must be exported (start with capitals) to work with contract api metadata
|
||||||
type Asset struct {
|
type Asset struct {
|
||||||
ObjectType string `json:"object_type"` // ObjectType is used to distinguish different object types in the same chaincode namespace
|
ObjectType string `json:"objectType"` // ObjectType is used to distinguish different object types in the same chaincode namespace
|
||||||
ID string `json:"asset_id"`
|
ID string `json:"assetID"`
|
||||||
OwnerOrg string `json:"owner_org"`
|
OwnerOrg string `json:"ownerOrg"`
|
||||||
PublicDescription string `json:"public_description"`
|
PublicDescription string `json:"publicDescription"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type receipt struct {
|
type receipt struct {
|
||||||
|
|
@ -55,16 +42,15 @@ type receipt struct {
|
||||||
timestamp time.Time
|
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 {
|
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, assetID, publicDescription string) error {
|
||||||
|
transientMap, err := ctx.GetStub().GetTransient()
|
||||||
transMap, err := ctx.GetStub().GetTransient()
|
|
||||||
if err != nil {
|
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
|
// Asset properties must be retrieved from the transient field as they are private
|
||||||
immutablePropertiesJSON, ok := transMap["asset_properties"]
|
immutablePropertiesJSON, ok := transientMap["asset_properties"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("asset_properties key not found in the transient map")
|
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.
|
// In this scenario, client is only authorized to read/write private data from its own peer.
|
||||||
clientOrgID, err := getClientOrgID(ctx, true)
|
clientOrgID, err := getClientOrgID(ctx, true)
|
||||||
if err != nil {
|
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{
|
asset := Asset{
|
||||||
ObjectType: "asset",
|
ObjectType: "asset",
|
||||||
ID: assetID,
|
ID: assetID,
|
||||||
OwnerOrg: clientOrgID,
|
OwnerOrg: clientOrgID,
|
||||||
PublicDescription: publicDescription,
|
PublicDescription: publicDescription,
|
||||||
}
|
}
|
||||||
|
assetBytes, err := json.Marshal(asset)
|
||||||
assetJSON, err := json.Marshal(asset)
|
|
||||||
if err != nil {
|
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 {
|
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
|
// Set the endorsement policy such that an owner org peer is required to endorse future updates
|
||||||
err = setAssetStateBasedEndorsement(ctx, asset.ID, clientOrgID)
|
err = setAssetStateBasedEndorsement(ctx, asset.ID, clientOrgID)
|
||||||
if err != nil {
|
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
|
// Persist private immutable asset properties to owner's private data collection
|
||||||
collection := buildCollectionName(clientOrgID)
|
collection := buildCollectionName(clientOrgID)
|
||||||
err = ctx.GetStub().PutPrivateData(collection, asset.ID, []byte(immutablePropertiesJSON))
|
err = ctx.GetStub().PutPrivateData(collection, asset.ID, immutablePropertiesJSON)
|
||||||
if err != nil {
|
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
|
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 {
|
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.
|
// No need to check client org id matches peer org id, rely on the asset ownership check instead.
|
||||||
clientOrgID, err := getClientOrgID(ctx, false)
|
clientOrgID, err := getClientOrgID(ctx, false)
|
||||||
if err != nil {
|
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)
|
asset, err := s.ReadAsset(ctx, assetID)
|
||||||
if err != nil {
|
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 {
|
if clientOrgID != asset.OwnerOrg {
|
||||||
return fmt.Errorf("a client from %s cannot update the description of a asset owned by %s", 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
|
asset.PublicDescription = newDescription
|
||||||
|
|
||||||
updatedAssetJSON, err := json.Marshal(asset)
|
updatedAssetJSON, err := json.Marshal(asset)
|
||||||
if err != nil {
|
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)
|
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
|
// AgreeToSell adds seller's asking price to seller's implicit private data collection
|
||||||
func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface, assetID string) error {
|
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)
|
asset, err := s.ReadAsset(ctx, assetID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -150,11 +130,12 @@ func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface,
|
||||||
|
|
||||||
clientOrgID, err := getClientOrgID(ctx, true)
|
clientOrgID, err := getClientOrgID(ctx, true)
|
||||||
if err != nil {
|
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 {
|
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)
|
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
|
// agreeToPrice adds a bid or ask price to caller's implicit private data collection
|
||||||
func agreeToPrice(ctx contractapi.TransactionContextInterface, assetID string, priceType string) error {
|
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.
|
// In this scenario, client is only authorized to read/write private data from its own peer.
|
||||||
clientOrgID, err := getClientOrgID(ctx, true)
|
clientOrgID, err := getClientOrgID(ctx, true)
|
||||||
if err != nil {
|
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()
|
transMap, err := ctx.GetStub().GetTransient()
|
||||||
if err != nil {
|
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,
|
// Asset price must be retrieved from the transient field as they are private
|
||||||
// so that there is no risk of nondeterministic marshaling.
|
price, ok := transMap["asset_price"]
|
||||||
priceJSON, ok := transMap["asset_price"]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("asset_price key not found in the transient map")
|
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
|
// to avoid collisions between private asset properties, sell price, and buy price
|
||||||
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{assetID})
|
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{assetID})
|
||||||
if err != nil {
|
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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyAssetProperties implement function to verify asset properties using the hash
|
// VerifyAssetProperties Allows a buyer to validate the properties of
|
||||||
// Allows a buyer to validate the properties of an asset against the owner's implicit private data collection
|
// an asset against the owner's implicit private data collection
|
||||||
func (s *SmartContract) VerifyAssetProperties(ctx contractapi.TransactionContextInterface, assetID string) (bool, error) {
|
func (s *SmartContract) VerifyAssetProperties(ctx contractapi.TransactionContextInterface, assetID string) (bool, error) {
|
||||||
transMap, err := ctx.GetStub().GetTransient()
|
transMap, err := ctx.GetStub().GetTransient()
|
||||||
if err != nil {
|
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"]
|
immutablePropertiesJSON, ok := transMap["asset_properties"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("asset_properties key not found in the transient map")
|
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)
|
asset, err := s.ReadAsset(ctx, assetID)
|
||||||
if err != nil {
|
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)
|
collectionOwner := buildCollectionName(asset.OwnerOrg)
|
||||||
immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionOwner, assetID)
|
immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionOwner, assetID)
|
||||||
if err != nil {
|
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 {
|
if immutablePropertiesOnChainHash == nil {
|
||||||
return false, fmt.Errorf("asset private properties hash does not exist: %s", assetID)
|
return false, fmt.Errorf("asset private properties hash does not exist: %s", assetID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get sha256 hash of passed immutable properties
|
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
hash.Write(immutablePropertiesJSON)
|
hash.Write(immutablePropertiesJSON)
|
||||||
calculatedPropertiesHash := hash.Sum(nil)
|
calculatedPropertiesHash := hash.Sum(nil)
|
||||||
|
|
||||||
// verify that the hash of the passed immutable properties matches the on-chain hash
|
// verify that the hash of the passed immutable properties matches the on-chain hash
|
||||||
if !bytes.Equal(immutablePropertiesOnChainHash, calculatedPropertiesHash) {
|
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
|
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 checks transfer conditions and then transfers asset state to buyer.
|
||||||
// TransferAsset can only be called by current owner
|
// TransferAsset can only be called by current owner
|
||||||
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, assetID string, buyerOrgID string) error {
|
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)
|
clientOrgID, err := getClientOrgID(ctx, false)
|
||||||
if err != nil {
|
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()
|
transMap, err := ctx.GetStub().GetTransient()
|
||||||
if err != nil {
|
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"]
|
immutablePropertiesJSON, ok := transMap["asset_properties"]
|
||||||
|
|
@ -273,24 +252,24 @@ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterfac
|
||||||
}
|
}
|
||||||
|
|
||||||
var agreement Agreement
|
var agreement Agreement
|
||||||
err = json.Unmarshal([]byte(priceJSON), &agreement)
|
err = json.Unmarshal(priceJSON, &agreement)
|
||||||
if err != nil {
|
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)
|
asset, err := s.ReadAsset(ctx, assetID)
|
||||||
if err != nil {
|
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)
|
err = verifyTransferConditions(ctx, asset, immutablePropertiesJSON, clientOrgID, buyerOrgID, priceJSON)
|
||||||
if err != nil {
|
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)
|
err = transferAssetState(ctx, asset, immutablePropertiesJSON, clientOrgID, buyerOrgID, agreement.Price)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed asset transfer: %s", err.Error())
|
return fmt.Errorf("failed asset transfer: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
// 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 {
|
if clientOrgID != asset.OwnerOrg {
|
||||||
return fmt.Errorf("a client from %s cannot transfer a asset owned by %s", 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)
|
collectionSeller := buildCollectionName(clientOrgID)
|
||||||
immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, asset.ID)
|
immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, asset.ID)
|
||||||
if err != nil {
|
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 {
|
if immutablePropertiesOnChainHash == nil {
|
||||||
return fmt.Errorf("asset private properties hash does not exist: %s", asset.ID)
|
return fmt.Errorf("asset private properties hash does not exist: %s", asset.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get sha256 hash of passed immutable properties
|
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
hash.Write(immutablePropertiesJSON)
|
hash.Write(immutablePropertiesJSON)
|
||||||
calculatedPropertiesHash := hash.Sum(nil)
|
calculatedPropertiesHash := hash.Sum(nil)
|
||||||
|
|
||||||
// verify that the hash of the passed immutable properties matches the on-chain hash
|
// verify that the hash of the passed immutable properties matches the on-chain hash
|
||||||
if !bytes.Equal(immutablePropertiesOnChainHash, calculatedPropertiesHash) {
|
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})
|
assetForSaleKey, err := ctx.GetStub().CreateCompositeKey(typeAssetForSale, []string{asset.ID})
|
||||||
if err != nil {
|
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)
|
sellerPriceHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, assetForSaleKey)
|
||||||
if err != nil {
|
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 {
|
if sellerPriceHash == nil {
|
||||||
return fmt.Errorf("seller price for %s does not exist", asset.ID)
|
return fmt.Errorf("seller price for %s does not exist", asset.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get buyer bid price
|
// Get buyers bid price
|
||||||
collectionBuyer := buildCollectionName(buyerOrgID)
|
collectionBuyer := buildCollectionName(buyerOrgID)
|
||||||
assetBidKey, err := ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID})
|
assetBidKey, err := ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID})
|
||||||
if err != nil {
|
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)
|
buyerPriceHash, err := ctx.GetStub().GetPrivateDataHash(collectionBuyer, assetBidKey)
|
||||||
if err != nil {
|
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 {
|
if buyerPriceHash == nil {
|
||||||
return fmt.Errorf("buyer price for %s does not exist", asset.ID)
|
return fmt.Errorf("buyer price for %s does not exist", asset.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get sha256 hash of passed price
|
|
||||||
hash = sha256.New()
|
hash = sha256.New()
|
||||||
hash.Write(priceJSON)
|
hash.Write(priceJSON)
|
||||||
calculatedPriceHash := hash.Sum(nil)
|
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) {
|
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) {
|
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
|
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 {
|
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
|
asset.OwnerOrg = buyerOrgID
|
||||||
|
updatedAsset, err := json.Marshal(asset)
|
||||||
updatedAssetJSON, _ := json.Marshal(asset)
|
|
||||||
|
|
||||||
err := ctx.GetStub().PutState(asset.ID, updatedAssetJSON)
|
|
||||||
if err != nil {
|
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
|
// Change the endorsement policy to the new owner
|
||||||
err = setAssetStateBasedEndorsement(ctx, asset.ID, buyerOrgID)
|
err = setAssetStateBasedEndorsement(ctx, asset.ID, buyerOrgID)
|
||||||
if err != nil {
|
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)
|
// Transfer the private properties (delete from seller collection, create in buyer collection)
|
||||||
collectionSeller := buildCollectionName(clientOrgID)
|
collectionSeller := buildCollectionName(clientOrgID)
|
||||||
err = ctx.GetStub().DelPrivateData(collectionSeller, asset.ID)
|
err = ctx.GetStub().DelPrivateData(collectionSeller, asset.ID)
|
||||||
if err != nil {
|
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)
|
collectionBuyer := buildCollectionName(buyerOrgID)
|
||||||
err = ctx.GetStub().PutPrivateData(collectionBuyer, asset.ID, immutablePropertiesJSON)
|
err = ctx.GetStub().PutPrivateData(collectionBuyer, asset.ID, immutablePropertiesJSON)
|
||||||
if err != nil {
|
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
|
// Delete the price records for seller
|
||||||
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(typeAssetForSale, []string{asset.ID})
|
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(typeAssetForSale, []string{asset.ID})
|
||||||
if err != nil {
|
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)
|
err = ctx.GetStub().DelPrivateData(collectionSeller, assetPriceKey)
|
||||||
if err != nil {
|
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
|
// Delete the price records for buyer
|
||||||
assetPriceKey, err = ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID})
|
assetPriceKey, err = ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID})
|
||||||
if err != nil {
|
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)
|
err = ctx.GetStub().DelPrivateData(collectionBuyer, assetPriceKey)
|
||||||
if err != nil {
|
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
|
// 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
|
// 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()})
|
receiptBuyKey, err := ctx.GetStub().CreateCompositeKey(typeAssetBuyReceipt, []string{asset.ID, ctx.GetStub().GetTxID()})
|
||||||
if err != nil {
|
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 {
|
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{
|
assetReceipt := receipt{
|
||||||
price: price,
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal receipt: %s", err.Error())
|
return fmt.Errorf("failed to put private asset receipt for buyer: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
err = ctx.GetStub().PutPrivateData(collectionBuyer, receiptBuyKey, receiptJSON)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to put private asset receipt for buyer: %s", err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
receiptSaleKey, err := ctx.GetStub().CreateCompositeKey(typeAssetSaleReceipt, []string{ctx.GetStub().GetTxID(), asset.ID})
|
receiptSaleKey, err := ctx.GetStub().CreateCompositeKey(typeAssetSaleReceipt, []string{ctx.GetStub().GetTxID(), asset.ID})
|
||||||
if err != nil {
|
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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getClientOrgID gets the client org ID.
|
// 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 client org ID can optionally be verified against the peer org ID, to ensure that a client
|
||||||
// The only exception in this scenario is for TransferAsset, since the current owner needs to get an endorsement from the buyer's peer.
|
// 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) {
|
func getClientOrgID(ctx contractapi.TransactionContextInterface, verifyOrg bool) (string, error) {
|
||||||
|
|
||||||
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
|
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
|
||||||
if err != nil {
|
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 {
|
if verifyOrg {
|
||||||
|
|
@ -490,36 +486,41 @@ func getClientOrgID(ctx contractapi.TransactionContextInterface, verifyOrg bool)
|
||||||
return clientOrgID, nil
|
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 {
|
func verifyClientOrgMatchesPeerOrg(clientOrgID string) error {
|
||||||
peerOrgID, err := shim.GetMSPID()
|
peerOrgID, err := shim.GetMSPID()
|
||||||
if err != nil {
|
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 {
|
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
|
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 {
|
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetID string, orgToEndorse string) error {
|
||||||
|
|
||||||
endorsementPolicy, err := statebased.NewStateEP(nil)
|
endorsementPolicy, err := statebased.NewStateEP(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgToEndorse)
|
err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgToEndorse)
|
||||||
if err != nil {
|
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 {
|
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 {
|
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
|
return nil
|
||||||
|
|
@ -532,7 +533,7 @@ func buildCollectionName(clientOrgID string) string {
|
||||||
func getClientImplicitCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {
|
func getClientImplicitCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||||
clientOrgID, err := getClientOrgID(ctx, true)
|
clientOrgID, err := getClientOrgID(ctx, true)
|
||||||
if err != nil {
|
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)
|
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
|
||||||
|
|
@ -544,15 +545,12 @@ func getClientImplicitCollectionName(ctx contractapi.TransactionContextInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
chaincode, err := contractapi.NewChaincode(new(SmartContract))
|
chaincode, err := contractapi.NewChaincode(new(SmartContract))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error create transfer asset chaincode: %s", err.Error())
|
log.Panicf("Error create transfer asset chaincode: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := chaincode.Start(); err != nil {
|
if err := chaincode.Start(); err != nil {
|
||||||
fmt.Printf("Error starting asset chaincode: %s", err.Error())
|
log.Panicf("Error starting asset chaincode: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
SPDX-License-Identifier: Apache-2.0
|
||||||
* or more contributor license agreements. See the NOTICE file
|
*/
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
@ -24,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/ptypes"
|
||||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -42,27 +28,25 @@ type Agreement struct {
|
||||||
|
|
||||||
// ReadAsset returns the public asset data
|
// ReadAsset returns the public asset data
|
||||||
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) {
|
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)
|
assetJSON, err := ctx.GetStub().GetState(assetID)
|
||||||
if err != nil {
|
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 {
|
if assetJSON == nil {
|
||||||
return nil, fmt.Errorf("%s does not exist", assetID)
|
return nil, fmt.Errorf("%s does not exist", assetID)
|
||||||
}
|
}
|
||||||
|
|
||||||
asset := new(Asset)
|
var asset *Asset
|
||||||
_ = json.Unmarshal(assetJSON, asset)
|
err = json.Unmarshal(assetJSON, asset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return asset, nil
|
return asset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssetPrivateProperties returns the immutable asset properties from owner's private data collection
|
// GetAssetPrivateProperties returns the immutable asset properties from owner's private data collection
|
||||||
func (s *SmartContract) GetAssetPrivateProperties(ctx contractapi.TransactionContextInterface, assetID string) (string, error) {
|
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.
|
// In this scenario, client is only authorized to read/write private data from its own peer.
|
||||||
collection, err := getClientImplicitCollectionName(ctx)
|
collection, err := getClientImplicitCollectionName(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -71,7 +55,7 @@ func (s *SmartContract) GetAssetPrivateProperties(ctx contractapi.TransactionCon
|
||||||
|
|
||||||
immutableProperties, err := ctx.GetStub().GetPrivateData(collection, assetID)
|
immutableProperties, err := ctx.GetStub().GetPrivateData(collection, assetID)
|
||||||
if err != nil {
|
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 {
|
if immutableProperties == nil {
|
||||||
return "", fmt.Errorf("asset private details does not exist in client org's collection: %s", assetID)
|
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
|
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) {
|
func (s *SmartContract) GetAssetSalesPrice(ctx contractapi.TransactionContextInterface, assetID string) (string, error) {
|
||||||
return getAssetPrice(ctx, assetID, typeAssetForSale)
|
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) {
|
func (s *SmartContract) GetAssetBidPrice(ctx contractapi.TransactionContextInterface, assetID string) (string, error) {
|
||||||
return getAssetPrice(ctx, assetID, typeAssetBid)
|
return getAssetPrice(ctx, assetID, typeAssetBid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAssetPrice gets the bid or ask price from caller's implicit private data collection
|
// 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) {
|
func getAssetPrice(ctx contractapi.TransactionContextInterface, assetID string, priceType string) (string, error) {
|
||||||
|
|
||||||
collection, err := getClientImplicitCollectionName(ctx)
|
collection, err := getClientImplicitCollectionName(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -100,18 +83,18 @@ func getAssetPrice(ctx contractapi.TransactionContextInterface, assetID string,
|
||||||
|
|
||||||
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{assetID})
|
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{assetID})
|
||||||
if err != nil {
|
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 {
|
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 "", 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
|
// QueryAssetSaleAgreements returns all of an organization's proposed sales
|
||||||
|
|
@ -119,7 +102,7 @@ func (s *SmartContract) QueryAssetSaleAgreements(ctx contractapi.TransactionCont
|
||||||
return queryAgreementsByType(ctx, typeAssetForSale)
|
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) {
|
func (s *SmartContract) QueryAssetBuyAgreements(ctx contractapi.TransactionContextInterface) ([]Agreement, error) {
|
||||||
return queryAgreementsByType(ctx, typeAssetBid)
|
return queryAgreementsByType(ctx, typeAssetBid)
|
||||||
}
|
}
|
||||||
|
|
@ -133,25 +116,24 @@ func queryAgreementsByType(ctx contractapi.TransactionContextInterface, agreeTyp
|
||||||
// Query for any object type starting with `agreeType`
|
// Query for any object type starting with `agreeType`
|
||||||
agreementsIterator, err := ctx.GetStub().GetPrivateDataByPartialCompositeKey(collection, agreeType, []string{})
|
agreementsIterator, err := ctx.GetStub().GetPrivateDataByPartialCompositeKey(collection, agreeType, []string{})
|
||||||
if err != nil {
|
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()
|
defer agreementsIterator.Close()
|
||||||
|
|
||||||
agreements := []Agreement{}
|
var agreements []Agreement
|
||||||
|
|
||||||
for agreementsIterator.HasNext() {
|
for agreementsIterator.HasNext() {
|
||||||
resp, err := agreementsIterator.Next()
|
resp, err := agreementsIterator.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
newAgree := new(Agreement)
|
var agreement Agreement
|
||||||
err = json.Unmarshal(resp.Value, newAgree)
|
err = json.Unmarshal(resp.Value, &agreement)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
agreements = append(agreements, *newAgree)
|
agreements = append(agreements, agreement)
|
||||||
}
|
}
|
||||||
|
|
||||||
return agreements, nil
|
return agreements, nil
|
||||||
|
|
@ -165,27 +147,30 @@ func (s *SmartContract) QueryAssetHistory(ctx contractapi.TransactionContextInte
|
||||||
}
|
}
|
||||||
defer resultsIterator.Close()
|
defer resultsIterator.Close()
|
||||||
|
|
||||||
records := []QueryResult{}
|
var results []QueryResult
|
||||||
|
|
||||||
for resultsIterator.HasNext() {
|
for resultsIterator.HasNext() {
|
||||||
response, err := resultsIterator.Next()
|
response, err := resultsIterator.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
asset := new(Asset)
|
var asset *Asset
|
||||||
err = json.Unmarshal(response.Value, asset)
|
err = json.Unmarshal(response.Value, &asset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timestamp, err := ptypes.Timestamp(response.Timestamp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
record := QueryResult{
|
record := QueryResult{
|
||||||
TxId: response.TxId,
|
TxId: response.TxId,
|
||||||
Timestamp: time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)),
|
Timestamp: timestamp,
|
||||||
Record: asset,
|
Record: asset,
|
||||||
}
|
}
|
||||||
records = append(records, record)
|
results = append(results, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
return records, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue