diff --git a/common/interop-protos/common/asset_locks.proto b/common/interop-protos/common/asset_locks.proto index 301121bfe..04a607f5c 100644 --- a/common/interop-protos/common/asset_locks.proto +++ b/common/interop-protos/common/asset_locks.proto @@ -18,7 +18,7 @@ message AssetClaim { } message AssetLockHTLC { - bytes hash = 1; + bytes hashBase64 = 1; uint64 expiryTimeSecs = 2; enum TimeSpec { EPOCH = 0; @@ -28,7 +28,7 @@ message AssetLockHTLC { } message AssetClaimHTLC { - bytes hashPreimage = 1; + bytes hashPreimageBase64 = 1; } message AssetExchangeAgreement { @@ -44,3 +44,17 @@ message FungibleAssetExchangeAgreement { string locker = 3; string recipient = 4; } + +message AssetContractHTLC { + string contractId = 1; + AssetExchangeAgreement agreement = 2; + AssetLockHTLC lock = 3; + AssetClaimHTLC claim = 4; +} + +message FungibleAssetContractHTLC { + string contractId = 1; + FungibleAssetExchangeAgreement agreement = 2; + AssetLockHTLC lock = 3; + AssetClaimHTLC claim = 4; +} diff --git a/core/network/fabric-interop-cc/contracts/interop/manage_assets.go b/core/network/fabric-interop-cc/contracts/interop/manage_assets.go index c78597860..d30dd4081 100644 --- a/core/network/fabric-interop-cc/contracts/interop/manage_assets.go +++ b/core/network/fabric-interop-cc/contracts/interop/manage_assets.go @@ -47,6 +47,13 @@ const( contractIdPrefix = "ContractId_" // prefix for the map, contractId --> asset-key ) +// helper functions to log and return errors +func logThenErrorf(format string, args ...interface{}) error { + errorMsg := fmt.Sprintf(format, args...) + log.Error(errorMsg) + return errors.New(errorMsg) +} + // function to generate a "SHA256" hash in base64 format for a given preimage func generateSHA256HashInBase64Form(preimage string) string { hasher := sha256.New() @@ -68,9 +75,7 @@ func generateContractIdMapKey(contractId string) string { func generateAssetLockKeyAndContractId(ctx contractapi.TransactionContextInterface, assetAgreement *common.AssetExchangeAgreement) (string, string, error) { assetLockKey, err := ctx.GetStub().CreateCompositeKey("AssetExchangeContract", []string{assetAgreement.Type, assetAgreement.Id}) if err != nil { - errorMsg := fmt.Sprintf("error while creating composite key: %+v", err) - log.Error(errorMsg) - return "", "", errors.New(errorMsg) + return "", "", logThenErrorf("error while creating composite key: %+v", err) } contractId := generateSHA256HashInBase64Form(assetLockKey) @@ -98,7 +103,7 @@ func (s *SmartContract) LockAsset(ctx contractapi.TransactionContextInterface, a return "", err } //display the requested asset agreement - log.Info(fmt.Sprintf("assetExchangeAgreement: %+v\n", assetAgreement)) + log.Infof("assetExchangeAgreement: %+v\n", assetAgreement) lockInfoHTLC := &common.AssetLockHTLC{} err = proto.Unmarshal([]byte(lockInfoBytes), lockInfoHTLC) @@ -107,12 +112,10 @@ func (s *SmartContract) LockAsset(ctx contractapi.TransactionContextInterface, a return "", err } //display the passed lock information - log.Info(fmt.Sprintf("lockInfoHTLC: %+v\n", lockInfoHTLC)) + log.Infof("lockInfoHTLC: %+v\n", lockInfoHTLC) if lockInfoHTLC.TimeSpec != common.AssetLockHTLC_EPOCH { - errorMsg := "only EPOCH time is supported at present" - log.Error(errorMsg) - return "", errors.New(errorMsg) + return "", logThenErrorf("only EPOCH time is supported at present") } assetLockKey, contractId, err := generateAssetLockKeyAndContractId(ctx, assetAgreement) @@ -121,7 +124,7 @@ func (s *SmartContract) LockAsset(ctx contractapi.TransactionContextInterface, a return "", err } - assetLockVal := AssetLockValue{Locker: assetAgreement.Locker, Recipient: assetAgreement.Recipient, Hash: string(lockInfoHTLC.Hash), ExpiryTimeSecs: lockInfoHTLC.ExpiryTimeSecs} + assetLockVal := AssetLockValue{Locker: assetAgreement.Locker, Recipient: assetAgreement.Recipient, Hash: string(lockInfoHTLC.HashBase64), ExpiryTimeSecs: lockInfoHTLC.ExpiryTimeSecs} assetLockValBytes, err := ctx.GetStub().GetState(assetLockKey) if err != nil { @@ -130,16 +133,12 @@ func (s *SmartContract) LockAsset(ctx contractapi.TransactionContextInterface, a } if assetLockValBytes != nil { - errorMsg := fmt.Sprintf("asset of type %s and ID %s is already locked", assetAgreement.Type, assetAgreement.Id) - log.Error(errorMsg) - return "", errors.New(errorMsg) + return "", logThenErrorf("asset of type %s and ID %s is already locked", assetAgreement.Type, assetAgreement.Id) } assetLockValBytes, err = json.Marshal(assetLockVal) if err != nil { - errorMsg := fmt.Sprintf("marshal error: %s", err) - log.Error(errorMsg) - return "", errors.New(errorMsg) + return "", logThenErrorf("marshal error: %+v", err) } err = ctx.GetStub().PutState(assetLockKey, assetLockValBytes) @@ -150,9 +149,7 @@ func (s *SmartContract) LockAsset(ctx contractapi.TransactionContextInterface, a assetLockKeyBytes, err := json.Marshal(assetLockKey) if err != nil { - errorMsg := fmt.Sprintf("marshal error: %s", err) - log.Error(errorMsg) - return "", errors.New(errorMsg) + return "", logThenErrorf("marshal error: %+v", err) } err = ctx.GetStub().PutState(generateContractIdMapKey(string(contractId)), assetLockKeyBytes) @@ -173,7 +170,7 @@ func (s *SmartContract) UnLockAsset(ctx contractapi.TransactionContextInterface, return err } //display the requested asset agreement - log.Info(fmt.Sprintf("assetExchangeAgreement: %+v\n", assetAgreement)) + log.Infof("assetExchangeAgreement: %+v\n", assetAgreement) assetLockKey, _, err := generateAssetLockKeyAndContractId(ctx, assetAgreement) if err != nil { @@ -187,7 +184,7 @@ func (s *SmartContract) UnLockAsset(ctx contractapi.TransactionContextInterface, return err } - if assetLockValBytes == nil { + if assetLockValBytes == nil { errorMsg := fmt.Sprintf("no asset of type %s and ID %s is locked", assetAgreement.Type, assetAgreement.Id) log.Error(errorMsg) return errors.New(errorMsg) @@ -209,7 +206,7 @@ func (s *SmartContract) UnLockAsset(ctx contractapi.TransactionContextInterface, // Check if expiry time is elapsed currentTimeSecs := uint64(time.Now().Unix()) - if uint64(currentTimeSecs) < assetLockVal.ExpiryTimeSecs { + if currentTimeSecs < assetLockVal.ExpiryTimeSecs { errorMsg := fmt.Sprintf("cannot unlock asset of type %s and ID %s as the expiry time is not yet elapsed", assetAgreement.Type, assetAgreement.Id) log.Error(errorMsg) return errors.New(errorMsg) @@ -235,7 +232,7 @@ func (s *SmartContract) IsAssetLocked(ctx contractapi.TransactionContextInterfac return false, err } //display the requested asset agreement - log.Info(fmt.Sprintf("assetExchangeAgreement: %+v\n", assetAgreement)) + log.Infof("assetExchangeAgreement: %+v\n", assetAgreement) assetLockKey, _, err := generateAssetLockKeyAndContractId(ctx, assetAgreement) if err != nil { @@ -262,11 +259,11 @@ func (s *SmartContract) IsAssetLocked(ctx contractapi.TransactionContextInterfac log.Error(errorMsg) return false, errors.New(errorMsg) } - log.Info(fmt.Sprintf("assetLockVal: %+v\n", assetLockVal)) + log.Infof("assetLockVal: %+v\n", assetLockVal) // Check if expiry time is elapsed currentTimeSecs := uint64(time.Now().Unix()) - if uint64(currentTimeSecs) >= assetLockVal.ExpiryTimeSecs { + if currentTimeSecs >= assetLockVal.ExpiryTimeSecs { errorMsg := fmt.Sprintf("expiry time for asset of type %s and ID %s is already elapsed", assetAgreement.Type, assetAgreement.Id) log.Error(errorMsg) return false, errors.New(errorMsg) @@ -307,9 +304,9 @@ func checkIfCorrectPreimage(preimageBase64 string, hashBase64 string) (bool, err shaHashBase64 := generateSHA256HashInBase64Form(string(preimage)) if shaHashBase64 == hashBase64 { - log.Info(fmt.Sprintf("%s: preimage %s is passed correctly.\n", funName, preimage)) + log.Infof("%s: preimage %s is passed correctly.\n", funName, preimage) } else { - log.Info(fmt.Sprintf("%s: preimage %s is not passed correctly.\n", funName, preimage)) + log.Infof("%s: preimage %s is not passed correctly.\n", funName, preimage) return false, nil } return true, nil @@ -325,7 +322,7 @@ func (s *SmartContract) ClaimAsset(ctx contractapi.TransactionContextInterface, return err } // display the requested asset agreement - log.Info(fmt.Sprintf("assetExchangeAgreement: %+v\n", assetAgreement)) + log.Infof("assetExchangeAgreement: %+v\n", assetAgreement) claimInfo := &common.AssetClaimHTLC{} err = proto.Unmarshal([]byte(claimInfoBytes), claimInfo) @@ -336,7 +333,7 @@ func (s *SmartContract) ClaimAsset(ctx contractapi.TransactionContextInterface, } // display the claim information - log.Info(fmt.Sprintf("claimInfo: %+v\n", claimInfo)) + log.Infof("claimInfo: %+v\n", claimInfo) assetLockKey, _, err := generateAssetLockKeyAndContractId(ctx, assetAgreement) if err != nil { @@ -372,14 +369,14 @@ func (s *SmartContract) ClaimAsset(ctx contractapi.TransactionContextInterface, // Check if expiry time is elapsed currentTimeSecs := uint64(time.Now().Unix()) - if uint64(currentTimeSecs) >= assetLockVal.ExpiryTimeSecs { + if currentTimeSecs >= assetLockVal.ExpiryTimeSecs { errorMsg := fmt.Sprintf("cannot claim asset of type %s and ID %s as the expiry time is already elapsed", assetAgreement.Type, assetAgreement.Id) log.Error(errorMsg) return errors.New(errorMsg) } // compute the hash from the preimage - isCorrectPreimage, err := checkIfCorrectPreimage(string(claimInfo.HashPreimage), string(assetLockVal.Hash)) + isCorrectPreimage, err := checkIfCorrectPreimage(string(claimInfo.HashPreimageBase64), string(assetLockVal.Hash)) if err != nil { errorMsg := fmt.Sprintf("claim asset of type %s and ID %s error: %v", assetAgreement.Type, assetAgreement.Id, err) log.Error(errorMsg) @@ -452,7 +449,7 @@ func (s *SmartContract) UnLockAssetUsingContractId(ctx contractapi.TransactionCo // Check if expiry time is elapsed currentTimeSecs := uint64(time.Now().Unix()) - if uint64(currentTimeSecs) < assetLockVal.ExpiryTimeSecs { + if currentTimeSecs < assetLockVal.ExpiryTimeSecs { errorMsg := fmt.Sprintf("cannot unlock asset associated with the contractId %s as the expiry time is not yet elapsed", contractId) log.Error(errorMsg) return errors.New(errorMsg) @@ -493,18 +490,18 @@ func (s *SmartContract) ClaimAssetUsingContractId(ctx contractapi.TransactionCon } // display the claim information - log.Info(fmt.Sprintf("claimInfo: %+v\n", claimInfo)) + log.Infof("claimInfo: %+v\n", claimInfo) // Check if expiry time is elapsed currentTimeSecs := uint64(time.Now().Unix()) - if uint64(currentTimeSecs) >= assetLockVal.ExpiryTimeSecs { + if currentTimeSecs >= assetLockVal.ExpiryTimeSecs { errorMsg := fmt.Sprintf("cannot claim asset associated with contractId %s as the expiry time is already elapsed", contractId) log.Error(errorMsg) return errors.New(errorMsg) } // compute the hash from the preimage - isCorrectPreimage, err := checkIfCorrectPreimage(string(claimInfo.HashPreimage), string(assetLockVal.Hash)) + isCorrectPreimage, err := checkIfCorrectPreimage(string(claimInfo.HashPreimageBase64), string(assetLockVal.Hash)) if err != nil { errorMsg := fmt.Sprintf("claim asset associated with contractId %s failed with error: %v", contractId, err) log.Error(errorMsg) @@ -545,7 +542,7 @@ func (s *SmartContract) IsAssetLockedQueryUsingContractId(ctx contractapi.Transa // Check if expiry time is elapsed currentTimeSecs := uint64(time.Now().Unix()) - if uint64(currentTimeSecs) >= assetLockVal.ExpiryTimeSecs { + if currentTimeSecs >= assetLockVal.ExpiryTimeSecs { errorMsg := fmt.Sprintf("expiry time for asset associated with contractId %s is already elapsed", contractId) log.Error(errorMsg) return false, errors.New(errorMsg) @@ -577,7 +574,7 @@ func (s *SmartContract) LockFungibleAsset(ctx contractapi.TransactionContextInte } //display the passed lock information - log.Info(fmt.Sprintf("lockInfoHTLC: %+v\n", lockInfoHTLC)) + log.Infof("lockInfoHTLC: %+v\n", lockInfoHTLC) if lockInfoHTLC.TimeSpec != common.AssetLockHTLC_EPOCH { errorMsg := "only EPOCH time is supported at present" @@ -589,7 +586,7 @@ func (s *SmartContract) LockFungibleAsset(ctx contractapi.TransactionContextInte contractId := generateFungibleAssetLockContractId(ctx, assetAgreement) assetLockVal := FungibleAssetLockValue{Type: assetAgreement.Type, NumUnits: assetAgreement.NumUnits, Locker: assetAgreement.Locker, - Recipient: assetAgreement.Recipient, Hash: string(lockInfoHTLC.Hash), ExpiryTimeSecs: lockInfoHTLC.ExpiryTimeSecs} + Recipient: assetAgreement.Recipient, Hash: string(lockInfoHTLC.HashBase64), ExpiryTimeSecs: lockInfoHTLC.ExpiryTimeSecs} assetLockValBytes, err := ctx.GetStub().GetState(contractId) if err != nil { @@ -660,7 +657,7 @@ func (s *SmartContract) IsFungibleAssetLocked(ctx contractapi.TransactionContext // Check if expiry time is elapsed currentTimeSecs := uint64(time.Now().Unix()) - if uint64(currentTimeSecs) >= assetLockVal.ExpiryTimeSecs { + if currentTimeSecs >= assetLockVal.ExpiryTimeSecs { errorMsg := fmt.Sprintf("expiry time for fungible asset associated with contractId %s is already elapsed", contractId) log.Error(errorMsg) return false, errors.New(errorMsg) @@ -687,18 +684,18 @@ func (s *SmartContract) ClaimFungibleAsset(ctx contractapi.TransactionContextInt } // display the claim information - log.Info(fmt.Sprintf("claimInfo: %+v\n", claimInfo)) + log.Infof("claimInfo: %+v\n", claimInfo) // Check if expiry time is elapsed currentTimeSecs := uint64(time.Now().Unix()) - if uint64(currentTimeSecs) >= assetLockVal.ExpiryTimeSecs { + if currentTimeSecs >= assetLockVal.ExpiryTimeSecs { errorMsg := fmt.Sprintf("cannot claim fungible asset associated with contractId %s as the expiry time is already elapsed", contractId) log.Error(errorMsg) return errors.New(errorMsg) } // compute the hash from the preimage - isCorrectPreimage, err := checkIfCorrectPreimage(string(claimInfo.HashPreimage), string(assetLockVal.Hash)) + isCorrectPreimage, err := checkIfCorrectPreimage(string(claimInfo.HashPreimageBase64), string(assetLockVal.Hash)) if err != nil { errorMsg := fmt.Sprintf("claim fungible asset associated with contractId %s failed with error: %v", contractId, err) log.Error(errorMsg) @@ -732,7 +729,7 @@ func (s *SmartContract) UnLockFungibleAsset(ctx contractapi.TransactionContextIn // Check if expiry time is elapsed currentTimeSecs := uint64(time.Now().Unix()) - if uint64(currentTimeSecs) < assetLockVal.ExpiryTimeSecs { + if currentTimeSecs < assetLockVal.ExpiryTimeSecs { errorMsg := fmt.Sprintf("cannot unlock fungible asset associated with the contractId %s as the expiry time is not yet elapsed", contractId) log.Error(errorMsg) return errors.New(errorMsg) diff --git a/core/network/fabric-interop-cc/contracts/interop/manage_assets_test.go b/core/network/fabric-interop-cc/contracts/interop/manage_assets_test.go index 86c70ae25..58ee8146a 100644 --- a/core/network/fabric-interop-cc/contracts/interop/manage_assets_test.go +++ b/core/network/fabric-interop-cc/contracts/interop/manage_assets_test.go @@ -35,7 +35,7 @@ func TestLockAsset(t *testing.T) { currentTimeSecs := uint64(time.Now().Unix()) lockInfoHTLC := &common.AssetLockHTLC { - Hash: []byte(hashBase64), + HashBase64: []byte(hashBase64), // lock for next 5 minutes ExpiryTimeSecs: currentTimeSecs + defaultTimeLockSecs, TimeSpec: common.AssetLockHTLC_EPOCH, @@ -66,7 +66,7 @@ func TestLockAsset(t *testing.T) { // no need to set chaincodeStub.GetStateReturns below since the error is hit before GetState() ledger access in LockAsset() lockInfoHTLC = &common.AssetLockHTLC { - Hash: []byte(hashBase64), + HashBase64: []byte(hashBase64), // lock for next 5 mintues ExpiryTimeSecs: currentTimeSecs + defaultTimeLockSecs, // TimeSpec of AssetLockHTLC_DURATION is not currently supported @@ -91,7 +91,7 @@ func TestUnLockAsset(t *testing.T) { currentTimeSecs := uint64(time.Now().Unix()) lockInfoHTLC := &common.AssetLockHTLC { - Hash: []byte(hashBase64), + HashBase64: []byte(hashBase64), // lock for sometime in the past for testing UnLockAsset functionality ExpiryTimeSecs: currentTimeSecs - defaultTimeLockSecs, TimeSpec: common.AssetLockHTLC_EPOCH, @@ -158,7 +158,7 @@ func TestIsAssetLocked(t *testing.T) { currentTimeSecs := uint64(time.Now().Unix()) lockInfoHTLC := &common.AssetLockHTLC { - Hash: []byte(hashBase64), + HashBase64: []byte(hashBase64), ExpiryTimeSecs: currentTimeSecs + defaultTimeLockSecs, TimeSpec: common.AssetLockHTLC_EPOCH, } @@ -308,7 +308,7 @@ func TestClaimAsset(t *testing.T) { currentTimeSecs := uint64(time.Now().Unix()) lockInfoHTLC := &common.AssetLockHTLC { - Hash: []byte(hashBase64), + HashBase64: []byte(hashBase64), ExpiryTimeSecs: currentTimeSecs + defaultTimeLockSecs, TimeSpec: common.AssetLockHTLC_EPOCH, } @@ -323,7 +323,7 @@ func TestClaimAsset(t *testing.T) { assetAgreementBytes, _ := proto.Marshal(assetAgreement) claimInfo := &common.AssetClaimHTLC { - HashPreimage: []byte(preimageBase64), + HashPreimageBase64: []byte(preimageBase64), } claimInfoBytes, _ := proto.Marshal(claimInfo) @@ -352,7 +352,7 @@ func TestClaimAsset(t *testing.T) { wrongPreimage := "abc" wrongPreimageBase64 := base64.StdEncoding.EncodeToString([]byte(wrongPreimage)) wrongClaimInfo := &common.AssetClaimHTLC { - HashPreimage: []byte(wrongPreimageBase64), + HashPreimageBase64: []byte(wrongPreimageBase64), } wrongClaimInfoBytes, _ := proto.Marshal(wrongClaimInfo) @@ -499,7 +499,7 @@ func TestClaimAssetUsingContractId(t *testing.T) { assetLockKey, contractId, _ := generateAssetLockKeyAndContractId(ctx, assetAgreement) claimInfo := &common.AssetClaimHTLC { - HashPreimage: []byte(preimageBase64), + HashPreimageBase64: []byte(preimageBase64), } claimInfoBytes, _ := proto.Marshal(claimInfo) @@ -549,7 +549,7 @@ func TestClaimAssetUsingContractId(t *testing.T) { wrongPreimage := "abc" wrongPreimageBase64 := base64.StdEncoding.EncodeToString([]byte(wrongPreimage)) wrongClaimInfo := &common.AssetClaimHTLC { - HashPreimage: []byte(wrongPreimageBase64), + HashPreimageBase64: []byte(wrongPreimageBase64), } wrongClaimInfoBytes, _ := proto.Marshal(wrongClaimInfo) assetLockVal = AssetLockValue{Locker: locker, Recipient: recipient, Hash: hashBase64, ExpiryTimeSecs: currentTimeSecs + defaultTimeLockSecs} @@ -692,7 +692,7 @@ func TestLockFungibleAsset(t *testing.T) { // Test failure with TimeSpec that is part of lock information not being currently supported // no need to set chaincodeStub.GetStateReturns below since the error is hit before GetState() ledger access lockInfoHTLC := &common.AssetLockHTLC { - Hash: []byte(hashBase64), + HashBase64: []byte(hashBase64), // lock for next 5 mintues ExpiryTimeSecs: currentTimeSecs + defaultTimeLockSecs, // TimeSpec of AssetLockHTLC_DURATION is not currently supported @@ -707,7 +707,7 @@ func TestLockFungibleAsset(t *testing.T) { // Test failure with GetState(contractId) fail to read the world state chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve contractId %s", contractId)) lockInfoHTLC = &common.AssetLockHTLC { - Hash: []byte(hashBase64), + HashBase64: []byte(hashBase64), // lock for next 5 mintues ExpiryTimeSecs: currentTimeSecs + defaultTimeLockSecs, // TimeSpec of AssetLockHTLC_EPOCH is only supported currently @@ -721,7 +721,7 @@ func TestLockFungibleAsset(t *testing.T) { // Test failure with contractId already existing on the ledger assetLockVal := FungibleAssetLockValue{Type: assetType, NumUnits: numUnits, Locker: locker, Recipient: recipient, - Hash: string(lockInfoHTLC.Hash), ExpiryTimeSecs: lockInfoHTLC.ExpiryTimeSecs} + Hash: string(lockInfoHTLC.HashBase64), ExpiryTimeSecs: lockInfoHTLC.ExpiryTimeSecs} assetLockValBytes, _ := json.Marshal(assetLockVal) chaincodeStub.GetStateReturns(assetLockValBytes, nil) _, err = interopcc.LockFungibleAsset(ctx, string(assetAgreementBytes), string(lockInfoBytes)) @@ -824,7 +824,7 @@ func TestClaimFungibleAsset(t *testing.T) { contractId := generateFungibleAssetLockContractId(ctx, assetAgreement) claimInfo := &common.AssetClaimHTLC { - HashPreimage: []byte(preimageBase64), + HashPreimageBase64: []byte(preimageBase64), } claimInfoBytes, _ := proto.Marshal(claimInfo) @@ -856,7 +856,7 @@ func TestClaimFungibleAsset(t *testing.T) { wrongPreimage := "abc" wrongPreimageBase64 := base64.StdEncoding.EncodeToString([]byte(wrongPreimage)) wrongClaimInfo := &common.AssetClaimHTLC { - HashPreimage: []byte(wrongPreimageBase64), + HashPreimageBase64: []byte(wrongPreimageBase64), } wrongClaimInfoBytes, _ := proto.Marshal(wrongClaimInfo) assetLockVal = FungibleAssetLockValue{Type: assetType, NumUnits: numUnits, Locker: locker, Recipient: recipient, diff --git a/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks.go b/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks.go index 10c3b6f9b..ecf7b0bdd 100644 --- a/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks.go +++ b/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks.go @@ -91,7 +91,7 @@ func (am *AssetManagement) LockAsset(stub shim.ChaincodeStubInterface, assetAgre log.Error(errorMsg) return false, errors.New(errorMsg) } - if len(lockInfoHTLC.Hash) == 0 { + if len(lockInfoHTLC.HashBase64) == 0 { errorMsg = "empty lock hash value" log.Error(errorMsg) return false, errors.New(errorMsg) @@ -161,7 +161,7 @@ func (am *AssetManagement) LockFungibleAsset(stub shim.ChaincodeStubInterface, a log.Error(errorMsg) return "", errors.New(errorMsg) } - if len(lockInfoHTLC.Hash) == 0 { + if len(lockInfoHTLC.HashBase64) == 0 { errorMsg = "empty lock hash value" log.Error(errorMsg) return "", errors.New(errorMsg) @@ -317,7 +317,7 @@ func (am *AssetManagement) ClaimAsset(stub shim.ChaincodeStubInterface, assetAgr log.Error(err.Error()) return false, err } - if len(claimInfoHTLC.HashPreimage) == 0 { + if len(claimInfoHTLC.HashPreimageBase64) == 0 { errorMsg = "empty lock hash preimage" log.Error(errorMsg) return false, errors.New(errorMsg) @@ -372,7 +372,7 @@ func (am *AssetManagement) ClaimFungibleAsset(stub shim.ChaincodeStubInterface, log.Error(err.Error()) return false, err } - if len(claimInfoHTLC.HashPreimage) == 0 { + if len(claimInfoHTLC.HashPreimageBase64) == 0 { errorMsg = "empty lock hash preimage" log.Error(errorMsg) return false, errors.New(errorMsg) diff --git a/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks_contract.go b/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks_contract.go index f54a6621b..1ecb2a7d3 100644 --- a/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks_contract.go +++ b/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks_contract.go @@ -30,10 +30,6 @@ func (amc *AssetManagementContract) Configure(interopChaincodeId string) { // Ledger transaction (invocation) functions -func (amc *AssetManagementContract) AddFungibleAssetCount(ctx contractapi.TransactionContextInterface, assetType string, numUnits uint64) (bool, error) { - return amc.assetManagement.AddFungibleAssetCount(ctx.GetStub(), assetType, numUnits) -} - func (amc *AssetManagementContract) LockAsset(ctx contractapi.TransactionContextInterface, assetAgreementSerializedProto, lockInfoSerializedProto string) (bool, error) { assetAgreement := &common.AssetExchangeAgreement{} if len(assetAgreementSerializedProto) == 0 { @@ -55,31 +51,74 @@ func (amc *AssetManagementContract) LockAsset(ctx contractapi.TransactionContext log.Error(err.Error()) return false, err } - return amc.assetManagement.LockAsset(ctx.GetStub(), assetAgreement, lockInfo) + + // The below 'SetEvent' should be the last in a given transaction (if this function is being called by another), otherwise it will be overridden + retVal, err := amc.assetManagement.LockAsset(ctx.GetStub(), assetAgreement, lockInfo) + if retVal && err == nil { + lockInfoHTLC := &common.AssetLockHTLC{} + err = proto.Unmarshal(lockInfo.LockInfo, lockInfoHTLC) + if err == nil { + contractInfo := &common.AssetContractHTLC{ + Agreement: assetAgreement, + Lock: lockInfoHTLC, + } + contractInfoBytes, err := proto.Marshal(contractInfo) + if err == nil { + err = ctx.GetStub().SetEvent("LockAsset", contractInfoBytes) + } + } + if err != nil { + log.Warn("Unable to set 'LockAsset' event") + log.Warn(err.Error()) + } + } + return retVal, err } -func (amc *AssetManagementContract) LockFungibleAsset(ctx contractapi.TransactionContextInterface, fungibleAssetExchangeAgreementSerializedProto, lockInfoSerializedProto string) (bool, error) { +func (amc *AssetManagementContract) LockFungibleAsset(ctx contractapi.TransactionContextInterface, fungibleAssetExchangeAgreementSerializedProto, lockInfoSerializedProto string) (string, error) { assetAgreement := &common.FungibleAssetExchangeAgreement{} if len(fungibleAssetExchangeAgreementSerializedProto) == 0 { log.Error("empty asset agreement") - return false, fmt.Errorf("empty asset agreement") + return "", fmt.Errorf("empty asset agreement") } err := proto.Unmarshal([]byte(fungibleAssetExchangeAgreementSerializedProto), assetAgreement) if err != nil { log.Error(err.Error()) - return false, err + return "", err } lockInfo := &common.AssetLock{} if len(lockInfoSerializedProto) == 0 { log.Error("empty lock info") - return false, fmt.Errorf("empty lock info") + return "", fmt.Errorf("empty lock info") } err = proto.Unmarshal([]byte(lockInfoSerializedProto), lockInfo) if err != nil { log.Error(err.Error()) - return false, err + return "", err + } + + // The below 'SetEvent' should be the last in a given transaction (if this function is being called by another), otherwise it will be overridden + retVal, err := amc.assetManagement.LockFungibleAsset(ctx.GetStub(), assetAgreement, lockInfo) + if err == nil { + lockInfoHTLC := &common.AssetLockHTLC{} + err = proto.Unmarshal(lockInfo.LockInfo, lockInfoHTLC) + if err == nil { + contractInfo := &common.FungibleAssetContractHTLC{ + ContractId: retVal, + Agreement: assetAgreement, + Lock: lockInfoHTLC, + } + contractInfoBytes, err := proto.Marshal(contractInfo) + if err == nil { + err = ctx.GetStub().SetEvent("LockFungibleAsset", contractInfoBytes) + } + } + if err != nil { + log.Warn("Unable to set 'LockFungibleAsset' event") + log.Warn(err.Error()) + } } - return amc.assetManagement.LockFungibleAsset(ctx.GetStub(), assetAgreement, lockInfo) + return retVal, err } func (amc *AssetManagementContract) IsAssetLocked(ctx contractapi.TransactionContextInterface, assetAgreementSerializedProto string) (bool, error) { @@ -96,18 +135,12 @@ func (amc *AssetManagementContract) IsAssetLocked(ctx contractapi.TransactionCon return amc.assetManagement.IsAssetLocked(ctx.GetStub(), assetAgreement) } -func (amc *AssetManagementContract) IsFungibleAssetLocked(ctx contractapi.TransactionContextInterface, fungibleAssetExchangeAgreementSerializedProto string) (bool, error) { - assetAgreement := &common.FungibleAssetExchangeAgreement{} - if len(fungibleAssetExchangeAgreementSerializedProto) == 0 { - log.Error("empty asset agreement") - return false, fmt.Errorf("empty asset agreement") - } - err := proto.Unmarshal([]byte(fungibleAssetExchangeAgreementSerializedProto), assetAgreement) - if err != nil { - log.Error(err.Error()) - return false, err +func (amc *AssetManagementContract) IsFungibleAssetLocked(ctx contractapi.TransactionContextInterface, contractId string) (bool, error) { + if len(contractId) == 0 { + log.Error("empty contract id") + return false, fmt.Errorf("empty contract id") } - return amc.assetManagement.IsFungibleAssetLocked(ctx.GetStub(), assetAgreement) + return amc.assetManagement.IsFungibleAssetLocked(ctx.GetStub(), contractId) } func (amc *AssetManagementContract) ClaimAsset(ctx contractapi.TransactionContextInterface, assetAgreementSerializedProto, claimInfoSerializedProto string) (bool, error) { @@ -131,31 +164,67 @@ func (amc *AssetManagementContract) ClaimAsset(ctx contractapi.TransactionContex log.Error(err.Error()) return false, err } - return amc.assetManagement.ClaimAsset(ctx.GetStub(), assetAgreement, claimInfo) -} -func (amc *AssetManagementContract) ClaimFungibleAsset(ctx contractapi.TransactionContextInterface, fungibleAssetExchangeAgreementSerializedProto, claimInfoSerializedProto string) (bool, error) { - assetAgreement := &common.FungibleAssetExchangeAgreement{} - if len(fungibleAssetExchangeAgreementSerializedProto) == 0 { - log.Error("empty asset agreement") - return false, fmt.Errorf("empty asset agreement") + // The below 'SetEvent' should be the last in a given transaction (if this function is being called by another), otherwise it will be overridden + retVal, err := amc.assetManagement.ClaimAsset(ctx.GetStub(), assetAgreement, claimInfo) + if retVal && err == nil { + claimInfoHTLC := &common.AssetClaimHTLC{} + err = proto.Unmarshal(claimInfo.ClaimInfo, claimInfoHTLC) + if err == nil { + contractInfo := &common.AssetContractHTLC{ + Agreement: assetAgreement, + Claim: claimInfoHTLC, + } + contractInfoBytes, err := proto.Marshal(contractInfo) + if err == nil { + err = ctx.GetStub().SetEvent("ClaimAsset", contractInfoBytes) + } + } + if err != nil { + log.Warn("Unable to set 'ClaimAsset' event") + log.Warn(err.Error()) + } } - err := proto.Unmarshal([]byte(fungibleAssetExchangeAgreementSerializedProto), assetAgreement) - if err != nil { - log.Error(err.Error()) - return false, err + return retVal, err +} + +func (amc *AssetManagementContract) ClaimFungibleAsset(ctx contractapi.TransactionContextInterface, contractId, claimInfoSerializedProto string) (bool, error) { + if len(contractId) == 0 { + log.Error("empty contract id") + return false, fmt.Errorf("empty contract id") } claimInfo := &common.AssetClaim{} if len(claimInfoSerializedProto) == 0 { log.Error("empty claim info") return false, fmt.Errorf("empty claim info") } - err = proto.Unmarshal([]byte(claimInfoSerializedProto), claimInfo) + err := proto.Unmarshal([]byte(claimInfoSerializedProto), claimInfo) if err != nil { log.Error(err.Error()) return false, err } - return amc.assetManagement.ClaimFungibleAsset(ctx.GetStub(), assetAgreement, claimInfo) + + // The below 'SetEvent' should be the last in a given transaction (if this function is being called by another), otherwise it will be overridden + retVal, err := amc.assetManagement.ClaimFungibleAsset(ctx.GetStub(), contractId, claimInfo) + if retVal && err == nil { + claimInfoHTLC := &common.AssetClaimHTLC{} + err = proto.Unmarshal(claimInfo.ClaimInfo, claimInfoHTLC) + if err == nil { + contractInfo := &common.FungibleAssetContractHTLC{ + ContractId: contractId, + Claim: claimInfoHTLC, + } + contractInfoBytes, err := proto.Marshal(contractInfo) + if err == nil { + err = ctx.GetStub().SetEvent("ClaimFungibleAsset", contractInfoBytes) + } + } + if err != nil { + log.Warn("Unable to set 'ClaimFungibleAsset' event") + log.Warn(err.Error()) + } + } + return retVal, err } func (amc *AssetManagementContract) UnlockAsset(ctx contractapi.TransactionContextInterface, assetAgreementSerializedProto string) (bool, error) { @@ -169,34 +238,52 @@ func (amc *AssetManagementContract) UnlockAsset(ctx contractapi.TransactionConte log.Error(err.Error()) return false, err } - return amc.assetManagement.UnlockAsset(ctx.GetStub(), assetAgreement) + + // The below 'SetEvent' should be the last in a given transaction (if this function is being called by another), otherwise it will be overridden + retVal, err := amc.assetManagement.UnlockAsset(ctx.GetStub(), assetAgreement) + if retVal && err == nil { + contractInfo := &common.AssetContractHTLC{ + Agreement: assetAgreement, + } + contractInfoBytes, err := proto.Marshal(contractInfo) + if err == nil { + err = ctx.GetStub().SetEvent("UnlockAsset", contractInfoBytes) + } + if err != nil { + log.Warn("Unable to set 'UnlockAsset' event") + log.Warn(err.Error()) + } + } + return retVal, err } -func (amc *AssetManagementContract) UnlockFungibleAsset(ctx contractapi.TransactionContextInterface, fungibleAssetExchangeAgreementSerializedProto string) (bool, error) { - assetAgreement := &common.FungibleAssetExchangeAgreement{} - if len(fungibleAssetExchangeAgreementSerializedProto) == 0 { - log.Error("empty asset agreement") - return false, fmt.Errorf("empty asset agreement") +func (amc *AssetManagementContract) UnlockFungibleAsset(ctx contractapi.TransactionContextInterface, contractId string) (bool, error) { + if len(contractId) == 0 { + log.Error("empty contract id") + return false, fmt.Errorf("empty contract id") } - err := proto.Unmarshal([]byte(fungibleAssetExchangeAgreementSerializedProto), assetAgreement) - if err != nil { - log.Error(err.Error()) - return false, err + + // The below 'SetEvent' should be the last in a given transaction (if this function is being called by another), otherwise it will be overridden + retVal, err := amc.assetManagement.UnlockFungibleAsset(ctx.GetStub(), contractId) + if retVal && err == nil { + contractInfo := &common.AssetContractHTLC{ + ContractId: contractId, + } + contractInfoBytes, err := proto.Marshal(contractInfo) + if err == nil { + err = ctx.GetStub().SetEvent("UnlockFungibleAsset", contractInfoBytes) + } + if err != nil { + log.Warn("Unable to set 'UnlockFungibleAsset' event") + log.Warn(err.Error()) + } } - return amc.assetManagement.UnlockFungibleAsset(ctx.GetStub(), assetAgreement) + return retVal, err } // Ledger query functions -func (amc *AssetManagementContract) GetTotalFungibleAssetCount(ctx contractapi.TransactionContextInterface, assetType string) (uint64, error) { - return amc.assetManagement.GetTotalFungibleAssetCount(ctx.GetStub(), assetType) -} - -func (amc *AssetManagementContract) GetUnlockedFungibleAssetCount(ctx contractapi.TransactionContextInterface, assetType string) (uint64, error) { - return amc.assetManagement.GetUnlockedFungibleAssetCount(ctx.GetStub(), assetType) -} - func (amc *AssetManagementContract) GetTotalFungibleLockedAssets(ctx contractapi.TransactionContextInterface, assetType string) (uint64, error) { return amc.assetManagement.GetTotalFungibleLockedAssets(ctx.GetStub(), assetType) } diff --git a/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks_test.go b/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks_test.go index cc24b7947..18b460012 100644 --- a/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks_test.go +++ b/core/network/fabric-interop-cc/interfaces/asset-mgmt/asset_locks_test.go @@ -223,9 +223,9 @@ func TestAssetLock(t *testing.T) { newAssetId := "A002" recipient := "Bob" locker := clientId - hash := []byte("j8r484r484") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") lockInfoHTLC := &common.AssetLockHTLC { - Hash: nil, + HashBase64: nil, ExpiryTimeSecs: 0, } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) @@ -270,7 +270,7 @@ func TestAssetLock(t *testing.T) { require.False(t, lockSuccess) assetAgreement.Recipient = "" - lockInfoHTLC.Hash = hash + lockInfoHTLC.HashBase64 = hash lockInfoBytes, _ = proto.Marshal(lockInfoHTLC) lockInfo.LockInfo = lockInfoBytes assetAgreement.Id = assetId @@ -279,7 +279,7 @@ func TestAssetLock(t *testing.T) { require.False(t, lockSuccess) assetAgreement.Recipient = recipient - lockInfoHTLC.Hash = []byte{} + lockInfoHTLC.HashBase64 = []byte{} lockInfoBytes, _ = proto.Marshal(lockInfoHTLC) lockInfo.LockInfo = lockInfoBytes lockSuccess, err = amcc.LockAsset(amstub, assetAgreement, lockInfo) @@ -292,7 +292,7 @@ func TestAssetLock(t *testing.T) { require.False(t, lockSuccess) // Test success - lockInfoHTLC.Hash = hash + lockInfoHTLC.HashBase64 = hash lockInfoBytes, _ = proto.Marshal(lockInfoHTLC) lockInfo.LockInfo = lockInfoBytes lockSuccess, err = amcc.LockAsset(amstub, assetAgreement, lockInfo) @@ -332,9 +332,9 @@ func TestFungibleAssetLock(t *testing.T) { numUnits := uint64(1000) recipient := "Bob" locker := clientId - hash := []byte("j8r484r484") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") lockInfoHTLC := &common.AssetLockHTLC { - Hash: nil, + HashBase64: nil, ExpiryTimeSecs: 0, } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) @@ -375,7 +375,7 @@ func TestFungibleAssetLock(t *testing.T) { require.Error(t, err) assetAgreement.Recipient = "" - lockInfoHTLC.Hash = hash + lockInfoHTLC.HashBase64 = hash lockInfoBytes, _ = proto.Marshal(lockInfoHTLC) lockInfo.LockInfo = lockInfoBytes assetAgreement.NumUnits = numUnits @@ -383,7 +383,7 @@ func TestFungibleAssetLock(t *testing.T) { require.Error(t, err) assetAgreement.Recipient = recipient - lockInfoHTLC.Hash = []byte{} + lockInfoHTLC.HashBase64 = []byte{} lockInfoBytes, _ = proto.Marshal(lockInfoHTLC) lockInfo.LockInfo = lockInfoBytes contractId, err := amcc.LockFungibleAsset(amstub, assetAgreement, lockInfo) @@ -394,8 +394,8 @@ func TestFungibleAssetLock(t *testing.T) { require.Error(t, err) require.False(t, lockSuccess) - // Test success when both asset agreement and lock information are provided - lockInfoHTLC.Hash = hash + // Test failure when there is no unit balance (total not declared yet) + lockInfoHTLC.HashBase64 = hash lockInfoBytes, _ = proto.Marshal(lockInfoHTLC) lockInfo.LockInfo = lockInfoBytes contractId, err = amcc.LockFungibleAsset(amstub, assetAgreement, lockInfo) @@ -423,8 +423,8 @@ func TestIsAssetLocked(t *testing.T) { assetId := "A001" recipient := "Bob" locker := clientId - hash := []byte("j8r484r484") - hashPreimage := []byte("asset-exchange-scenario") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") + hashPreimage := []byte("YW5jaXNjbzEeMBwGA1UE") assetAgreement := &common.AssetExchangeAgreement { Type: assetType, Id: assetId, @@ -432,7 +432,7 @@ func TestIsAssetLocked(t *testing.T) { Locker: locker, } lockInfoHTLC := &common.AssetLockHTLC { - Hash: hash, + HashBase64: hash, ExpiryTimeSecs: 0, } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) @@ -441,7 +441,7 @@ func TestIsAssetLocked(t *testing.T) { LockInfo: lockInfoBytes, } claimInfoHTLC := &common.AssetClaimHTLC { - HashPreimage: hashPreimage, + HashPreimageBase64: hashPreimage, } claimInfoBytes, _ := proto.Marshal(claimInfoHTLC) claimInfo := &common.AssetClaim { @@ -541,8 +541,8 @@ func TestIsFungibleAssetLocked(t *testing.T) { numUnits := uint64(1000) recipient := "Bob" locker := clientId - hash := []byte("j8r484r484") - hashPreimage := []byte("asset-exchange-scenario") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") + hashPreimage := []byte("YW5jaXNjbzEeMBwGA1UE") assetAgreement := &common.FungibleAssetExchangeAgreement { Type: assetType, NumUnits: numUnits, @@ -550,7 +550,7 @@ func TestIsFungibleAssetLocked(t *testing.T) { Locker: locker, } lockInfoHTLC := &common.AssetLockHTLC { - Hash: hash, + HashBase64: hash, ExpiryTimeSecs: 0, } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) @@ -559,7 +559,7 @@ func TestIsFungibleAssetLocked(t *testing.T) { LockInfo: lockInfoBytes, } claimInfoHTLC := &common.AssetClaimHTLC { - HashPreimage: hashPreimage, + HashPreimageBase64: hashPreimage, } claimInfoBytes, _ := proto.Marshal(claimInfoHTLC) claimInfo := &common.AssetClaim { @@ -626,7 +626,7 @@ func TestAssetUnlock(t *testing.T) { assetId := "A001" recipient := "Bob" locker := clientId - hash := []byte("j8r484r484") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") assetAgreement := &common.AssetExchangeAgreement { Type: assetType, Id: assetId, @@ -636,7 +636,7 @@ func TestAssetUnlock(t *testing.T) { currTime := time.Now() expiryTime := currTime.Add(time.Minute) // expires in 1 minute lockInfoHTLC := &common.AssetLockHTLC { - Hash: hash, + HashBase64: hash, ExpiryTimeSecs: uint64(expiryTime.Unix()), } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) @@ -718,7 +718,7 @@ func TestFungibleAssetUnlock(t *testing.T) { numUnits := uint64(1000) recipient := "Bob" locker := clientId - hash := []byte("j8r484r484") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") assetAgreement := &common.FungibleAssetExchangeAgreement { Type: assetType, NumUnits: numUnits, @@ -728,7 +728,7 @@ func TestFungibleAssetUnlock(t *testing.T) { currTime := time.Now() expiryTime := currTime.Add(time.Minute) // expires in 1 minute lockInfoHTLC := &common.AssetLockHTLC { - Hash: hash, + HashBase64: hash, ExpiryTimeSecs: uint64(expiryTime.Unix()), } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) @@ -796,10 +796,10 @@ func TestAssetClaim(t *testing.T) { assetId := "A001" recipient := "Bob" locker := clientId - hash := []byte("j8r484r484") - hashPreimage := []byte("asset-exchange-scenario") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") + hashPreimage := []byte("YW5jaXNjbzEeMBwGA1UE") claimInfoHTLC := &common.AssetClaimHTLC { - HashPreimage: nil, + HashPreimageBase64: nil, } claimInfoBytes, _ := proto.Marshal(claimInfoHTLC) claimInfo := &common.AssetClaim { @@ -843,7 +843,7 @@ func TestAssetClaim(t *testing.T) { require.False(t, claimSuccess) assetAgreement.Locker = "" - claimInfoHTLC.HashPreimage = hashPreimage + claimInfoHTLC.HashPreimageBase64 = hashPreimage claimInfoBytes, _ = proto.Marshal(claimInfoHTLC) claimInfo.ClaimInfo = claimInfoBytes assetAgreement.Id = assetId @@ -852,7 +852,7 @@ func TestAssetClaim(t *testing.T) { require.False(t, claimSuccess) assetAgreement.Locker = locker - claimInfoHTLC.HashPreimage = []byte{} + claimInfoHTLC.HashPreimageBase64 = []byte{} claimInfoBytes, _ = proto.Marshal(claimInfoHTLC) claimInfo.ClaimInfo = claimInfoBytes claimSuccess, err = amcc.ClaimAsset(amstub, assetAgreement, claimInfo) @@ -867,7 +867,7 @@ func TestAssetClaim(t *testing.T) { // Test success // First, lock an asset lockInfoHTLC := &common.AssetLockHTLC { - Hash: hash, + HashBase64: hash, ExpiryTimeSecs: 0, } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) @@ -886,7 +886,7 @@ func TestAssetClaim(t *testing.T) { // Now claim the asset assetAgreement.Locker = locker - claimInfoHTLC.HashPreimage = hashPreimage + claimInfoHTLC.HashPreimageBase64 = hashPreimage claimInfoBytes, _ = proto.Marshal(claimInfoHTLC) claimInfo.ClaimInfo = claimInfoBytes setCreator(amstub, recipient) @@ -909,10 +909,10 @@ func TestFungibleAssetClaim(t *testing.T) { numUnits := uint64(1000) recipient := "Bob" locker := clientId - hash := []byte("j8r484r484") - hashPreimage := []byte("asset-exchange-scenario") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") + hashPreimage := []byte("YW5jaXNjbzEeMBwGA1UE") claimInfoHTLC := &common.AssetClaimHTLC { - HashPreimage: nil, + HashPreimageBase64: nil, } claimInfoBytes, _ := proto.Marshal(claimInfoHTLC) claimInfo := &common.AssetClaim { @@ -941,7 +941,7 @@ func TestFungibleAssetClaim(t *testing.T) { // Test success lockInfoHTLC := &common.AssetLockHTLC { - Hash: hash, + HashBase64: hash, ExpiryTimeSecs: 0, } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) @@ -959,7 +959,7 @@ func TestFungibleAssetClaim(t *testing.T) { // Now claim the asset assetAgreement.Locker = locker - claimInfoHTLC.HashPreimage = hashPreimage + claimInfoHTLC.HashPreimageBase64 = hashPreimage claimInfoBytes, _ = proto.Marshal(claimInfoHTLC) claimInfo.ClaimInfo = claimInfoBytes setCreator(amstub, recipient) @@ -981,14 +981,14 @@ func TestFungibleAssetCountFunctions(t *testing.T) { assetType := "cbdc" numUnits := uint64(1000) recipient := "Bob" - hash := []byte("j8r484r484") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") fungibleAssetExchangeAgreement := &common.FungibleAssetExchangeAgreement { Type: assetType, NumUnits: numUnits, Recipient: recipient, } lockInfoHTLC := &common.AssetLockHTLC { - Hash: hash, + HashBase64: hash, ExpiryTimeSecs: 0, } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) @@ -1025,7 +1025,7 @@ func TestAssetListFunctions(t *testing.T) { numUnits := uint64(1000) recipient := "Bob" locker := clientId - hash := []byte("j8r484r484") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") assetAgreement := &common.AssetExchangeAgreement { Type: assetType, Id: assetId, @@ -1041,7 +1041,7 @@ func TestAssetListFunctions(t *testing.T) { currTime := time.Now() expiryTime := currTime.Add(time.Minute) // expires in 1 minute lockInfoHTLC := &common.AssetLockHTLC { - Hash: hash, + HashBase64: hash, ExpiryTimeSecs: uint64(expiryTime.Unix()), } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) @@ -1167,7 +1167,7 @@ func TestAssetTimeFunctions(t *testing.T) { numUnits := uint64(1000) recipient := "Bob" locker := clientId - hash := []byte("j8r484r484") + hash := []byte("MBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0GA1UECxMGY2xpZW50MSQwIgYDVQQD") assetAgreement := &common.AssetExchangeAgreement { Type: assetType, Id: assetId, @@ -1183,7 +1183,7 @@ func TestAssetTimeFunctions(t *testing.T) { currTime := time.Now() expiryTime := currTime.Add(time.Minute) // expires in 1 minute lockInfoHTLC := &common.AssetLockHTLC { - Hash: hash, + HashBase64: hash, ExpiryTimeSecs: uint64(expiryTime.Unix()), } lockInfoBytes, _ := proto.Marshal(lockInfoHTLC) diff --git a/sdks/fabric/interoperation-node-sdk/package.json b/sdks/fabric/interoperation-node-sdk/package.json index ab09385ad..23bfb52d1 100644 --- a/sdks/fabric/interoperation-node-sdk/package.json +++ b/sdks/fabric/interoperation-node-sdk/package.json @@ -13,7 +13,7 @@ "types": "./types/index.d.ts", "scripts": { "test": "nyc mocha -r ts-node/register --exclude 'test/data/**/*.js' --recursive -t 10000", - "protos": "grpc_tools_node_protoc --proto_path=protos --proto_path=fabric-protos --js_out=import_style=commonjs,binary:protos-js/ --grpc_out=grpc_js:protos-js/ --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` protos/relay/datatransfer.proto protos/networks/networks.proto protos/driver/driver.proto protos/common/ack.proto protos/common/query.proto protos/fabric/view_data.proto protos/common/state.proto protos/common/proofs.proto protos/common/verification_policy.proto fabric-protos/peer/proposal_response.proto fabric-protos/peer/proposal.proto fabric-protos/peer/chaincode.proto fabric-protos/common/policies.proto fabric-protos/msp/msp_principal.proto && protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=./protos-js -I ./protos -I ./fabric-protos protos/relay/datatransfer.proto protos/networks/networks.proto protos/driver/driver.proto protos/common/ack.proto protos/common/query.proto protos/fabric/view_data.proto protos/common/state.proto protos/common/proofs.proto protos/common/verification_policy.proto fabric-protos/peer/proposal_response.proto fabric-protos/peer/proposal.proto fabric-protos/peer/chaincode.proto fabric-protos/common/policies.proto fabric-protos/msp/msp_principal.proto", + "protos": "grpc_tools_node_protoc --proto_path=protos --proto_path=fabric-protos --js_out=import_style=commonjs,binary:protos-js/ --grpc_out=grpc_js:protos-js/ --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` protos/relay/datatransfer.proto protos/networks/networks.proto protos/driver/driver.proto protos/common/asset_locks.proto protos/common/ack.proto protos/common/query.proto protos/fabric/view_data.proto protos/common/state.proto protos/common/proofs.proto protos/common/verification_policy.proto fabric-protos/peer/proposal_response.proto fabric-protos/peer/proposal.proto fabric-protos/peer/chaincode.proto fabric-protos/common/policies.proto fabric-protos/msp/msp_principal.proto && protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=./protos-js -I ./protos -I ./fabric-protos protos/relay/datatransfer.proto protos/networks/networks.proto protos/driver/driver.proto protos/common/asset_locks.proto protos/common/ack.proto protos/common/query.proto protos/fabric/view_data.proto protos/common/state.proto protos/common/proofs.proto protos/common/verification_policy.proto fabric-protos/peer/proposal_response.proto fabric-protos/peer/proposal.proto fabric-protos/peer/chaincode.proto fabric-protos/common/policies.proto fabric-protos/msp/msp_principal.proto", "build": "tsc && cp -r protos-js build/", "prepublishOnly": "npm test", "lint": "eslint '*/**/*.{js,ts,tsx}' --quiet --fix", diff --git a/sdks/fabric/interoperation-node-sdk/src/AssetManager.ts b/sdks/fabric/interoperation-node-sdk/src/AssetManager.ts new file mode 100644 index 000000000..bf347d0a3 --- /dev/null +++ b/sdks/fabric/interoperation-node-sdk/src/AssetManager.ts @@ -0,0 +1,768 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * This file provides helper functions for interoperability operations. + **/ +/** End file docs */ + +import log4js from "log4js"; +import crypto from "crypto"; +import fabproto6 from "fabric-protos"; +import * as helpers from "./helpers"; +import assetLocksPb from "../protos-js/common/asset_locks_pb"; +import { Contract, ContractListener } from "fabric-network"; +const logger = log4js.getLogger("InteroperableHelper"); + + +// Create an asset exchange agreement structure +function createAssetExchangeAgreementSerialized(assetType, assetID, recipientECert, lockerECert) +{ + const assetExchangeAgreement = new assetLocksPb.AssetExchangeAgreement(); + assetExchangeAgreement.setType(assetType); + assetExchangeAgreement.setId(assetID); + assetExchangeAgreement.setRecipient(recipientECert); + assetExchangeAgreement.setLocker(lockerECert); + return assetExchangeAgreement.serializeBinary().toString(); +} + +// Create a fungible asset exchange agreement structure +function createFungibleAssetExchangeAgreementSerialized(assetType, numUnits, recipientECert, lockerECert) +{ + const assetExchangeAgreement = new assetLocksPb.FungibleAssetExchangeAgreement(); + assetExchangeAgreement.setType(assetType); + assetExchangeAgreement.setNumunits(numUnits); + assetExchangeAgreement.setRecipient(recipientECert); + assetExchangeAgreement.setLocker(lockerECert); + return assetExchangeAgreement.serializeBinary().toString(); +} + +// Create an asset lock structure +function createAssetLockInfoSerialized(hashValue, expiryTimeSecs) +{ + const lockInfoHTLC = new assetLocksPb.AssetLockHTLC(); + lockInfoHTLC.setHashbase64(hashValue); + lockInfoHTLC.setExpirytimesecs(expiryTimeSecs); + lockInfoHTLC.setTimespec(assetLocksPb.AssetLockHTLC.TimeSpec.EPOCH) + const lockInfoHTLCSerialized = lockInfoHTLC.serializeBinary(); + const lockInfo = new assetLocksPb.AssetLock(); + lockInfo.setLockmechanism(assetLocksPb.LockMechanism.HTLC); + lockInfo.setLockinfo(lockInfoHTLCSerialized); + return lockInfo.serializeBinary().toString(); +} + +// Create an asset claim structure +function createAssetClaimInfoSerialized(hashPreimage) +{ + const claimInfoHTLC = new assetLocksPb.AssetClaimHTLC(); + claimInfoHTLC.setHashpreimagebase64(Buffer.from(hashPreimage).toString('base64')); + const claimInfoHTLCSerialized = claimInfoHTLC.serializeBinary(); + const claimInfo = new assetLocksPb.AssetClaim(); + claimInfo.setLockmechanism(assetLocksPb.LockMechanism.HTLC); + claimInfo.setClaiminfo(claimInfoHTLCSerialized); + return claimInfo.serializeBinary().toString(); +} + +// Create a SHA-256 hash over an ASCII string +function createSHA256HashBase64(preimage: string) +{ + return crypto.createHash('sha256').update(preimage).digest('base64'); +} + +// Create a secure pseudo-random preimage of a given length +function generateRandomHashPreimage(strLength: number) +{ + if (!strLength || strLength <= 0) + { + strLength = 20; // Default length + } + return crypto.randomBytes(strLength).toString(); +} + +/** + * First/second step of a Hashed Time Lock Contract + * - Lock a unique asset instance using a hash + **/ +const createHTLC = async ( + contract: Contract, + assetType: string, + assetID: string, + recipientECert: string, + hashPreimage: string, + hashValue: string, + expiryTimeSecs: number, + timeoutCallback: (c: Contract, t: string, i: string, r: string, p: string, v: string) => any, +): Promise<{ preimage: any; result: any }> => { + + if (!contract) + { + logger.error("Contract handle not supplied"); + return { preimage: "", result: false }; + } + if (!assetType) + { + logger.error("Asset type not supplied"); + return { preimage: "", result: false }; + } + if (!assetID) + { + logger.error("Asset ID not supplied"); + return { preimage: "", result: false }; + } + if (!recipientECert) + { + logger.error("Recipient ECert not supplied"); + return { preimage: "", result: false }; + } + const currTimeSecs = Math.floor(Date.now()/1000); // Convert epoch milliseconds to seconds + if (expiryTimeSecs <= currTimeSecs) + { + logger.error("Supplied expiry time invalid or in the past: %s; current time: %s", new Date(expiryTimeSecs).toISOString(), new Date(currTimeSecs).toISOString()); + return { preimage: "", result: false }; + } + + if (!hashValue || hashValue.length == 0) + { + if (!hashPreimage || hashPreimage.length == 0) + { + // Generate the preimage + hashPreimage = generateRandomHashPreimage(-1); + } + // Hash the preimage + hashValue = createSHA256HashBase64(hashPreimage); + } + + const assetExchangeAgreementStr = createAssetExchangeAgreementSerialized(assetType, assetID, recipientECert, ""); + const lockInfoStr = createAssetLockInfoSerialized(hashValue, expiryTimeSecs); + + // Normal invoke function + const [result, submitError] = await helpers.handlePromise( + contract.submitTransaction("LockAsset", assetExchangeAgreementStr, lockInfoStr), + ); + if (submitError) { + throw new Error(`LockAsset submitTransaction Error: ${submitError}`); + } + + if (timeoutCallback) + { + // Start timer for lock expiration + setTimeout(timeoutCallback, (expiryTimeSecs * 1000) - Date.now(), contract, assetType, assetID, recipientECert, hashPreimage, hashValue); + } + + return { preimage: hashPreimage, result: result }; +}; + +/** + * First/second step of a Hashed Time Lock Contract + * - Lock a set of fungible assets using a hash + **/ +const createFungibleHTLC = async ( + contract: Contract, + assetType: string, + numUnits: number, + recipientECert: string, + hashPreimage: string, + hashValue: string, + expiryTimeSecs: number, + timeoutCallback: (c: Contract, i: string, t: string, n: number, r: string, p: string, v: string) => any, +): Promise<{ preimage: any; result: any }> => { + + if (!contract) + { + logger.error("Contract handle not supplied"); + return { preimage: "", result: "" }; + } + if (!assetType) + { + logger.error("Asset type not supplied"); + return { preimage: "", result: "" }; + } + if (numUnits <= 0) + { + logger.error("Asset count must be a positive integer"); + return { preimage: "", result: "" }; + } + if (!recipientECert) + { + logger.error("Recipient ECert not supplied"); + return { preimage: "", result: "" }; + } + const currTimeSecs = Math.floor(Date.now()/1000); // Convert epoch milliseconds to seconds + if (expiryTimeSecs <= currTimeSecs) + { + logger.error("Supplied expiry time invalid or in the past: %s; current time: %s", new Date(expiryTimeSecs).toISOString(), new Date(currTimeSecs).toISOString()); + return { preimage: "", result: "" }; + } + + if (!hashValue || hashValue.length == 0) + { + if (!hashPreimage || hashPreimage.length == 0) + { + // Generate the preimage + hashPreimage = generateRandomHashPreimage(-1); + } + // Hash the preimage + hashValue = createSHA256HashBase64(hashPreimage); + } + + const assetExchangeAgreementStr = createFungibleAssetExchangeAgreementSerialized(assetType, numUnits, recipientECert, ""); + const lockInfoStr = createAssetLockInfoSerialized(hashValue, expiryTimeSecs); + + // Normal invoke function + const [contractId, submitError] = await helpers.handlePromise( + contract.submitTransaction("LockFungibleAsset", assetExchangeAgreementStr, lockInfoStr), + ); + if (submitError) { + throw new Error(`LockFungibleAsset submitTransaction Error: ${submitError}`); + } + + if (timeoutCallback) + { + // Start timer for lock expiration + setTimeout(timeoutCallback, (expiryTimeSecs * 1000) - Date.now(), contract, contractId, assetType, numUnits, recipientECert, hashPreimage, hashValue); + } + + return { preimage: hashPreimage, result: contractId }; +}; + +/** + * Latter step of a Hashed Time Lock Contract + * - Claim a unique asset instance using a hash preimage + **/ +const claimAssetInHTLC = async ( + contract: Contract, + assetType: string, + assetID: string, + lockerECert: string, + hashPreimage: string, +): Promise => { + + if (!contract) + { + logger.error("Contract handle not supplied"); + return false; + } + if (!assetType) + { + logger.error("Asset type not supplied"); + return false; + } + if (!assetID) + { + logger.error("Asset ID not supplied"); + return false; + } + if (!lockerECert) + { + logger.error("Locker ECert not supplied"); + return false; + } + if (!hashPreimage) + { + logger.error("Hash Preimage not supplied"); + return false; + } + + const assetExchangeAgreementStr = createAssetExchangeAgreementSerialized(assetType, assetID, "", lockerECert); + const claimInfoStr = createAssetClaimInfoSerialized(hashPreimage); + + // Normal invoke function + const [result, submitError] = await helpers.handlePromise( + contract.submitTransaction("ClaimAsset", assetExchangeAgreementStr, claimInfoStr), + ); + if (submitError) { + throw new Error(`ClaimAsset submitTransaction Error: ${submitError}`); + } + return result; +}; + +/** + * Latter step of a Hashed Time Lock Contract + * - Claim a set of fungible assets using a hash preimage + **/ +const claimFungibleAssetInHTLC = async ( + contract: Contract, + contractId: string, + hashPreimage: string, +): Promise => { + + if (!contract) + { + logger.error("Contract handle not supplied"); + return false; + } + if (!contractId) + { + logger.error("contract ID not supplied"); + return false; + } + if (!hashPreimage) + { + logger.error("Hash Preimage not supplied"); + return false; + } + + const claimInfoStr = createAssetClaimInfoSerialized(hashPreimage); + + // Normal invoke function + const [result, submitError] = await helpers.handlePromise( + contract.submitTransaction("ClaimFungibleAsset", contractId, claimInfoStr), + ); + if (submitError) { + throw new Error(`ClaimFungibleAsset submitTransaction Error: ${submitError}`); + } + return result; +}; + +/** + * Rollback step of a Hashed Time Lock Contract + * - Reclaim a unique asset instance + **/ +const reclaimAssetInHTLC = async ( + contract: Contract, + assetType: string, + assetID: string, + recipientECert: string, +): Promise => { + + if (!contract) + { + logger.error("Contract handle not supplied"); + return false; + } + if (!assetType) + { + logger.error("Asset type not supplied"); + return false; + } + if (!assetID) + { + logger.error("Asset ID not supplied"); + return false; + } + if (!recipientECert) + { + logger.error("Recipient ECert not supplied"); + return false; + } + + const assetExchangeAgreementStr = createAssetExchangeAgreementSerialized(assetType, assetID, recipientECert, ""); + + // Normal invoke function + const [result, submitError] = await helpers.handlePromise( + contract.submitTransaction("UnlockAsset", assetExchangeAgreementStr), + ); + if (submitError) { + throw new Error(`UnlockAsset submitTransaction Error: ${submitError}`); + } + return result; +}; + +/** + * Rollback step of a Hashed Time Lock Contract + * - Reclaim a set of fungible assets + **/ +const reclaimFungibleAssetInHTLC = async ( + contract: Contract, + contractId: string, +): Promise => { + + if (!contract) + { + logger.error("Contract handle not supplied"); + return false; + } + if (!contractId) + { + logger.error("contract ID not supplied"); + return false; + } + + // Normal invoke function + const [result, submitError] = await helpers.handlePromise( + contract.submitTransaction("UnlockFungibleAsset", contractId), + ); + if (submitError) { + throw new Error(`UnlockFungibleAsset submitTransaction Error: ${submitError}`); + } + return result; +}; + +/** + * Query the state of a Hashed Time Lock Contract + * - Determine if a unique asset instance is locked by a given party for another given party + **/ +const isAssetLockedInHTLC = async ( + contract: Contract, + assetType: string, + assetID: string, + recipientECert: string, + lockerECert: string, +): Promise => { + + if (!contract) + { + logger.error("Contract handle not supplied"); + return false; + } + if (!assetType) + { + logger.error("Asset type not supplied"); + return false; + } + if (!assetID) + { + logger.error("Asset ID not supplied"); + return false; + } + if (!recipientECert) + { + logger.error("Recipient ECert not supplied"); + return false; + } + if (!lockerECert) + { + logger.error("Locker ECert not supplied"); + return false; + } + + const assetExchangeAgreementStr = createAssetExchangeAgreementSerialized(assetType, assetID, recipientECert, lockerECert); + + // Normal invoke function + const [result, evaluateError] = await helpers.handlePromise( + contract.evaluateTransaction("IsAssetLocked", assetExchangeAgreementStr), + ); + if (evaluateError) { + throw new Error(`IsAssetLocked evaluateTransaction Error: ${evaluateError}`); + } + return result; +}; + +/** + * Query the state of a Hashed Time Lock Contract + * - Determine if a set of fungible assets is locked by a given party for another given party + **/ +const isFungibleAssetLockedInHTLC = async ( + contract: Contract, + contractId: string, +): Promise => { + + if (!contract) + { + logger.error("Contract handle not supplied"); + return false; + } + if (!contractId) + { + logger.error("contract ID not supplied"); + return false; + } + + // Normal invoke function + const [result, evaluateError] = await helpers.handlePromise( + contract.evaluateTransaction("IsFungibleAssetLocked", contractId), + ); + if (evaluateError) { + throw new Error(`IsFungibleAssetLocked evaluateTransaction Error: ${evaluateError}`); + } + return result; +}; + + +/** + * HTLC Lifecycle Events + * - Developers should note that event emission and actions in response occur on a best effort basis. + * - Also, there is no guarantee that a particular event (lock, claim, reclaim) will ever get emitted + * - Therefore, the calling (or listening) logic should incorporate suitable fallbacks and timeouts. + **/ + +/** + * The below functions trigger callbacks passed as arguments when a matching event is received from the contract layer + **/ +const StartHTLCEventListener = ( + contract: Contract, + eventName: string, + contractId: string, + assetType: string, + assetId: string, + numUnits: number, + recipientECert: string, + lockerECert: string, + eventCallback: Function, +): void => { + const listener: ContractListener = async (event) => { + if (event.eventName === eventName) { + let assetLockContractInfo; + if (eventName.includes('Fungible')) { + const eventInfo: assetLocksPb.FungibleAssetContractHTLC = assetLocksPb.FungibleAssetContractHTLC.deserializeBinary(event.payload); + assetLockContractInfo = eventInfo; + } else { + const eventInfo: assetLocksPb.AssetContractHTLC = assetLocksPb.AssetContractHTLC.deserializeBinary(event.payload); + assetLockContractInfo = eventInfo; + } + const infoContractId = assetLockContractInfo.getContractid(); + if (contractId && contractId.length > 0) { + if (infoContractId.length > 0 && infoContractId !== contractId) { + return; + } + } + const infoAssetType = assetLockContractInfo.getAgreement().getType(); + if (assetType && assetType.length > 0) { + if (infoAssetType.length > 0 && infoAssetType !== assetType) { + return; + } + } + let infoNumUnits: number, infoAssetId: string; + if (eventName.includes('Fungible')) { + infoNumUnits = assetLockContractInfo.getAgreement().getNumunits(); + if (infoNumUnits !== numUnits) { + return; + } + } else { + infoAssetId = assetLockContractInfo.getAgreement().getId(); + if (assetId && assetId.length > 0) { + if (infoAssetId.length > 0 && infoAssetId !== assetId) { + return; + } + } + } + const infoRecipient = assetLockContractInfo.getAgreement().getRecipient(); + if (recipientECert && recipientECert.length > 0) { + if (infoRecipient.length > 0 && infoRecipient !== recipientECert) { + return; + } + } + const infoLocker = assetLockContractInfo.getAgreement().getLocker(); + if (lockerECert && lockerECert.length > 0) { + if (infoLocker.length > 0 && infoLocker !== lockerECert) { + return; + } + } + // All filters passed + if (eventName === 'LockAsset' || eventName === 'LockFungibleAsset') { + const hashBase64 = assetLockContractInfo.getLock().getHashbase64(); + const hashValue: string = Buffer.from(hashBase64.toString(), 'base64').toString('utf8'); + if (eventName === 'LockAsset') { + eventCallback(contract, infoContractId, infoAssetType, infoAssetId, infoRecipient, infoLocker, hashValue); + } else { + eventCallback(contract, infoContractId, infoAssetType, infoNumUnits, infoRecipient, infoLocker, hashValue); + } + } else if (eventName === 'ClaimAsset' || eventName === 'ClaimFungibleAsset') { + const hashPreimageBase64 = assetLockContractInfo.getClaim().getHashpreimagebase64(); + const hashPreimage: string = Buffer.from(hashPreimageBase64.toString(), 'base64').toString('utf8'); + if (eventName === 'ClaimAsset') { + eventCallback(contract, infoContractId, infoAssetType, infoAssetId, infoRecipient, infoLocker, hashPreimage); + } else { + eventCallback(contract, infoContractId, infoAssetType, infoNumUnits, infoRecipient, infoLocker, hashPreimage); + } + } else if (eventName === 'UnlockAsset') { + eventCallback(contract, infoContractId, infoAssetType, infoAssetId, infoRecipient, infoLocker); + } else if (eventName === 'UnlockFungibleAsset') { + eventCallback(contract, infoContractId, infoAssetType, infoNumUnits, infoRecipient, infoLocker); + } + } + }; + contract.addContractListener(listener); +} + +const StartHTLCAssetLockListener = ( + contract: Contract, + contractId: string, + assetType: string, + assetId: string, + recipientECert: string, + lockerECert: string, + lockCallback: (c: Contract, d: string, t: string, i: string, r: string, l: string, v: string) => any, +): void => { + StartHTLCEventListener(contract, 'LockAsset', contractId, assetType, assetId, -1, recipientECert, lockerECert, lockCallback); +} + +const StartHTLCAssetClaimListener = ( + contract: Contract, + contractId: string, + assetType: string, + assetId: string, + recipientECert: string, + lockerECert: string, + claimCallback: (c: Contract, d: string, t: string, i: string, r: string, l: string, p: string) => any, +): void => { + StartHTLCEventListener(contract, 'ClaimAsset', contractId, assetType, assetId, -1, recipientECert, lockerECert, claimCallback); +} + +const StartHTLCAssetUnlockListener = ( + contract: Contract, + contractId: string, + assetType: string, + assetId: string, + recipientECert: string, + lockerECert: string, + unlockCallback: (c: Contract, d: string, t: string, i: string, r: string, l: string) => any, +): void => { + StartHTLCEventListener(contract, 'UnlockAsset', contractId, assetType, assetId, -1, recipientECert, lockerECert, unlockCallback); +} + +const StartHTLCFungibleAssetLockListener = ( + contract: Contract, + contractId: string, + assetType: string, + numUnits: number, + recipientECert: string, + lockerECert: string, + lockCallback: (c: Contract, d: string, t: string, n: number, r: string, l: string, v: string) => any, +): void => { + StartHTLCEventListener(contract, 'LockFungibleAsset', contractId, assetType, "", numUnits, recipientECert, lockerECert, lockCallback); +} + +const StartHTLCFungibleAssetClaimListener = ( + contract: Contract, + contractId: string, + assetType: string, + numUnits: number, + recipientECert: string, + lockerECert: string, + claimCallback: (c: Contract, d: string, t: string, n: number, r: string, l: string, p: string) => any, +): void => { + StartHTLCEventListener(contract, 'ClaimFungibleAsset', contractId, assetType, "", numUnits, recipientECert, lockerECert, claimCallback); +} + +const StartHTLCFungibleAssetUnlockListener = ( + contract: Contract, + contractId: string, + assetType: string, + numUnits: number, + recipientECert: string, + lockerECert: string, + unlockCallback: (c: Contract, d: string, t: string, n: number, r: string, l: string) => any, +): void => { + StartHTLCEventListener(contract, 'UnlockFungibleAsset', contractId, assetType, "", numUnits, recipientECert, lockerECert, unlockCallback); +} + +/** + * The below functions return promises for HTLC events. + * Developers can use 'await' to synchronously manage asset swapping logic. + **/ +const HTLCAssetLocked = async ( + contract: Contract, + contractId: string, + assetType: string, + assetId: string, + recipientECert: string, + lockerECert: string, +): Promise => { + return new Promise((resolve, reject) => { + const waitForLock = (contract, contractId, assetType, assetId, recipientECert, lockerECert, hashValue) => { + resolve(hashValue); + }; + StartHTLCAssetLockListener(contract, contractId, assetType, assetId, recipientECert, lockerECert, waitForLock); + }); +} + +const HTLCAssetClaimed = async ( + contract: Contract, + contractId: string, + assetType: string, + assetId: string, + recipientECert: string, + lockerECert: string, +): Promise => { + return new Promise((resolve, reject) => { + const waitForClaim = (contract, contractId, assetType, assetId, recipientECert, lockerECert, hashPreimage) => { + resolve(hashPreimage); + }; + StartHTLCAssetClaimListener(contract, contractId, assetType, assetId, recipientECert, lockerECert, waitForClaim); + }); +} + +const HTLCAssetUnlocked = async ( + contract: Contract, + contractId: string, + assetType: string, + assetId: string, + recipientECert: string, + lockerECert: string, +): Promise => { + return new Promise((resolve, reject) => { + const waitForUnlock = (contract, contractId, assetType, assetId, recipientECert, lockerECert) => { + resolve(); + }; + StartHTLCAssetUnlockListener(contract, contractId, assetType, assetId, recipientECert, lockerECert, waitForUnlock); + }); +} + +const HTLCFungibleAssetLocked = async ( + contract: Contract, + contractId: string, + assetType: string, + numUnits: number, + recipientECert: string, + lockerECert: string, +): Promise => { + return new Promise((resolve, reject) => { + const waitForLock = (contract, contractId, assetType, numUnits, recipientECert, lockerECert, hashValue) => { + resolve(hashValue); + }; + StartHTLCFungibleAssetLockListener(contract, contractId, assetType, numUnits, recipientECert, lockerECert, waitForLock); + }); +} + +const HTLCFungibleAssetClaimed = async ( + contract: Contract, + contractId: string, + assetType: string, + numUnits: number, + recipientECert: string, + lockerECert: string, +): Promise => { + return new Promise((resolve, reject) => { + const waitForClaim = (contract, contractId, assetType, numUnits, recipientECert, lockerECert, hashPreimage) => { + resolve(hashPreimage); + }; + StartHTLCFungibleAssetClaimListener(contract, contractId, assetType, numUnits, recipientECert, lockerECert, waitForClaim); + }); +} + +const HTLCFungibleAssetUnlocked = async ( + contract: Contract, + contractId: string, + assetType: string, + numUnits: number, + recipientECert: string, + lockerECert: string, +): Promise => { + return new Promise((resolve, reject) => { + const waitForUnlock = (contract, contractId, assetType, numUnits, recipientECert, lockerECert) => { + resolve(); + }; + StartHTLCFungibleAssetUnlockListener(contract, contractId, assetType, numUnits, recipientECert, lockerECert, waitForUnlock); + }); +} + +export { + createAssetExchangeAgreementSerialized, + createFungibleAssetExchangeAgreementSerialized, + createAssetLockInfoSerialized, + createAssetClaimInfoSerialized, + createHTLC, + createFungibleHTLC, + claimAssetInHTLC, + claimFungibleAssetInHTLC, + reclaimAssetInHTLC, + reclaimFungibleAssetInHTLC, + isAssetLockedInHTLC, + isFungibleAssetLockedInHTLC, + StartHTLCAssetLockListener, + StartHTLCAssetClaimListener, + StartHTLCAssetUnlockListener, + StartHTLCFungibleAssetLockListener, + StartHTLCFungibleAssetClaimListener, + StartHTLCFungibleAssetUnlockListener, + HTLCAssetLocked, + HTLCAssetClaimed, + HTLCAssetUnlocked, + HTLCFungibleAssetLocked, + HTLCFungibleAssetClaimed, + HTLCFungibleAssetUnlocked, +}; diff --git a/sdks/fabric/interoperation-node-sdk/test/AssetManager.js b/sdks/fabric/interoperation-node-sdk/test/AssetManager.js new file mode 100644 index 000000000..432ec5901 --- /dev/null +++ b/sdks/fabric/interoperation-node-sdk/test/AssetManager.js @@ -0,0 +1,421 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* eslint-disable no-unused-expressions */ + +const fs = require("fs"); +const keyutil = require("jsrsasign").KEYUTIL; +const sinon = require("sinon"); +const chai = require("chai"); +const chaiAsPromised = require("chai-as-promised"); + +chai.use(chaiAsPromised); +const { expect } = chai; +chai.should(); + +const { Wallets } = require("fabric-network"); +const { ContractImpl } = require("fabric-network/lib/contract"); +const { NetworkImpl } = require("fabric-network/lib/network"); +const assetManager = require("../src/AssetManager"); +import assetLocksPb from "../protos-js/common/asset_locks_pb"; + +describe("AssetManager", () => { + const mspId = "mspId"; + const foreignNetworkId = "foreignNetworkId"; + const userName = "user_name"; + + const assetType = "bond"; + const assetID = "A001"; + const fungibleAssetType = "cbdc"; + const numUnits = 1000; + const recipientECert = fs.readFileSync(`${__dirname}/data/anotherSignCert.pem`).toString(); + let lockerECert; + + let wallet; + let amc; + // Initialize wallet with a single user identity + async function initializeWallet() { + const privKeyFile = `${__dirname}/data/privKey.pem`; + const signCertFile = `${__dirname}/data/signCert.pem`; + const privateKeyStr = fs.readFileSync(privKeyFile).toString(); + const signCert = fs.readFileSync(signCertFile).toString(); + lockerECert = signCert; + wallet = await Wallets.newInMemoryWallet(); + const userIdentity = { + credentials: { certificate: signCert, privateKey: privateKeyStr }, + mspId, + type: "X.509", + }; + await wallet.put(userName, userIdentity); + return userIdentity; + } + + async function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + beforeEach(async () => { + await initializeWallet(); + const network = sinon.createStubInstance(NetworkImpl); + amc = new ContractImpl(network, "amc", "AssetManager"); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe("create HTLC for unique asset", () => { + let amcStub; + + beforeEach(() => { + amcStub = sinon.stub(amc, "submitTransaction").resolves(false); + }); + + it("asset lock fails with invalid parameters", async () => { + let expiryTimeSecs = Math.floor(Date.now()/1000) + 300; // Convert epoch milliseconds to seconds and add 5 minutes + let assetLockInvocation = await assetManager.createHTLC(null, assetType, assetID, recipientECert, "", "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('boolean'); + expect(assetLockInvocation.result).to.equal(false); + assetLockInvocation = await assetManager.createHTLC(amc, "", assetID, recipientECert, "", "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('boolean'); + expect(assetLockInvocation.result).to.equal(false); + assetLockInvocation = await assetManager.createHTLC(amc, assetType, "", recipientECert, "", "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('boolean'); + expect(assetLockInvocation.result).to.equal(false); + assetLockInvocation = await assetManager.createHTLC(amc, assetType, assetID, "", "", "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('boolean'); + expect(assetLockInvocation.result).to.equal(false); + assetLockInvocation = await assetManager.createHTLC(amc, assetType, assetID, recipientECert, "", "", expiryTimeSecs - 600); // Expiry time in the past + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('boolean'); + expect(assetLockInvocation.result).to.equal(false); + }); + + it("submit asset lock invocation", async () => { + let assetAgreementStr = assetManager.createAssetExchangeAgreementSerialized(assetType, assetID, recipientECert, ""); + const hashValue = "abcdef123456"; + let expiryTimeSecs = Math.floor(Date.now()/1000) + 300; // Convert epoch milliseconds to seconds and add 5 minutes + let lockInfoStr = assetManager.createAssetLockInfoSerialized(hashValue, expiryTimeSecs); + amcStub.withArgs("LockAsset", assetAgreementStr, lockInfoStr).resolves(true); + let assetLockInvocation = await assetManager.createHTLC(amc, assetType, assetID, recipientECert, "", hashValue, expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('boolean'); + expect(assetLockInvocation.result).to.equal(true); + amcStub.withArgs("LockAsset", assetAgreementStr, sinon.match.any).resolves(true); + assetLockInvocation = await assetManager.createHTLC(amc, assetType, assetID, recipientECert, "", hashValue, expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('boolean'); + expect(assetLockInvocation.result).to.equal(true); + assetLockInvocation = await assetManager.createHTLC(amc, assetType, assetID, recipientECert, "", "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.be.above(0); + expect(assetLockInvocation.result).to.be.a('boolean'); + expect(assetLockInvocation.result).to.equal(true); + const hashPreimage = "some-preimage"; + assetLockInvocation = await assetManager.createHTLC(amc, assetType, assetID, recipientECert, hashPreimage, "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.equal(hashPreimage); + expect(assetLockInvocation.result).to.be.a('boolean'); + expect(assetLockInvocation.result).to.equal(true); + const testAttr = assetType + ':' + assetID + ':' + recipientECert + ':' + hashPreimage + ':' + hashValue; + const timeoutCb = function(c, t, i, r, p, v) { + console.log('Asset lock TIMEOUT at', Date()); + c.testAttr = t + ':' + i + ':' + r + ':' + p + ':' + v; + }; + expiryTimeSecs = Math.floor(Date.now()/1000) + 3; // 3 seconds + assetLockInvocation = await assetManager.createHTLC(amc, assetType, assetID, recipientECert, hashPreimage, hashValue, expiryTimeSecs, timeoutCb); + await sleep(4000); + expect(amc).to.have.own.property('testAttr'); + expect(amc.testAttr).to.equal(testAttr); + }); + }); + + describe("create HTLC for fungible asset", () => { + let amcStub; + + beforeEach(() => { + amcStub = sinon.stub(amc, "submitTransaction").resolves(""); + }); + + it("asset lock fails with invalid parameters", async () => { + let expiryTimeSecs = Math.floor(Date.now()/1000) + 300; // Convert epoch milliseconds to seconds and add 5 minutes + let assetLockInvocation = await assetManager.createFungibleHTLC(null, fungibleAssetType, numUnits, recipientECert, "", "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('string'); + expect(assetLockInvocation.result).to.equal(''); + assetLockInvocation = await assetManager.createFungibleHTLC(amc, "", numUnits, recipientECert, "", "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('string'); + expect(assetLockInvocation.result).to.equal(''); + assetLockInvocation = await assetManager.createFungibleHTLC(amc, fungibleAssetType, -1, recipientECert, "", "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('string'); + expect(assetLockInvocation.result).to.equal(''); + assetLockInvocation = await assetManager.createFungibleHTLC(amc, fungibleAssetType, numUnits, "", "", "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('string'); + expect(assetLockInvocation.result).to.equal(''); + assetLockInvocation = await assetManager.createFungibleHTLC(amc, fungibleAssetType, numUnits, recipientECert, "", "", expiryTimeSecs - 600); // Expiry time in the past + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('string'); + expect(assetLockInvocation.result).to.equal(''); + }); + + it("submit asset lock invocation", async () => { + let assetAgreementStr = assetManager.createFungibleAssetExchangeAgreementSerialized(fungibleAssetType, numUnits, recipientECert, ""); + const hashValue = "abcdef123456"; + let expiryTimeSecs = Math.floor(Date.now()/1000) + 300; // Convert epoch milliseconds to seconds and add 5 minutes + let lockInfoStr = assetManager.createAssetLockInfoSerialized(hashValue, expiryTimeSecs); + const contractId = "CONTRACT-1234"; + amcStub.withArgs("LockFungibleAsset", assetAgreementStr, lockInfoStr).resolves(contractId); + let assetLockInvocation = await assetManager.createFungibleHTLC(amc, fungibleAssetType, numUnits, recipientECert, "", hashValue, expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('string'); + expect(assetLockInvocation.result).to.equal(contractId); + amcStub.withArgs("LockFungibleAsset", assetAgreementStr, sinon.match.any).resolves(contractId); + assetLockInvocation = await assetManager.createFungibleHTLC(amc, fungibleAssetType, numUnits, recipientECert, "", hashValue, expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.equal(0); + expect(assetLockInvocation.result).to.be.a('string'); + expect(assetLockInvocation.result).to.equal(contractId); + assetLockInvocation = await assetManager.createFungibleHTLC(amc, fungibleAssetType, numUnits, recipientECert, "", "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.be.a("string"); + expect(assetLockInvocation.preimage.length).to.be.above(0); + expect(assetLockInvocation.result).to.be.a('string'); + expect(assetLockInvocation.result).to.equal(contractId); + const hashPreimage = "some-preimage"; + assetLockInvocation = await assetManager.createFungibleHTLC(amc, fungibleAssetType, numUnits, recipientECert, hashPreimage, "", expiryTimeSecs); + expect(assetLockInvocation).to.be.an('object').that.has.all.keys('preimage', 'result'); + expect(assetLockInvocation.preimage).to.equal(hashPreimage); + expect(assetLockInvocation.result).to.be.a('string'); + expect(assetLockInvocation.result).to.equal(contractId); + const testAttr = contractId + ':' + fungibleAssetType + ':' + numUnits + ':' + recipientECert + ':' + hashPreimage + ':' + hashValue; + const timeoutCb = function(c, i, t, n, r, p, v) { + console.log('Fungible asset lock TIMEOUT at', Date()); + c.testAttr = i + ':' + t + ':' + n + ':' + r + ':' + p + ':' + v; + }; + expiryTimeSecs = Math.floor(Date.now()/1000) + 3; // 3 seconds + assetLockInvocation = await assetManager.createFungibleHTLC(amc, fungibleAssetType, numUnits, recipientECert, hashPreimage, hashValue, expiryTimeSecs, timeoutCb); + await sleep(4000); + expect(amc).to.have.own.property('testAttr'); + expect(amc.testAttr).to.equal(testAttr); + }); + }); + + describe("claim unique asset locked in HTLC", () => { + let amcStub; + const hashPreimage = "xyz+123-*ty%"; + + beforeEach(() => { + amcStub = sinon.stub(amc, "submitTransaction").resolves(false); + }); + + it("asset claim fails with invalid parameters", async () => { + let assetClaimInvocation = await assetManager.claimAssetInHTLC(null, assetType, assetID, lockerECert, hashPreimage); + expect(assetClaimInvocation).to.be.a('boolean'); + expect(assetClaimInvocation).to.equal(false); + assetClaimInvocation = await assetManager.claimAssetInHTLC(amc, "", assetID, lockerECert, hashPreimage); + expect(assetClaimInvocation).to.be.a('boolean'); + expect(assetClaimInvocation).to.equal(false); + assetClaimInvocation = await assetManager.claimAssetInHTLC(amc, assetType, "", lockerECert, hashPreimage); + expect(assetClaimInvocation).to.be.a('boolean'); + expect(assetClaimInvocation).to.equal(false); + assetClaimInvocation = await assetManager.claimAssetInHTLC(amc, assetType, assetID, "", hashPreimage); + expect(assetClaimInvocation).to.be.a('boolean'); + expect(assetClaimInvocation).to.equal(false); + assetClaimInvocation = await assetManager.claimAssetInHTLC(amc, assetType, assetID, lockerECert, ""); + expect(assetClaimInvocation).to.be.a('boolean'); + expect(assetClaimInvocation).to.equal(false); + }); + + it("submit asset claim invocation", async () => { + let assetAgreementStr = assetManager.createAssetExchangeAgreementSerialized(assetType, assetID, "", lockerECert); + let claimInfoStr = assetManager.createAssetClaimInfoSerialized(hashPreimage); + amcStub.withArgs("ClaimAsset", assetAgreementStr, claimInfoStr).resolves(true); + let assetClaimInvocation = await assetManager.claimAssetInHTLC(amc, assetType, assetID, lockerECert, hashPreimage); + expect(assetClaimInvocation).to.be.a('boolean'); + expect(assetClaimInvocation).to.equal(true); + }); + }); + + describe("claim fungible asset locked in HTLC", () => { + let amcStub; + const hashPreimage = "xyz+123-*ty%"; + const contractId = "CONTRACT-1234"; + + beforeEach(() => { + amcStub = sinon.stub(amc, "submitTransaction").resolves(false); + }); + + it("asset claim fails with invalid parameters", async () => { + let assetClaimInvocation = await assetManager.claimFungibleAssetInHTLC(null, contractId, hashPreimage); + expect(assetClaimInvocation).to.be.a('boolean'); + expect(assetClaimInvocation).to.equal(false); + assetClaimInvocation = await assetManager.claimFungibleAssetInHTLC(amc, "", hashPreimage); + expect(assetClaimInvocation).to.be.a('boolean'); + expect(assetClaimInvocation).to.equal(false); + assetClaimInvocation = await assetManager.claimFungibleAssetInHTLC(amc, contractId, ""); + expect(assetClaimInvocation).to.be.a('boolean'); + expect(assetClaimInvocation).to.equal(false); + }); + + it("submit asset claim invocation", async () => { + let claimInfoStr = assetManager.createAssetClaimInfoSerialized(hashPreimage); + amcStub.withArgs("ClaimFungibleAsset", contractId, claimInfoStr).resolves(true); + let assetClaimInvocation = await assetManager.claimFungibleAssetInHTLC(amc, contractId, hashPreimage); + expect(assetClaimInvocation).to.be.a('boolean'); + expect(assetClaimInvocation).to.equal(true); + }); + }); + + describe("reclaim unique asset locked in HTLC", () => { + let amcStub; + + beforeEach(() => { + amcStub = sinon.stub(amc, "submitTransaction").resolves(false); + }); + + it("asset reclaim fails with invalid parameters", async () => { + let assetReclaimInvocation = await assetManager.reclaimAssetInHTLC(null, assetType, assetID, recipientECert); + expect(assetReclaimInvocation).to.be.a('boolean'); + expect(assetReclaimInvocation).to.equal(false); + assetReclaimInvocation = await assetManager.reclaimAssetInHTLC(amc, "", assetID, recipientECert); + expect(assetReclaimInvocation).to.be.a('boolean'); + expect(assetReclaimInvocation).to.equal(false); + assetReclaimInvocation = await assetManager.reclaimAssetInHTLC(amc, assetType, "", recipientECert); + expect(assetReclaimInvocation).to.be.a('boolean'); + expect(assetReclaimInvocation).to.equal(false); + assetReclaimInvocation = await assetManager.reclaimAssetInHTLC(amc, assetType, assetID, ""); + expect(assetReclaimInvocation).to.be.a('boolean'); + expect(assetReclaimInvocation).to.equal(false); + }); + + it("submit asset claim invocation", async () => { + let assetAgreementStr = assetManager.createAssetExchangeAgreementSerialized(assetType, assetID, recipientECert, ""); + amcStub.withArgs("UnlockAsset", assetAgreementStr).resolves(true); + let assetReclaimInvocation = await assetManager.reclaimAssetInHTLC(amc, assetType, assetID, recipientECert); + expect(assetReclaimInvocation).to.be.a('boolean'); + expect(assetReclaimInvocation).to.equal(true); + }); + }); + + describe("reclaim fungible asset locked in HTLC", () => { + let amcStub; + const contractId = "CONTRACT-1234"; + + beforeEach(() => { + amcStub = sinon.stub(amc, "submitTransaction").resolves(false); + }); + + it("asset reclaim fails with invalid parameters", async () => { + let assetReclaimInvocation = await assetManager.reclaimFungibleAssetInHTLC(null, contractId); + expect(assetReclaimInvocation).to.be.a('boolean'); + expect(assetReclaimInvocation).to.equal(false); + assetReclaimInvocation = await assetManager.reclaimFungibleAssetInHTLC(amc, ""); + expect(assetReclaimInvocation).to.be.a('boolean'); + expect(assetReclaimInvocation).to.equal(false); + }); + + it("submit asset claim invocation", async () => { + amcStub.withArgs("UnlockFungibleAsset", contractId).resolves(true); + let assetReclaimInvocation = await assetManager.reclaimFungibleAssetInHTLC(amc, contractId); + expect(assetReclaimInvocation).to.be.a('boolean'); + expect(assetReclaimInvocation).to.equal(true); + }); + }); + + describe("check unique asset lock status in HTLC", () => { + let amcStub; + + beforeEach(() => { + amcStub = sinon.stub(amc, "evaluateTransaction").resolves(false); + }); + + it("asset lock status check fails with invalid parameters", async () => { + let assetLockQuery = await assetManager.isAssetLockedInHTLC(null, assetType, assetID, recipientECert, lockerECert); + expect(assetLockQuery).to.be.a('boolean'); + expect(assetLockQuery).to.equal(false); + assetLockQuery = await assetManager.isAssetLockedInHTLC(amc, "", assetID, recipientECert, lockerECert); + expect(assetLockQuery).to.be.a('boolean'); + expect(assetLockQuery).to.equal(false); + assetLockQuery = await assetManager.isAssetLockedInHTLC(amc, assetType, "", recipientECert, lockerECert); + expect(assetLockQuery).to.be.a('boolean'); + expect(assetLockQuery).to.equal(false); + assetLockQuery = await assetManager.isAssetLockedInHTLC(amc, assetType, assetID, "", lockerECert); + expect(assetLockQuery).to.be.a('boolean'); + expect(assetLockQuery).to.equal(false); + assetLockQuery = await assetManager.isAssetLockedInHTLC(amc, assetType, assetID, recipientECert, ""); + expect(assetLockQuery).to.be.a('boolean'); + expect(assetLockQuery).to.equal(false); + }); + + it("submit asset lock status query", async () => { + let assetAgreementStr = assetManager.createAssetExchangeAgreementSerialized(assetType, assetID, recipientECert, lockerECert); + amcStub.withArgs("IsAssetLocked", assetAgreementStr).resolves(true); + let assetLockQuery = await assetManager.isAssetLockedInHTLC(amc, assetType, assetID, recipientECert, lockerECert); + expect(assetLockQuery).to.be.a('boolean'); + expect(assetLockQuery).to.equal(true); + }); + }); + + describe("check fungible asset lock status in HTLC", () => { + let amcStub; + const contractId = "CONTRACT-1234"; + + beforeEach(() => { + amcStub = sinon.stub(amc, "evaluateTransaction").resolves(false); + }); + + it("asset lock status check fails with invalid parameters", async () => { + let assetLockQuery = await assetManager.isFungibleAssetLockedInHTLC(null, contractId); + expect(assetLockQuery).to.be.a('boolean'); + expect(assetLockQuery).to.equal(false); + assetLockQuery = await assetManager.isFungibleAssetLockedInHTLC(amc, ""); + expect(assetLockQuery).to.be.a('boolean'); + expect(assetLockQuery).to.equal(false); + }); + + it("submit asset lock status query", async () => { + amcStub.withArgs("IsFungibleAssetLocked", contractId).resolves(true); + let assetLockQuery = await assetManager.isFungibleAssetLockedInHTLC(amc, contractId); + expect(assetLockQuery).to.be.a('boolean'); + expect(assetLockQuery).to.equal(true); + }); + }); +}); diff --git a/sdks/fabric/interoperation-node-sdk/test/data/anotherSignCert.pem b/sdks/fabric/interoperation-node-sdk/test/data/anotherSignCert.pem new file mode 100644 index 000000000..202820972 --- /dev/null +++ b/sdks/fabric/interoperation-node-sdk/test/data/anotherSignCert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICNjCCAd2gAwIBAgIRAJ7xltB2QbZ1NXbpj1g9dIcwCgYIKoZIzj0EAwIwezEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xHTAbBgNVBAoTFGNhcnJpZXJvcmcudHJhZGUuY29tMSAwHgYDVQQD +ExdjYS5jYXJyaWVyb3JnLnRyYWRlLmNvbTAeFw0yMTAyMjIxMDA4MDBaFw0zMTAy +MjAxMDA4MDBaMHAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw +FAYDVQQHEw1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQLEwZjbGllbnQxIzAhBgNVBAMM +GlVzZXIxQGNhcnJpZXJvcmcudHJhZGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAENb8HAEq+6w/QkyOcuJ8dxSD5dlqzGOYIrLA9iQ8RuwvQtGOQk1IElK/T +xnSUXBTQfKSW7jszxf8ixt7MhJunJqNNMEswDgYDVR0PAQH/BAQDAgeAMAwGA1Ud +EwEB/wQCMAAwKwYDVR0jBCQwIoAg0Z9daWiEhKVlUqx3z5b7k960IvK2pKbf0Tb1 +Vw3baogwCgYIKoZIzj0EAwIDRwAwRAIgHDVXaD5fU3RMxWJY2NCdOB8mbrPhmMn7 +qVeftYVkaXwCIE+/Oy2LJVZNPkUwQ3qlVLEhdSYxJVKEHj9Aob9wxIsz +-----END CERTIFICATE-----