mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
updated and improved asset-transfer-secured-agreement sample (#751)
Signed-off-by: fraVlaca <ocsenarf@outlook.com>
This commit is contained in:
parent
4681fe7865
commit
94867dd517
5 changed files with 203 additions and 160 deletions
|
|
@ -24,6 +24,7 @@ The smart contract (in folder `chaincode-go`) implements the following functions
|
|||
- GetAssetPrivateProperties
|
||||
- GetAssetSalesPrice
|
||||
- GetAssetBidPrice
|
||||
- GetAssetHashId
|
||||
- QueryAssetSaleAgreements
|
||||
- QueryAssetBuyAgreements
|
||||
- QueryAssetHistory
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const chaincodeName = 'secured';
|
|||
|
||||
//Use a random key so that we can run multiple times
|
||||
const now = Date.now().toString();
|
||||
const assetKey = `asset${now}`;
|
||||
let assetKey: string;
|
||||
|
||||
async function main(): Promise<void> {
|
||||
|
||||
|
|
@ -57,9 +57,8 @@ async function main(): Promise<void> {
|
|||
const contractWrapperOrg2 = new ContractWrapper(contractOrg2, mspIdOrg2);
|
||||
|
||||
// Create an asset by organization Org1, this only requires the owning organization to endorse.
|
||||
await contractWrapperOrg1.createAsset({ assetId: assetKey,
|
||||
ownerOrg: mspIdOrg1,
|
||||
publicDescription: `Asset ${assetKey} owned by ${mspIdOrg1} is not for sale`}, { ObjectType: 'asset_properties', Color: 'blue', Size: 35 });
|
||||
assetKey = await contractWrapperOrg1.createAsset(mspIdOrg1,
|
||||
`Asset owned by ${mspIdOrg1} is not for sale`, { ObjectType: 'asset_properties', Color: 'blue', Size: 35 });
|
||||
|
||||
// Read the public details by org1.
|
||||
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg1);
|
||||
|
|
@ -109,16 +108,16 @@ async function main(): Promise<void> {
|
|||
assetId: assetKey,
|
||||
price: 110,
|
||||
tradeId: now,
|
||||
});
|
||||
}, mspIdOrg2);
|
||||
|
||||
// Check the private information about the asset from Org2. Org1 would have to send Org2 asset details,
|
||||
// so the hash of the details may be checked by the chaincode.
|
||||
await contractWrapperOrg2.verifyAssetProperties({ assetId:assetKey, color:'blue', size:35});
|
||||
await contractWrapperOrg2.verifyAssetProperties(assetKey, {color:'blue', size:35});
|
||||
|
||||
// Agree to a buy by org2.
|
||||
await contractWrapperOrg2.agreeToBuy( {assetId: assetKey,
|
||||
price: 100,
|
||||
tradeId: now});
|
||||
tradeId: now}, { ObjectType: 'asset_properties', Color: 'blue', Size: 35 });
|
||||
|
||||
// Org1 should be able to read the sale price of this asset.
|
||||
await contractWrapperOrg1.getAssetSalesPrice(assetKey, mspIdOrg1);
|
||||
|
|
@ -142,12 +141,12 @@ async function main(): Promise<void> {
|
|||
// Org1 will try to transfer the asset to Org2
|
||||
// This will fail due to the sell price and the bid price are not the same.
|
||||
try{
|
||||
await contractWrapperOrg1.transferAsset({ObjectType: 'asset_properties', Color: 'blue', Size: 35}, { assetId: assetKey, price: 110, tradeId: now}, [ mspIdOrg1, mspIdOrg2 ], mspIdOrg1, mspIdOrg2);
|
||||
await contractWrapperOrg1.transferAsset({ assetId: assetKey, price: 110, tradeId: now}, mspIdOrg1, mspIdOrg2);
|
||||
} catch(e) {
|
||||
console.log(`${RED}*** Failed: transferAsset - ${e}${RESET}`);
|
||||
}
|
||||
// Agree to a sell by Org1, the seller will agree to the bid price of Org2.
|
||||
await contractWrapperOrg1.agreeToSell({assetId:assetKey, price:100, tradeId:now});
|
||||
await contractWrapperOrg1.agreeToSell({assetId:assetKey, price:100, tradeId:now}, mspIdOrg2);
|
||||
|
||||
// Read the public details by org1.
|
||||
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg1);
|
||||
|
|
@ -167,14 +166,14 @@ async function main(): Promise<void> {
|
|||
// Org2 user will try to transfer the asset to Org1.
|
||||
// This will fail as the owner is Org1.
|
||||
try{
|
||||
await contractWrapperOrg2.transferAsset({ObjectType: 'asset_properties', Color: 'blue', Size: 35}, { assetId: assetKey, price: 100, tradeId: now}, [ mspIdOrg1, mspIdOrg2 ], mspIdOrg1, mspIdOrg2);
|
||||
await contractWrapperOrg2.transferAsset({ assetId: assetKey, price: 100, tradeId: now}, mspIdOrg1, mspIdOrg2);
|
||||
} catch(e) {
|
||||
console.log(`${RED}*** Failed: transferAsset - ${e}${RESET}`);
|
||||
}
|
||||
|
||||
// Org1 will transfer the asset to Org2.
|
||||
// This will now complete as the sell price and the bid price are the same.
|
||||
await contractWrapperOrg1.transferAsset({ObjectType: 'asset_properties', Color: 'blue', Size: 35}, { assetId: assetKey, price: 100, tradeId: now}, [ mspIdOrg1, mspIdOrg2 ], mspIdOrg1, mspIdOrg2);
|
||||
await contractWrapperOrg1.transferAsset({ assetId: assetKey, price: 100, tradeId: now}, mspIdOrg1, mspIdOrg2);
|
||||
|
||||
// Read the public details by org1.
|
||||
await contractWrapperOrg1.readAsset(assetKey, mspIdOrg2);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { Contract } from '@hyperledger/fabric-gateway';
|
|||
import { TextDecoder } from 'util';
|
||||
import { GREEN, parse, RED, RESET } from './utils';
|
||||
import crpto from 'crypto';
|
||||
import { mspIdOrg2 } from './connect';
|
||||
|
||||
const randomBytes = crpto.randomBytes(256).toString('hex');
|
||||
|
||||
|
|
@ -19,7 +20,6 @@ interface AssetJSON {
|
|||
|
||||
interface AssetPropertiesJSON {
|
||||
objectType: string;
|
||||
assetID: string;
|
||||
color: string;
|
||||
size: number;
|
||||
salt: string;
|
||||
|
|
@ -44,7 +44,6 @@ export interface Asset {
|
|||
}
|
||||
|
||||
export interface AssetProperties {
|
||||
assetId: string;
|
||||
color: string;
|
||||
size: number;
|
||||
}
|
||||
|
|
@ -61,27 +60,30 @@ export class ContractWrapper {
|
|||
readonly #org: string;
|
||||
readonly #utf8Decoder = new TextDecoder();
|
||||
readonly #randomBytes: string = randomBytes;
|
||||
#endorsingOrgs: { [id: string]: string[] };
|
||||
|
||||
public constructor(contract: Contract, org: string) {
|
||||
this.#contract = contract;
|
||||
this.#org = org;
|
||||
this.#endorsingOrgs = {};
|
||||
}
|
||||
|
||||
public async createAsset(asset: Asset, privateData: AssetPrivateData): Promise<void> {
|
||||
console.log(`${GREEN}--> Submit Transaction: CreateAsset, ${asset.assetId} as ${asset.ownerOrg} - endorsed by Org1.${RESET}`);
|
||||
public async createAsset(ownerOrg: string, publicDescription: string, privateData: AssetPrivateData): Promise<string> {
|
||||
console.log(`${GREEN}--> Submit Transaction: CreateAsset as ${ownerOrg} - endorsed by Org1.${RESET}`);
|
||||
const assetPropertiesJSON: AssetPropertiesJSON = {
|
||||
objectType: 'asset_properties',
|
||||
assetID: asset.assetId,
|
||||
color: privateData.Color,
|
||||
size: privateData.Size,
|
||||
salt: this.#randomBytes };
|
||||
|
||||
await this.#contract.submit('CreateAsset', {
|
||||
arguments: [asset.assetId, asset.publicDescription],
|
||||
const resultBytes = await this.#contract.submit('CreateAsset', {
|
||||
arguments: [publicDescription],
|
||||
transientData: { asset_properties: JSON.stringify(assetPropertiesJSON)},
|
||||
});
|
||||
|
||||
console.log(`*** Result: committed, asset ${asset.assetId} is owned by Org1`);
|
||||
const assetID = this.#utf8Decoder.decode(resultBytes);
|
||||
this.#endorsingOrgs[assetID] = [ownerOrg];
|
||||
console.log(`*** Result: committed, asset ${assetID} is owned by ${ownerOrg}`);
|
||||
return assetID;
|
||||
}
|
||||
|
||||
public async readAsset(assetKey: string, ownerOrg: string): Promise<void> {
|
||||
|
|
@ -113,7 +115,6 @@ export class ContractWrapper {
|
|||
const resultString = this.#utf8Decoder.decode(resultBytes);
|
||||
const json = parse<AssetPropertiesJSON>(resultString);
|
||||
const result: AssetProperties = {
|
||||
assetId: json.assetID,
|
||||
color: json.color,
|
||||
size: json.size,
|
||||
};
|
||||
|
|
@ -129,12 +130,13 @@ export class ContractWrapper {
|
|||
|
||||
await this.#contract.submit('ChangePublicDescription', {
|
||||
arguments:[asset.assetId, asset.publicDescription],
|
||||
endorsingOrganizations: this.#endorsingOrgs[asset.assetId]
|
||||
});
|
||||
|
||||
console.log(`*** Result: committed, Desc: ${asset.publicDescription}`);
|
||||
}
|
||||
|
||||
public async agreeToSell(assetPrice: AssetPrice): Promise<void> {
|
||||
public async agreeToSell(assetPrice: AssetPrice, buyerOrgID: string): Promise<void> {
|
||||
|
||||
console.log(`${GREEN}--> Submit Transaction: AgreeToSell, ${assetPrice.assetId} as ${this.#org} - endorsed by ${this.#org}.${RESET}`);
|
||||
const assetPriceJSON: AssetPriceJSON = {
|
||||
|
|
@ -144,23 +146,28 @@ export class ContractWrapper {
|
|||
};
|
||||
|
||||
await this.#contract.submit('AgreeToSell', {
|
||||
arguments:[assetPrice.assetId],
|
||||
transientData: {asset_price: JSON.stringify(assetPriceJSON)}
|
||||
arguments:[assetPrice.assetId, buyerOrgID],
|
||||
transientData: {asset_price: JSON.stringify(assetPriceJSON)},
|
||||
endorsingOrganizations: this.#endorsingOrgs[assetPrice.assetId]
|
||||
});
|
||||
|
||||
//update local record of sbe to inlcude buyer org if not already
|
||||
if (this.#endorsingOrgs[assetPrice.assetId].indexOf('buyerOrgID') == -1){
|
||||
this.#endorsingOrgs[assetPrice.assetId].push(buyerOrgID);
|
||||
}
|
||||
|
||||
console.log(`*** Result: committed, ${this.#org} has agreed to sell asset ${assetPrice.assetId} for ${assetPrice.price}`);
|
||||
}
|
||||
|
||||
public async verifyAssetProperties(assetProperties: AssetProperties): Promise<void> {
|
||||
console.log(`${GREEN}--> Evalute: VerifyAssetProperties, ${assetProperties.assetId} as ${this.#org} - endorsed by ${this.#org}.${RESET}`);
|
||||
public async verifyAssetProperties(assetId: string, assetProperties: AssetProperties): Promise<void> {
|
||||
console.log(`${GREEN}--> Evalute: VerifyAssetProperties, ${assetId} as ${this.#org} - endorsed by ${this.#org} and ${mspIdOrg2}.${RESET}`);
|
||||
const assetPropertiesJSON: AssetPropertiesJSON = {objectType: 'asset_properties',
|
||||
assetID: assetProperties.assetId,
|
||||
color: assetProperties.color,
|
||||
size: assetProperties.size,
|
||||
salt: this.#randomBytes };
|
||||
|
||||
const resultBytes = await this.#contract.evaluate('VerifyAssetProperties', {
|
||||
arguments:[assetPropertiesJSON.assetID],
|
||||
arguments:[assetId],
|
||||
transientData: {asset_properties: JSON.stringify(assetPropertiesJSON)},
|
||||
});
|
||||
|
||||
|
|
@ -168,24 +175,29 @@ export class ContractWrapper {
|
|||
if (resultString.length !== 0) {
|
||||
const json = parse<AssetPropertiesJSON>(resultString);
|
||||
const result: AssetProperties = {
|
||||
assetId: json.assetID,
|
||||
color: json.color,
|
||||
size: json.size
|
||||
};
|
||||
if (result) {
|
||||
console.log(`*** Success VerifyAssetProperties, private information about asset ${assetProperties.assetId} has been verified by ${this.#org}`);
|
||||
console.log(`*** Success VerifyAssetProperties, private information about asset ${assetId} has been verified by ${this.#org}`);
|
||||
} else {
|
||||
console.log(`*** Failed: VerifyAssetProperties, private information about asset ${assetProperties.assetId} has not been verified by ${this.#org}`);
|
||||
console.log(`*** Failed: VerifyAssetProperties, private information about asset ${assetId} has not been verified by ${this.#org}`);
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error(`Private information about asset ${assetProperties.assetId} has not been verified by ${this.#org}`);
|
||||
throw new Error(`Private information about asset ${assetId} has not been verified by ${this.#org}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async agreeToBuy(assetPrice: AssetPrice, ): Promise<void> {
|
||||
public async agreeToBuy(assetPrice: AssetPrice, privateData: AssetPrivateData): Promise<void> {
|
||||
|
||||
console.log(`${GREEN}--> Submit Transaction: AgreeToBuy, ${assetPrice.assetId} as ${this.#org} - endorsed by ${this.#org} and ${mspIdOrg2}.${RESET}`);
|
||||
const assetPropertiesJSON: AssetPropertiesJSON = {
|
||||
objectType: 'asset_properties',
|
||||
color: privateData.Color,
|
||||
size: privateData.Size,
|
||||
salt: this.#randomBytes };
|
||||
|
||||
console.log(`${GREEN}--> Submit Transaction: AgreeToBuy, ${assetPrice.assetId} as ${this.#org} - endorsed by ${this.#org}.${RESET}`);
|
||||
const assetPriceJSON: AssetPriceJSON = {
|
||||
assetID: assetPrice.assetId,
|
||||
price: assetPrice.price,
|
||||
|
|
@ -194,7 +206,11 @@ export class ContractWrapper {
|
|||
|
||||
await this.#contract.submit('AgreeToBuy', {
|
||||
arguments:[assetPrice.assetId],
|
||||
transientData: {asset_price: JSON.stringify(assetPriceJSON)}
|
||||
transientData: {
|
||||
asset_price: JSON.stringify(assetPriceJSON),
|
||||
asset_properties: JSON.stringify(assetPropertiesJSON)
|
||||
},
|
||||
endorsingOrganizations: this.#endorsingOrgs[assetPrice.assetId]
|
||||
});
|
||||
|
||||
console.log(`*** Result: committed, ${this.#org} has agreed to buy asset ${assetPrice.assetId} for 100`);
|
||||
|
|
@ -242,9 +258,9 @@ export class ContractWrapper {
|
|||
console.log('*** Result: GetAssetBidPrice', result);
|
||||
}
|
||||
|
||||
public async transferAsset( privateData: AssetPrivateData, assetPrice: AssetPrice, endorsingOrganizations: string[], ownerOrgID: string, buyerOrgID: string): Promise<void> {
|
||||
public async transferAsset(assetPrice: AssetPrice, ownerOrgID: string, buyerOrgID: string): Promise<void> {
|
||||
|
||||
console.log(`${GREEN}--> Submit Transaction: TransferAsset, ${assetPrice.assetId} as ${this.#org } - endorsed by ${this.#org}.${RESET}`);
|
||||
console.log(`${GREEN}--> Submit Transaction: TransferAsset, ${assetPrice.assetId} as ${this.#org } - endorsed by ${this.#org} and ${buyerOrgID}.${RESET}`);
|
||||
|
||||
if (this.#org !== ownerOrgID) {
|
||||
console.log(`${GREEN}* Expected to fail as the owner is ${ownerOrgID}.${RESET}`);
|
||||
|
|
@ -252,20 +268,12 @@ export class ContractWrapper {
|
|||
console.log(`${GREEN}* Expected to fail as sell price and the bid price are not the same.${RESET}`);
|
||||
}
|
||||
|
||||
const assetPropertiesJSON: AssetPropertiesJSON = {objectType: 'asset_properties',
|
||||
assetID: assetPrice.assetId,
|
||||
color: privateData.Color,
|
||||
size: privateData.Size,
|
||||
salt: this.#randomBytes };
|
||||
|
||||
const assetPriceJSON: AssetPriceJSON = { assetID: assetPrice.assetId, price:assetPrice.price, tradeID:assetPrice.tradeId};
|
||||
|
||||
await this.#contract.submit('TransferAsset', {
|
||||
arguments:[assetPropertiesJSON.assetID, buyerOrgID],
|
||||
transientData: {
|
||||
asset_properties: JSON.stringify(assetPropertiesJSON),
|
||||
asset_price: JSON.stringify(assetPriceJSON)},
|
||||
endorsingOrganizations:endorsingOrganizations
|
||||
arguments:[assetPrice.assetId, buyerOrgID],
|
||||
transientData: { asset_price: JSON.stringify(assetPriceJSON) },
|
||||
endorsingOrganizations: this.#endorsingOrgs[assetPrice.assetId]
|
||||
});
|
||||
|
||||
console.log(`${GREEN}*** Result: committed, ${this.#org} has transfered the asset ${assetPrice.assetId} to ${buyerOrgID}.${RESET}`);
|
||||
|
|
|
|||
|
|
@ -207,8 +207,7 @@ async function main() {
|
|||
console.log(`${GREEN} **** START ****${RESET}`);
|
||||
try {
|
||||
const randomNumber = Math.floor(Math.random() * 100) + 1;
|
||||
// use a random key so that we can run multiple times
|
||||
const assetKey = `asset-${randomNumber}`;
|
||||
let assetKey;
|
||||
|
||||
/** ******* Fabric client init: Using Org1 identity to Org1 Peer ******* */
|
||||
const gatewayOrg1 = await initGatewayForOrg1();
|
||||
|
|
@ -231,20 +230,19 @@ async function main() {
|
|||
// the actual peers that may be active at any given time.
|
||||
const asset_properties = {
|
||||
object_type: 'asset_properties',
|
||||
asset_id: assetKey,
|
||||
color: 'blue',
|
||||
size: 35,
|
||||
salt: Buffer.from(randomNumber.toString()).toString('hex')
|
||||
};
|
||||
const asset_properties_string = JSON.stringify(asset_properties);
|
||||
console.log(`${GREEN}--> Submit Transaction: CreateAsset, ${assetKey} as Org1 - endorsed by Org1${RESET}`);
|
||||
console.log(`${GREEN}--> Submit Transaction: CreateAsset as Org1 - endorsed by Org1${RESET}`);
|
||||
console.log(`${asset_properties_string}`);
|
||||
transaction = contractOrg1.createTransaction('CreateAsset');
|
||||
transaction.setEndorsingOrganizations(org1);
|
||||
transaction.setTransient({
|
||||
asset_properties: Buffer.from(asset_properties_string)
|
||||
});
|
||||
await transaction.submit(assetKey, `Asset ${assetKey} owned by ${org1} is not for sale`);
|
||||
assetKey = await transaction.submit( `Asset owned by ${org1} is not for sale`);
|
||||
console.log(`*** Result: committed, asset ${assetKey} is owned by Org1`);
|
||||
} catch (createError) {
|
||||
console.log(`${RED}*** Failed: CreateAsset - ${createError}${RESET}`);
|
||||
|
|
@ -314,7 +312,8 @@ async function main() {
|
|||
transaction.setTransient({
|
||||
asset_price: Buffer.from(asset_price_string)
|
||||
});
|
||||
await transaction.submit(assetKey);
|
||||
//call agree to sell with desired price and target buyer organization
|
||||
await transaction.submit(assetKey, org2);
|
||||
console.log(`*** Result: committed, Org1 has agreed to sell asset ${assetKey} for 110`);
|
||||
} catch (sellError) {
|
||||
console.log(`${RED}*** Failed: AgreeToSell - ${sellError}${RESET}`);
|
||||
|
|
@ -326,7 +325,6 @@ async function main() {
|
|||
// details may be checked by the chaincode.
|
||||
const asset_properties = {
|
||||
object_type: 'asset_properties',
|
||||
asset_id: assetKey,
|
||||
color: 'blue',
|
||||
size: 35,
|
||||
salt: Buffer.from(randomNumber.toString()).toString('hex')
|
||||
|
|
@ -356,16 +354,24 @@ async function main() {
|
|||
try {
|
||||
// Agree to a buy by Org2
|
||||
const asset_price = {
|
||||
asset_id: assetKey,
|
||||
asset_id: assetKey.toString(),
|
||||
price: 100,
|
||||
trade_id: randomNumber.toString()
|
||||
};
|
||||
const asset_price_string = JSON.stringify(asset_price);
|
||||
const asset_properties = {
|
||||
object_type: 'asset_properties',
|
||||
color: 'blue',
|
||||
size: 35,
|
||||
salt: Buffer.from(randomNumber.toString()).toString('hex')
|
||||
};
|
||||
const asset_properties_string = JSON.stringify(asset_properties);
|
||||
console.log(`${GREEN}--> Submit Transaction: AgreeToBuy, ${assetKey} as Org2 - endorsed by Org2${RESET}`);
|
||||
transaction = contractOrg2.createTransaction('AgreeToBuy');
|
||||
transaction.setEndorsingOrganizations(org2);
|
||||
transaction.setEndorsingOrganizations(org1, org2);
|
||||
transaction.setTransient({
|
||||
asset_price: Buffer.from(asset_price_string)
|
||||
asset_price: Buffer.from(asset_price_string),
|
||||
asset_properties: Buffer.from(asset_properties_string)
|
||||
});
|
||||
await transaction.submit(assetKey);
|
||||
console.log(`*** Result: committed, Org2 has agreed to buy asset ${assetKey} for 100`);
|
||||
|
|
@ -395,14 +401,6 @@ async function main() {
|
|||
// Org1 will try to transfer the asset to Org2
|
||||
// This will fail due to the sell price and the bid price
|
||||
// are not the same
|
||||
const asset_properties = {
|
||||
object_type: 'asset_properties',
|
||||
asset_id: assetKey,
|
||||
color: 'blue',
|
||||
size: 35,
|
||||
salt: Buffer.from(randomNumber.toString()).toString('hex')
|
||||
};
|
||||
const asset_properties_string = JSON.stringify(asset_properties);
|
||||
const asset_price = {
|
||||
asset_id: assetKey,
|
||||
price: 110,
|
||||
|
|
@ -411,11 +409,9 @@ async function main() {
|
|||
const asset_price_string = JSON.stringify(asset_price);
|
||||
|
||||
console.log(`${GREEN}--> Submit Transaction: TransferAsset, ${assetKey} as Org1 - endorsed by Org1${RESET}`);
|
||||
console.log(`${asset_properties_string}`);
|
||||
transaction = contractOrg1.createTransaction('TransferAsset');
|
||||
transaction.setEndorsingOrganizations(org1);
|
||||
transaction.setTransient({
|
||||
asset_properties: Buffer.from(asset_properties_string),
|
||||
asset_price: Buffer.from(asset_price_string)
|
||||
});
|
||||
await transaction.submit(assetKey, org2);
|
||||
|
|
@ -428,18 +424,18 @@ async function main() {
|
|||
// Agree to a sell by Org1
|
||||
// Org1, the seller will agree to the bid price of Org2
|
||||
const asset_price = {
|
||||
asset_id: assetKey,
|
||||
asset_id: assetKey.toString(),
|
||||
price: 100,
|
||||
trade_id: randomNumber.toString()
|
||||
};
|
||||
const asset_price_string = JSON.stringify(asset_price);
|
||||
console.log(`${GREEN}--> Submit Transaction: AgreeToSell, ${assetKey} as Org1 - endorsed by Org1${RESET}`);
|
||||
transaction = contractOrg1.createTransaction('AgreeToSell');
|
||||
transaction.setEndorsingOrganizations(org1);
|
||||
transaction.setEndorsingOrganizations(org1, org2);
|
||||
transaction.setTransient({
|
||||
asset_price: Buffer.from(asset_price_string)
|
||||
});
|
||||
await transaction.submit(assetKey);
|
||||
await transaction.submit(assetKey, org2);
|
||||
console.log(`*** Result: committed, Org1 has agreed to sell asset ${assetKey} for 100`);
|
||||
} catch (sellError) {
|
||||
console.log(`${RED}*** Failed: AgreeToSell - ${sellError}${RESET}`);
|
||||
|
|
@ -460,27 +456,17 @@ async function main() {
|
|||
try {
|
||||
// Org2 user will try to transfer the asset to Org2
|
||||
// This will fail as the owner is Org1
|
||||
const asset_properties = {
|
||||
object_type: 'asset_properties',
|
||||
asset_id: assetKey,
|
||||
color: 'blue',
|
||||
size: 35,
|
||||
salt: Buffer.from(randomNumber.toString()).toString('hex')
|
||||
};
|
||||
const asset_properties_string = JSON.stringify(asset_properties);
|
||||
const asset_price = {
|
||||
asset_id: assetKey,
|
||||
asset_id: assetKey.toString(),
|
||||
price: 100,
|
||||
trade_id: randomNumber.toString()
|
||||
};
|
||||
const asset_price_string = JSON.stringify(asset_price);
|
||||
|
||||
console.log(`${GREEN}--> Submit Transaction: TransferAsset, ${assetKey} as Org2 - endorsed by Org1${RESET}`);
|
||||
console.log(`${asset_properties_string}`);
|
||||
transaction = contractOrg2.createTransaction('TransferAsset');
|
||||
transaction.setEndorsingOrganizations(org1, org2);
|
||||
transaction.setTransient({
|
||||
asset_properties: Buffer.from(asset_properties_string),
|
||||
asset_price: Buffer.from(asset_price_string)
|
||||
});
|
||||
await transaction.submit(assetKey, org2);
|
||||
|
|
@ -492,27 +478,18 @@ async function main() {
|
|||
try {
|
||||
// Org1 will transfer the asset to Org2
|
||||
// This will now complete as the sell price and the bid price are the same
|
||||
const asset_properties = {
|
||||
object_type: 'asset_properties',
|
||||
asset_id: assetKey,
|
||||
color: 'blue',
|
||||
size: 35,
|
||||
salt: Buffer.from(randomNumber.toString()).toString('hex')
|
||||
};
|
||||
const asset_properties_string = JSON.stringify(asset_properties);
|
||||
const asset_price = {
|
||||
asset_id: assetKey,
|
||||
asset_id: assetKey.toString(),
|
||||
price: 100,
|
||||
trade_id: randomNumber.toString()
|
||||
};
|
||||
const asset_price_string = JSON.stringify(asset_price);
|
||||
|
||||
console.log(`${GREEN}--> Submit Transaction: TransferAsset, ${assetKey} as Org1 - endorsed by Org1${RESET}`);
|
||||
console.log(`${asset_properties_string}`);
|
||||
|
||||
transaction = contractOrg1.createTransaction('TransferAsset');
|
||||
transaction.setEndorsingOrganizations(org1, org2);
|
||||
transaction.setTransient({
|
||||
asset_properties: Buffer.from(asset_properties_string),
|
||||
asset_price: Buffer.from(asset_price_string)
|
||||
});
|
||||
await transaction.submit(assetKey, org2);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
|
@ -42,24 +43,29 @@ type receipt struct {
|
|||
timestamp time.Time
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// CreateAsset creates an asset, sets it as owned by the client's org and returns its id
|
||||
// the id of the asset corresponds to the hash of the properties of the asset that are passed by transiet field
|
||||
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, publicDescription string) (string, error) {
|
||||
transientMap, err := ctx.GetStub().GetTransient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting transient: %v", err)
|
||||
return "", fmt.Errorf("error getting transient: %v", err)
|
||||
}
|
||||
|
||||
// Asset properties must be retrieved from the transient field as they are private
|
||||
immutablePropertiesJSON, ok := transientMap["asset_properties"]
|
||||
if !ok {
|
||||
return fmt.Errorf("asset_properties key not found in the transient map")
|
||||
return "", fmt.Errorf("asset_properties key not found in the transient map")
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
hash.Write(immutablePropertiesJSON)
|
||||
assetID := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
// Get client org id and verify it matches peer org id.
|
||||
// In this scenario, client is only authorized to read/write private data from its own peer.
|
||||
clientOrgID, err := getClientOrgID(ctx, true)
|
||||
clientOrgID, err := getClientOrgID(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get verified OrgID: %v", err)
|
||||
return "", fmt.Errorf("failed to get verified OrgID: %v", err)
|
||||
}
|
||||
|
||||
asset := Asset{
|
||||
|
|
@ -70,34 +76,35 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface,
|
|||
}
|
||||
assetBytes, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create asset JSON: %v", err)
|
||||
return "", fmt.Errorf("failed to create asset JSON: %v", err)
|
||||
}
|
||||
|
||||
err = ctx.GetStub().PutState(asset.ID, assetBytes)
|
||||
err = ctx.GetStub().PutState(assetID, assetBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put asset in public data: %v", err)
|
||||
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)
|
||||
endorsingOrgs := []string{clientOrgID}
|
||||
err = setAssetStateBasedEndorsement(ctx, asset.ID, endorsingOrgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed setting state based endorsement for owner: %v", err)
|
||||
return "", fmt.Errorf("failed setting state based endorsement for buyer and seller: %v", err)
|
||||
}
|
||||
|
||||
// Persist private immutable asset properties to owner's private data collection
|
||||
collection := buildCollectionName(clientOrgID)
|
||||
err = ctx.GetStub().PutPrivateData(collection, asset.ID, immutablePropertiesJSON)
|
||||
err = ctx.GetStub().PutPrivateData(collection, assetID, immutablePropertiesJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put Asset private details: %v", err)
|
||||
return "", fmt.Errorf("failed to put Asset private details: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return assetID, nil
|
||||
}
|
||||
|
||||
// ChangePublicDescription updates the assets public description. Only the current owner can update the public description
|
||||
func (s *SmartContract) ChangePublicDescription(ctx contractapi.TransactionContextInterface, assetID string, newDescription string) error {
|
||||
// No need to check client org id matches peer org id, rely on the asset ownership check instead.
|
||||
clientOrgID, err := getClientOrgID(ctx, false)
|
||||
clientOrgID, err := getClientOrgID(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get verified OrgID: %v", err)
|
||||
}
|
||||
|
|
@ -121,14 +128,15 @@ func (s *SmartContract) ChangePublicDescription(ctx contractapi.TransactionConte
|
|||
return ctx.GetStub().PutState(assetID, updatedAssetJSON)
|
||||
}
|
||||
|
||||
// AgreeToSell adds seller's asking price to seller's implicit private data collection
|
||||
func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface, assetID string) error {
|
||||
// AgreeToSell adds seller's asking price to seller's implicit private data collection and requires to specify the next possible buyer
|
||||
// Set the endorsement policy such that seller org and passed target buyer org peers are both required to endorse the tranfer
|
||||
func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface, assetID string, buyerOrgID string) error {
|
||||
asset, err := s.ReadAsset(ctx, assetID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientOrgID, err := getClientOrgID(ctx, true)
|
||||
clientOrgID, err := getClientOrgID(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get verified OrgID: %v", err)
|
||||
}
|
||||
|
|
@ -138,18 +146,48 @@ func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface,
|
|||
return fmt.Errorf("a client from %s cannot sell an asset owned by %s", clientOrgID, asset.OwnerOrg)
|
||||
}
|
||||
|
||||
// Set the endorsement policy such that owner org and seller org peers are both required to endorse the tranfer
|
||||
endorsingOrgs := []string{clientOrgID, buyerOrgID}
|
||||
err = setAssetStateBasedEndorsement(ctx, asset.ID, endorsingOrgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed setting state based endorsement for buyer and future seller: %v", err)
|
||||
}
|
||||
|
||||
return agreeToPrice(ctx, assetID, typeAssetForSale)
|
||||
}
|
||||
|
||||
// AgreeToBuy adds buyer's bid price to buyer's implicit private data collection
|
||||
// AgreeToBuy adds buyer's bid price and asset properties to buyer's implicit private data collection
|
||||
func (s *SmartContract) AgreeToBuy(ctx contractapi.TransactionContextInterface, assetID string) error {
|
||||
transientMap, err := ctx.GetStub().GetTransient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting transient: %v", err)
|
||||
}
|
||||
|
||||
clientOrgID, err := getClientOrgID(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get verified OrgID: %v", err)
|
||||
}
|
||||
|
||||
// Asset properties must be retrieved from the transient field as they are private
|
||||
immutablePropertiesJSON, ok := transientMap["asset_properties"]
|
||||
if !ok {
|
||||
return fmt.Errorf("asset_properties key not found in the transient map")
|
||||
}
|
||||
|
||||
// Persist private immutable asset properties to seller's private data collection
|
||||
collection := buildCollectionName(clientOrgID)
|
||||
err = ctx.GetStub().PutPrivateData(collection, assetID, immutablePropertiesJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put Asset private details: %v", err)
|
||||
}
|
||||
|
||||
return agreeToPrice(ctx, assetID, typeAssetBid)
|
||||
}
|
||||
|
||||
// agreeToPrice adds a bid or ask price to caller's implicit private data collection
|
||||
func agreeToPrice(ctx contractapi.TransactionContextInterface, assetID string, priceType string) error {
|
||||
// In this scenario, client is only authorized to read/write private data from its own peer.
|
||||
clientOrgID, err := getClientOrgID(ctx, true)
|
||||
clientOrgID, err := getClientOrgID(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get verified OrgID: %v", err)
|
||||
}
|
||||
|
|
@ -186,6 +224,7 @@ func agreeToPrice(ctx contractapi.TransactionContextInterface, assetID string, p
|
|||
|
||||
// VerifyAssetProperties Allows a buyer to validate the properties of
|
||||
// an asset against the owner's implicit private data collection
|
||||
// and verifies that the asset properties never changed from the origin of the asset by checking their hash against the assetID
|
||||
func (s *SmartContract) VerifyAssetProperties(ctx contractapi.TransactionContextInterface, assetID string) (bool, error) {
|
||||
transMap, err := ctx.GetStub().GetTransient()
|
||||
if err != nil {
|
||||
|
|
@ -225,13 +264,22 @@ func (s *SmartContract) VerifyAssetProperties(ctx contractapi.TransactionContext
|
|||
)
|
||||
}
|
||||
|
||||
// verify that the hash of the passed immutable properties and on chain hash matches the assetID
|
||||
if !(hex.EncodeToString(immutablePropertiesOnChainHash) == assetID) {
|
||||
return false, fmt.Errorf("hash %x for passed immutable properties %s does match on-chain hash %x but do not match assetID %s: asset was altered from its initial form",
|
||||
calculatedPropertiesHash,
|
||||
immutablePropertiesJSON,
|
||||
immutablePropertiesOnChainHash,
|
||||
assetID)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// TransferAsset checks transfer conditions and then transfers asset state to buyer.
|
||||
// TransferAsset can only be called by current owner
|
||||
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, assetID string, buyerOrgID string) error {
|
||||
clientOrgID, err := getClientOrgID(ctx, false)
|
||||
clientOrgID, err := getClientOrgID(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get verified OrgID: %v", err)
|
||||
}
|
||||
|
|
@ -241,11 +289,6 @@ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterfac
|
|||
return fmt.Errorf("error getting transient data: %v", err)
|
||||
}
|
||||
|
||||
immutablePropertiesJSON, ok := transMap["asset_properties"]
|
||||
if !ok {
|
||||
return fmt.Errorf("asset_properties key not found in the transient map")
|
||||
}
|
||||
|
||||
priceJSON, ok := transMap["asset_price"]
|
||||
if !ok {
|
||||
return fmt.Errorf("asset_price key not found in the transient map")
|
||||
|
|
@ -262,12 +305,12 @@ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterfac
|
|||
return fmt.Errorf("failed to get asset: %v", err)
|
||||
}
|
||||
|
||||
err = verifyTransferConditions(ctx, asset, immutablePropertiesJSON, clientOrgID, buyerOrgID, priceJSON)
|
||||
err = verifyTransferConditions(ctx, asset, clientOrgID, buyerOrgID, priceJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed transfer verification: %v", err)
|
||||
}
|
||||
|
||||
err = transferAssetState(ctx, asset, immutablePropertiesJSON, clientOrgID, buyerOrgID, agreement.Price)
|
||||
err = transferAssetState(ctx, asset, clientOrgID, buyerOrgID, agreement.Price)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed asset transfer: %v", err)
|
||||
}
|
||||
|
|
@ -279,7 +322,6 @@ 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 {
|
||||
|
|
@ -290,27 +332,30 @@ func verifyTransferConditions(ctx contractapi.TransactionContextInterface,
|
|||
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 both buyers and seller on-chain asset defintion hash matches
|
||||
|
||||
collectionSeller := buildCollectionName(clientOrgID)
|
||||
immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, asset.ID)
|
||||
collectionBuyer := buildCollectionName(buyerOrgID)
|
||||
sellerPropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, asset.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err)
|
||||
}
|
||||
if immutablePropertiesOnChainHash == nil {
|
||||
if sellerPropertiesOnChainHash == nil {
|
||||
return fmt.Errorf("asset private properties hash does not exist: %s", asset.ID)
|
||||
}
|
||||
buyerPropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionBuyer, asset.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err)
|
||||
}
|
||||
if buyerPropertiesOnChainHash == nil {
|
||||
return fmt.Errorf("asset private properties hash does not exist: %s", asset.ID)
|
||||
}
|
||||
|
||||
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,
|
||||
if !bytes.Equal(sellerPropertiesOnChainHash, buyerPropertiesOnChainHash) {
|
||||
return fmt.Errorf("on chain hash of seller %x does not match on-chain hash of buyer %x",
|
||||
sellerPropertiesOnChainHash,
|
||||
buyerPropertiesOnChainHash,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -330,7 +375,6 @@ func verifyTransferConditions(ctx contractapi.TransactionContextInterface,
|
|||
}
|
||||
|
||||
// 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: %v", err)
|
||||
|
|
@ -343,7 +387,7 @@ func verifyTransferConditions(ctx contractapi.TransactionContextInterface,
|
|||
return fmt.Errorf("buyer price for %s does not exist", asset.ID)
|
||||
}
|
||||
|
||||
hash = sha256.New()
|
||||
hash := sha256.New()
|
||||
hash.Write(priceJSON)
|
||||
calculatedPriceHash := hash.Sum(nil)
|
||||
|
||||
|
|
@ -369,7 +413,8 @@ func verifyTransferConditions(ctx contractapi.TransactionContextInterface,
|
|||
}
|
||||
|
||||
// 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 {
|
||||
// changes the endorsement for the transferred asset sbe to the new owner org
|
||||
func transferAssetState(ctx contractapi.TransactionContextInterface, asset *Asset, clientOrgID string, buyerOrgID string, price int) error {
|
||||
asset.OwnerOrg = buyerOrgID
|
||||
updatedAsset, err := json.Marshal(asset)
|
||||
if err != nil {
|
||||
|
|
@ -381,8 +426,9 @@ func transferAssetState(ctx contractapi.TransactionContextInterface, asset *Asse
|
|||
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)
|
||||
// Changes the endorsement policy to the new owner org
|
||||
endorsingOrgs := []string{buyerOrgID}
|
||||
err = setAssetStateBasedEndorsement(ctx, asset.ID, endorsingOrgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed setting state based endorsement for new owner: %v", err)
|
||||
}
|
||||
|
|
@ -395,10 +441,6 @@ func transferAssetState(ctx contractapi.TransactionContextInterface, asset *Asse
|
|||
}
|
||||
|
||||
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: %v", err)
|
||||
}
|
||||
|
||||
// Delete the price records for seller
|
||||
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(typeAssetForSale, []string{asset.ID})
|
||||
|
|
@ -466,23 +508,12 @@ func transferAssetState(ctx contractapi.TransactionContextInterface, asset *Asse
|
|||
}
|
||||
|
||||
// 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.
|
||||
func getClientOrgID(ctx contractapi.TransactionContextInterface, verifyOrg bool) (string, error) {
|
||||
func getClientOrgID(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed getting client's orgID: %v", err)
|
||||
}
|
||||
|
||||
if verifyOrg {
|
||||
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return clientOrgID, nil
|
||||
}
|
||||
|
||||
|
|
@ -503,14 +534,13 @@ func verifyClientOrgMatchesPeerOrg(clientOrgID string) error {
|
|||
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.
|
||||
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetID string, orgToEndorse string) error {
|
||||
// setAssetStateBasedEndorsement adds an endorsement policy to an asset so that the passed orgs need to agree upon transfer
|
||||
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetID string, orgsToEndorse []string) error {
|
||||
endorsementPolicy, err := statebased.NewStateEP(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgToEndorse)
|
||||
err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgsToEndorse...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add org to endorsement policy: %v", err)
|
||||
}
|
||||
|
|
@ -526,12 +556,40 @@ func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface,
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetAssetHash Allows a buyer to validate the properties of
|
||||
// an asset against the asset Id and return the hash
|
||||
func (s *SmartContract) GetAssetHashId(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
transientMap, err := ctx.GetStub().GetTransient()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error getting transient: %v", err)
|
||||
}
|
||||
|
||||
// Asset properties must be retrieved from the transient field as they are private
|
||||
propertiesJSON, ok := transientMap["asset_properties"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("asset_properties key not found in the transient map")
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
hash.Write(propertiesJSON)
|
||||
assetID := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
asset, err := s.ReadAsset(ctx, assetID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get asset: %v, asset properies provided do not represent any on chain asset", err)
|
||||
}
|
||||
if asset.ID != assetID {
|
||||
return "", fmt.Errorf("Asset properies provided do not correpond to any on chain asset")
|
||||
}
|
||||
return asset.ID, nil
|
||||
}
|
||||
|
||||
func buildCollectionName(clientOrgID string) string {
|
||||
return fmt.Sprintf("_implicit_org_%s", clientOrgID)
|
||||
}
|
||||
|
||||
func getClientImplicitCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
clientOrgID, err := getClientOrgID(ctx, true)
|
||||
clientOrgID, err := getClientOrgID(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get verified OrgID: %v", err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue