From 4e81b929ea4996b345616e6a579dccdedc295de3 Mon Sep 17 00:00:00 2001 From: Sandeep Nishad Date: Mon, 12 Jun 2023 22:18:29 +0530 Subject: [PATCH] feat(weaver): added multiple participants support for data sharing in corda - refactor(weaver): add pledge verification in interop contract - fix(corda-at): typo in ResponderRole for weaver asset transfer - fix(ci): rename asset-transfer-fabric job to asset-transfer - feat(weaver): add Consume command for external state Signed-off-by: Sandeep Nishad --- .../workflows/test_weaver-asset-transfer.yaml | 4 +- .../corda/contracts/AssetTransferContract.kt | 26 ++-- .../corda/contracts/ExternalStateContract.kt | 14 +- .../imodule/corda/states/AssetPledgeState.kt | 4 + .../corda/flows/AssetExchangeHTLCFlows.kt | 14 +- .../imodule/corda/flows/AssetTransferFlows.kt | 132 ++++++++++++++++-- .../corda/flows/WriteExternalStateFlows.kt | 93 +++++++++++- .../client/AssetExchangeManager.kt | 6 +- .../client/AssetStateManager.kt | 3 + .../client/AssetTransferManager.kt | 16 ++- .../client/HouseTokenExchangeManager.kt | 6 +- .../client/HouseTokenTransferManager.kt | 10 +- .../client/InteropManager.kt | 2 + .../weaver/sdk/corda/InteroperableHelper.kt | 17 ++- 14 files changed, 284 insertions(+), 63 deletions(-) diff --git a/.github/workflows/test_weaver-asset-transfer.yaml b/.github/workflows/test_weaver-asset-transfer.yaml index 9924c0ab7c..7b6ed3ea62 100644 --- a/.github/workflows/test_weaver-asset-transfer.yaml +++ b/.github/workflows/test_weaver-asset-transfer.yaml @@ -23,7 +23,7 @@ concurrency: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - asset-transfer-fabric: + asset-transfer: if: ${{ false }} # The type of runner that the job will run on runs-on: ubuntu-latest @@ -657,7 +657,7 @@ jobs: if: failure() run: docker logs driver-corda-Corda_Network2 - asset-transfer-fabric-local: + asset-transfer-local: # if: ${{ false }} # The type of runner that the job will run on runs-on: ubuntu-latest diff --git a/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/contracts/AssetTransferContract.kt b/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/contracts/AssetTransferContract.kt index f4683b976e..45bb24dd40 100644 --- a/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/contracts/AssetTransferContract.kt +++ b/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/contracts/AssetTransferContract.kt @@ -9,6 +9,7 @@ package org.hyperledger.cacti.weaver.imodule.corda.contracts import org.hyperledger.cacti.weaver.imodule.corda.states.AssetPledgeState import org.hyperledger.cacti.weaver.imodule.corda.states.AssetClaimStatusState import org.hyperledger.cacti.weaver.imodule.corda.states.NetworkIdState +import org.hyperledger.cacti.weaver.imodule.corda.states.ExternalState import net.corda.core.contracts.CommandData import net.corda.core.contracts.Contract import net.corda.core.contracts.requireSingleCommand @@ -17,6 +18,7 @@ import net.corda.core.contracts.StaticPointer import net.corda.core.transactions.LedgerTransaction import java.time.Instant import java.util.* +import co.paralleluniverse.fibers.Suspendable /** * AssetTransferContract defines the rules for managing a [AssetPledgeState]. @@ -37,14 +39,13 @@ class AssetTransferContract : Contract { when (command.value) { is Commands.Pledge -> requireThat { "There should be one input state." using (tx.inputs.size == 1) - "There should be one output state." using (tx.outputs.size == 1) - "The output state should be of type AssetPledgeState." using (tx.outputs[0].data is AssetPledgeState) + "There should be one output AssetPledgeState." using (tx.outputsOfType().size == 1) // Get the Asset Pledge State - val pledgeState = tx.outputs[0].data as AssetPledgeState + val pledgeState = tx.outputsOfType()[0] // Check if output belong to this contract - "Output state should belong to this contract" using (tx.outputs[0].contract.equals(ID)) + // "Output state should belong to this contract" using (tx.outputsOfType().contract().equals(ID)) // Check if timeout is beyond current time val expiryTime = Instant.ofEpochSecond(pledgeState.expiryTimeSecs) @@ -69,20 +70,21 @@ class AssetTransferContract : Contract { "AssetPledgeState.localNetwokId must match with the networkId of current network." using (pledgeState.localNetworkId.equals(validNetworkIdState.networkId)) } is Commands.ClaimRemoteAsset -> requireThat { - "There should be no input state." using (tx.inputs.size == 0) + "There should be one input External state." using (tx.inputsOfType().size == 1) "There should be two output states." using (tx.outputs.size == 2) "One of the output states should be of type AssetClaimStatusState." using (tx.outputsOfType().size == 1) // Check if output state [AssetClaimStatusState] belongs to this contract - "Output state should belong to this contract" using (tx.outputs[1].contract.equals(ID)) + //"Output state should belong to this contract" using (claimStateAndRefs[0].contract.equals(ID)) - // Get the input asset pledge state - val claimState = tx.outputs[1].data as AssetClaimStatusState + // Get the output asset claim state + val claimState = tx.outputsOfType()[0] val inReferences = tx.referenceInputRefsOfType() "There should be a single reference input network id." using (inReferences.size == 1) val validNetworkIdState = inReferences.get(0).state.data + // Claim State checks "AssetClaimStatusState.localNetwokID must match with the networkId of current network." using (claimState.localNetworkID.equals(validNetworkIdState.networkId)) // Check if timeWindow <= expiryTime @@ -100,12 +102,12 @@ class AssetTransferContract : Contract { "The required signers of the transaction must include the recipient." using (command.signers.containsAll(requiredSigners)) } is Commands.ReclaimPledgedAsset -> requireThat { - "There should be one input state." using (tx.inputs.size == 1) - "The input state should be of type AssetPledgeState." using (tx.inputs[0].state.data is AssetPledgeState) + "There should be one input AssetPledgeState." using (tx.inputsOfType().size == 1) + "There should be one input ExternalState." using (tx.inputsOfType().size == 1) "There should be one output state." using (tx.outputs.size == 1) // Get the input asset pledge state - val pledgeState = tx.inputs[0].state.data as AssetPledgeState + val pledgeState = tx.inputsOfType()[0] // Check if timeWindow > expiryTime val fromTime = tx.timeWindow!!.fromTime!! @@ -140,4 +142,4 @@ class AssetTransferContract : Contract { class ReclaimPledgedAsset : Commands class ClaimRemoteAsset : Commands } -} +} \ No newline at end of file diff --git a/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/contracts/ExternalStateContract.kt b/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/contracts/ExternalStateContract.kt index 08b8ee3e4d..363346ae76 100644 --- a/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/contracts/ExternalStateContract.kt +++ b/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/contracts/ExternalStateContract.kt @@ -34,22 +34,30 @@ class ExternalStateContract : Contract { override fun verify(tx: LedgerTransaction) { val command = tx.commands.requireSingleCommand() when(command.value) { - is Commands.Issue -> requireThat { + is Commands.Create -> requireThat { "There should be no input states" using (tx.inputs.isEmpty()) "There should be one output state" using (tx.outputs.size == 1) "The output state should be of type ExternalState" using (tx.outputs[0].data is ExternalState) val participantKeys = tx.outputs[0].data.participants.map { it.owningKey } "The required signers of the transaction must include all participants" using (command.signers.containsAll(participantKeys)) } + is Commands.Consume -> requireThat { + "There should be one ExternalState input states" using (tx.inputsOfType().size == 1) + "There should be no ExternalState output states" using (tx.outputsOfType().size == 0) + val participantKeys = tx.inputsOfType()[0].participants.map { it.owningKey } + "The required signers of the transaction must include all participants" using (command.signers.containsAll(participantKeys)) + } } } /** * Commands are used to indicate the intent of a transaction. * Commands for [ExternalStateContract] are: - * - Issue + * - Create + * - Consume */ interface Commands : CommandData { - class Issue : Commands + class Create : Commands + class Consume : Commands } } \ No newline at end of file diff --git a/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/states/AssetPledgeState.kt b/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/states/AssetPledgeState.kt index 6890893dce..b010315cf1 100644 --- a/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/states/AssetPledgeState.kt +++ b/weaver/core/network/corda-interop-app/interop-contracts/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/states/AssetPledgeState.kt @@ -50,6 +50,10 @@ data class AssetPledgeState( override val participants: List get() = listOf(locker) } +/* + * Since there is a limit on the number of parameters to the workflow + * This data class is used as parameter. + */ @CordaSerializable data class AssetPledgeParameters( var assetType: String, diff --git a/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/AssetExchangeHTLCFlows.kt b/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/AssetExchangeHTLCFlows.kt index f6f5526362..b1d9023d74 100644 --- a/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/AssetExchangeHTLCFlows.kt +++ b/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/AssetExchangeHTLCFlows.kt @@ -47,7 +47,7 @@ import java.util.Base64 @CordaSerializable enum class ResponderRole { - LOCKER, RECIPIENT, ISSUER, OBSERVER + LOCKER, RECIPIENT, SIGNER, OBSERVER } /** @@ -121,7 +121,7 @@ object LockAssetHTLC { /// Add issuer session if recipient or locker (i.e. me) is not issuer if (!recipient.equals(issuer) && !ourIdentity.equals(issuer)) { val issuerSession = initiateFlow(issuer) - issuerSession.send(ResponderRole.ISSUER) + issuerSession.send(ResponderRole.SIGNER) sessions += issuerSession } val fullySignedTx = subFlow(CollectSignaturesFlow(partSignedTx, sessions)) @@ -150,7 +150,7 @@ object LockAssetHTLC { @Suspendable override fun call(): SignedTransaction { val role = session.receive().unwrap { it } - if (role == ResponderRole.ISSUER) { + if (role == ResponderRole.SIGNER) { val signTransactionFlow = object : SignTransactionFlow(session) { override fun checkTransaction(stx: SignedTransaction) = requireThat { } @@ -407,7 +407,7 @@ object ClaimAssetHTLC { var sessions = listOf() if (!assetExchangeHTLCState.recipient.equals(issuer)) { val issuerSession = initiateFlow(issuer) - issuerSession.send(ResponderRole.ISSUER) + issuerSession.send(ResponderRole.SIGNER) sessions += issuerSession } val fullySignedTx = subFlow(CollectSignaturesFlow(partSignedTx, sessions)) @@ -437,7 +437,7 @@ object ClaimAssetHTLC { @Suspendable override fun call(): SignedTransaction { val role = session.receive().unwrap { it } - if (role == ResponderRole.ISSUER) { + if (role == ResponderRole.SIGNER) { val signTransactionFlow = object : SignTransactionFlow(session) { override fun checkTransaction(stx: SignedTransaction) = requireThat { } @@ -542,7 +542,7 @@ object UnlockAssetHTLC { if (!ourIdentity.equals(issuer)) { val issuerSession = initiateFlow(issuer) - issuerSession.send(ResponderRole.ISSUER) + issuerSession.send(ResponderRole.SIGNER) sessions += issuerSession } if (!ourIdentity.equals(assetExchangeHTLCState.locker)) { @@ -576,7 +576,7 @@ object UnlockAssetHTLC { @Suspendable override fun call(): SignedTransaction { val role = session.receive().unwrap { it } - if (role == ResponderRole.ISSUER) { + if (role == ResponderRole.SIGNER) { val signTransactionFlow = object : SignTransactionFlow(session) { override fun checkTransaction(stx: SignedTransaction) = requireThat { } diff --git a/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/AssetTransferFlows.kt b/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/AssetTransferFlows.kt index 9ea4b5ac62..e080f51287 100644 --- a/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/AssetTransferFlows.kt +++ b/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/AssetTransferFlows.kt @@ -12,9 +12,15 @@ import org.hyperledger.cacti.weaver.imodule.corda.contracts.AssetTransferContrac import org.hyperledger.cacti.weaver.imodule.corda.states.AssetPledgeState import org.hyperledger.cacti.weaver.imodule.corda.states.AssetClaimStatusState import org.hyperledger.cacti.weaver.imodule.corda.states.NetworkIdState +import org.hyperledger.cacti.weaver.imodule.corda.contracts.ExternalStateContract +import org.hyperledger.cacti.weaver.imodule.corda.states.ExternalState import org.hyperledger.cacti.weaver.protos.common.asset_transfer.AssetTransfer import org.hyperledger.cacti.weaver.protos.corda.ViewDataOuterClass import org.hyperledger.cacti.weaver.protos.common.interop_payload.InteropPayloadOuterClass +import org.hyperledger.cacti.weaver.protos.common.state.State +import org.hyperledger.cacti.weaver.protos.fabric.view_data.ViewData + +import org.hyperledger.fabric.protos.peer.ProposalPackage import com.google.protobuf.ByteString import net.corda.core.contracts.ContractState @@ -49,7 +55,7 @@ import java.util.Calendar */ @CordaSerializable enum class AssetTransferResponderRole { - PLEDGER, ISSUER, OBSERVER + PLEDGER, SIGNER, OBSERVER } /** @@ -142,7 +148,7 @@ object PledgeAsset { // Add issuer session if locker (i.e. me) is not issuer if (!ourIdentity.equals(issuer)) { val issuerSession = initiateFlow(issuer) - issuerSession.send(AssetTransferResponderRole.ISSUER) + issuerSession.send(AssetTransferResponderRole.SIGNER) sessions += issuerSession } val fullySignedTx = subFlow(CollectSignaturesFlow(partSignedTx, sessions)) @@ -171,9 +177,10 @@ object PledgeAsset { @Suspendable override fun call(): SignedTransaction { val role = session.receive().unwrap { it } - if (role == AssetTransferResponderRole.ISSUER) { + if (role == AssetTransferResponderRole.SIGNER) { val signTransactionFlow = object : SignTransactionFlow(session) { override fun checkTransaction(stx: SignedTransaction) = requireThat { + } } try { @@ -473,6 +480,7 @@ object ReclaimPledgedAsset { override fun call(): Either = try { val linearId = getLinearIdFromString(pledgeId) + val externalStateAndRef = subFlow(GetExternalStateAndRefByLinearId(claimStatusLinearId)) val viewData = subFlow(GetExternalStateByLinearId(claimStatusLinearId)) val externalStateView = ViewDataOuterClass.ViewData.parseFrom(viewData) val interopPayload = InteropPayloadOuterClass.InteropPayload.parseFrom(externalStateView.notarizedPayloadsList[0].payload) @@ -513,6 +521,10 @@ object ReclaimPledgedAsset { issuer.owningKey ).toList() ) + + val externalStateConsumeCommand = Command(ExternalStateContract.Commands.Consume(), + externalStateAndRef.state.data.participants.map { it.owningKey } + ) val networkIdStateRef = subFlow(RetrieveNetworkIdStateAndRef()) @@ -524,6 +536,7 @@ object ReclaimPledgedAsset { val txBuilder = TransactionBuilder(notary) .addInputState(assetPledgeStateAndRef) + .addInputState(externalStateAndRef) .addOutputState(reclaimAssetState, assetStateContractId) .addCommand(reclaimCmd).apply { networkIdStateRef!!.let { @@ -531,6 +544,7 @@ object ReclaimPledgedAsset { } } .addCommand(assetCreateCmd) + .addCommand(externalStateConsumeCommand) .setTimeWindow(TimeWindow.fromOnly(Instant.ofEpochSecond(assetPledgeState.expiryTimeSecs).plusNanos(1))) // Verify and collect signatures on the transaction @@ -542,7 +556,7 @@ object ReclaimPledgedAsset { if (!ourIdentity.equals(issuer)) { val issuerSession = initiateFlow(issuer) - issuerSession.send(AssetTransferResponderRole.ISSUER) + issuerSession.send(AssetTransferResponderRole.SIGNER) sessions += issuerSession } if (!ourIdentity.equals(assetPledgeState.locker)) { @@ -572,9 +586,20 @@ object ReclaimPledgedAsset { @Suspendable override fun call(): SignedTransaction { val role = session.receive().unwrap { it } - if (role == AssetTransferResponderRole.ISSUER) { + if (role == AssetTransferResponderRole.SIGNER) { val signTransactionFlow = object : SignTransactionFlow(session) { override fun checkTransaction(stx: SignedTransaction) = requireThat { + val tx = stx.tx.toLedgerTransaction(serviceHub) + // Get the input remote pledge state (external state) + val remoteClaimStatus = parseExternalStateForClaimStatus( + tx.inputsOfType()[0] + ) + val pledgeState = tx.inputsOfType()[0] + + // Claim Status checks + "Expiry Time should match in PledgeState and ClaimStatus" using (remoteClaimStatus.expiryTimeSecs == pledgeState.expiryTimeSecs) + "ClaimStatus should be false" using (remoteClaimStatus.claimStatus == false) + "Expiration status should be true" using (remoteClaimStatus.expirationStatus == true) } } try { @@ -683,6 +708,7 @@ object ClaimRemoteAsset { override fun call(): Either = try { // get the asset pledge details fetched earlier via interop query from import to export n/w + val externalStateAndRef = subFlow(GetExternalStateAndRefByLinearId(pledgeStatusLinearId)) val viewData = subFlow(GetExternalStateByLinearId(pledgeStatusLinearId)) val externalStateView = ViewDataOuterClass.ViewData.parseFrom(viewData) val interopPayload = InteropPayloadOuterClass.InteropPayload.parseFrom(externalStateView.notarizedPayloadsList[0].payload) @@ -730,6 +756,10 @@ object ClaimRemoteAsset { issuer.owningKey ).toList() ) + + val externalStateConsumeCommand = Command(ExternalStateContract.Commands.Consume(), + externalStateAndRef.state.data.participants.map { it.owningKey } + ) // Make sure the pledge has not expired (we assume the expiry timestamp set by the remote network) if (currentTimeSecs >= assetPledgeStatus.expiryTimeSecs) { @@ -766,6 +796,7 @@ object ClaimRemoteAsset { println("assetContractId: ${assetContractId}") val txBuilder = TransactionBuilder(notary) + .addInputState(externalStateAndRef) .addOutputState(outputAssetState, assetContractId) .addOutputState(assetClaimStatusState, AssetTransferContract.ID) .addCommand(claimCmd).apply { @@ -774,6 +805,7 @@ object ClaimRemoteAsset { } } .addCommand(assetCreateCmd) + .addCommand(externalStateConsumeCommand) .setTimeWindow(TimeWindow.untilOnly(Instant.ofEpochSecond(assetPledgeStatus.expiryTimeSecs))) // Verify and collect signatures on the transaction @@ -785,7 +817,7 @@ object ClaimRemoteAsset { if (!ourIdentity.equals(issuer)) { val issuerSession = initiateFlow(issuer) - issuerSession.send(AssetTransferResponderRole.ISSUER) + issuerSession.send(AssetTransferResponderRole.SIGNER) sessions += issuerSession } @@ -810,21 +842,30 @@ object ClaimRemoteAsset { class Acceptor(val session: FlowSession) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { - val role = session.receive().unwrap { it } - if (role == ResponderRole.ISSUER) { + val role = session.receive().unwrap { it } + if (role == AssetTransferResponderRole.SIGNER) { val signTransactionFlow = object : SignTransactionFlow(session) { override fun checkTransaction(stx: SignedTransaction) = requireThat { + val tx = stx.tx.toLedgerTransaction(serviceHub) + val remotePledgeStatus = parseExternalStateForPledgeStatus( + tx.inputsOfType()[0] + ) + val claimState = tx.outputsOfType()[0] + // Pledge State checks + "Expiry Time should match in PledgeState and ClaimStatus" using (remotePledgeStatus.expiryTimeSecs == claimState.expiryTimeSecs) + "Recipient should match in Pledge State and ClaimStatus" using (remotePledgeStatus.recipient == claimState.recipientCert) + "NetworkId should match in Pledge State and ClaimStatu" using (remotePledgeStatus.remoteNetworkID == claimState.localNetworkID) } } try { val txId = subFlow(signTransactionFlow).id - println("Issuer signed transaction.") + println("Party: ${ourIdentity} signed transaction.") return subFlow(ReceiveFinalityFlow(session, expectedTxId = txId)) } catch (e: Exception) { println("Error signing claim asset transaction by issuer: ${e.message}\n") return subFlow(ReceiveFinalityFlow(session)) } - } else if (role == ResponderRole.OBSERVER) { + } else if (role == AssetTransferResponderRole.OBSERVER) { val sTx = subFlow(ReceiveFinalityFlow(session, statesToRecord = StatesToRecord.ALL_VISIBLE)) println("Received Tx: ${sTx} and recorded states.") return sTx @@ -864,3 +905,74 @@ fun resolveGetAssetStateAndContractIdFlow(flowName: String, flowArgs: List) println("Flow Resolution Error: ${e.message} \n") Left(Error("Flow Resolution Error: ${e.message}")) } + + + +@Suspendable +fun parseExternalStateForPledgeStatus( + externalState: ExternalState +): AssetTransfer.AssetPledge { + val viewMetaByteArray = externalState.meta + val viewDataByteArray = externalState.state + val meta = State.Meta.parseFrom(viewMetaByteArray) + + var payload: ByteArray + when (meta.protocol) { + State.Meta.Protocol.CORDA -> { + val cordaViewData = ViewDataOuterClass.ViewData.parseFrom(viewDataByteArray) + println("cordaViewData: $cordaViewData") + val interopPayload = InteropPayloadOuterClass.InteropPayload.parseFrom(cordaViewData.notarizedPayloadsList[0].payload) + payload = interopPayload.payload.toByteArray() + } + State.Meta.Protocol.FABRIC -> { + val fabricViewData = ViewData.FabricView.parseFrom(viewDataByteArray) + println("fabricViewData: $fabricViewData") + // TODO: We assume here that the response payloads have been matched earlier, but perhaps we should match them here too + val chaincodeAction = ProposalPackage.ChaincodeAction.parseFrom(fabricViewData.endorsedProposalResponsesList[0].payload.extension) + val interopPayload = InteropPayloadOuterClass.InteropPayload.parseFrom(chaincodeAction.response.payload) + payload = interopPayload.payload.toByteArray() + } + else -> { + println("GetExternalStateByLinearId Error: Unrecognized protocol.\n") + throw IllegalArgumentException("Error: Unrecognized protocol.") + } + } + val payloadDecoded = Base64.getDecoder().decode(payload) + val assetPledgeStatus = AssetTransfer.AssetPledge.parseFrom(payloadDecoded) + return assetPledgeStatus +} + +@Suspendable +fun parseExternalStateForClaimStatus( + externalState: ExternalState +): AssetTransfer.AssetClaimStatus { + val viewMetaByteArray = externalState.meta + val viewDataByteArray = externalState.state + val meta = State.Meta.parseFrom(viewMetaByteArray) + + var payload: ByteArray + when (meta.protocol) { + State.Meta.Protocol.CORDA -> { + val cordaViewData = ViewDataOuterClass.ViewData.parseFrom(viewDataByteArray) + println("cordaViewData: $cordaViewData") + val interopPayload = InteropPayloadOuterClass.InteropPayload.parseFrom(cordaViewData.notarizedPayloadsList[0].payload) + payload = interopPayload.payload.toByteArray() + } + State.Meta.Protocol.FABRIC -> { + val fabricViewData = ViewData.FabricView.parseFrom(viewDataByteArray) + println("fabricViewData: $fabricViewData") + // TODO: We assume here that the response payloads have been matched earlier, but perhaps we should match them here too + val chaincodeAction = ProposalPackage.ChaincodeAction.parseFrom(fabricViewData.endorsedProposalResponsesList[0].payload.extension) + val interopPayload = InteropPayloadOuterClass.InteropPayload.parseFrom(chaincodeAction.response.payload) + payload = interopPayload.payload.toByteArray() + } + else -> { + println("GetExternalStateByLinearId Error: Unrecognized protocol.\n") + throw IllegalArgumentException("Error: Unrecognized protocol.") + } + } + val payloadDecoded = Base64.getDecoder().decode(payload) + val assetClaimStatus = AssetTransfer.AssetClaimStatus.parseFrom(payloadDecoded) + return assetClaimStatus +} + diff --git a/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/WriteExternalStateFlows.kt b/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/WriteExternalStateFlows.kt index db94d1e9e6..3b170bf936 100644 --- a/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/WriteExternalStateFlows.kt +++ b/weaver/core/network/corda-interop-app/interop-workflows/src/main/kotlin/org/hyperledger/cacti/weaver/imodule/corda/flows/WriteExternalStateFlows.kt @@ -15,10 +15,15 @@ import net.corda.core.contracts.Command import net.corda.core.contracts.UniqueIdentifier import net.corda.core.flows.* import net.corda.core.transactions.TransactionBuilder +import net.corda.core.contracts.StateAndRef import java.util.Base64 import net.corda.core.node.services.queryBy import net.corda.core.node.services.vault.QueryCriteria import com.google.protobuf.ByteString +import net.corda.core.identity.Party +import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.unwrap +import net.corda.core.contracts.requireThat import org.hyperledger.fabric.protos.msp.Identities import org.hyperledger.fabric.protos.peer.ProposalPackage @@ -39,10 +44,15 @@ import org.hyperledger.cacti.weaver.imodule.corda.states.ExternalState * @property view The view received from the foreign network. * @property address The address of the view, containing a location, securityDomain and view segment. */ +@InitiatingFlow @StartableByRPC -class WriteExternalStateInitiator( +class WriteExternalStateInitiator + @JvmOverloads + constructor( val viewBase64String: String, - val address: String): FlowLogic>() { + val address: String, + val participants: List = listOf() + ): FlowLogic>() { /** * The call() method captures the logic to perform the proof validation and the construction of @@ -60,16 +70,17 @@ class WriteExternalStateInitiator( verifyView(view, address, serviceHub).flatMap { println("View verification successful. Creating state to be stored in the vault.") // 2. Create the state to be stored + var externalStateParticipants = if (participants.contains(ourIdentity)) { participants } else { listOf(ourIdentity) + participants } val state = ExternalState( linearId = UniqueIdentifier(), - participants = listOf(ourIdentity), + participants = externalStateParticipants, meta = view.meta.toByteArray(), state = view.data.toByteArray()) println("Storing ExternalState in the vault:\n\tLinear Id = ${state.linearId}\n\tParticipants = ${state.participants}\n\tMeta = ${view.meta}\tState = ${Base64.getEncoder().encodeToString(state.state)}\n") // 3. Build the transaction val notary = serviceHub.networkMapCache.notaryIdentities.first() - val command = Command(ExternalStateContract.Commands.Issue(), ourIdentity.owningKey) + val command = Command(ExternalStateContract.Commands.Create(), ourIdentity.owningKey) val txBuilder = TransactionBuilder(notary) .addOutputState(state, ExternalStateContract.ID) .addCommand(command) @@ -77,7 +88,15 @@ class WriteExternalStateInitiator( // 4. Verify and collect signatures on the transaction txBuilder.verify(serviceHub) val tx = serviceHub.signInitialTransaction(txBuilder) - val sessions = listOf() + var sessions = listOf() + for (party in externalStateParticipants) { + if (!ourIdentity.equals(party)) { + val session = initiateFlow(party) + session.send(address) + sessions += session + } + } + val stx = subFlow(CollectSignaturesFlow(tx, sessions)) subFlow(FinalityFlow(stx, sessions)) @@ -90,6 +109,35 @@ class WriteExternalStateInitiator( } } +@InitiatedBy(WriteExternalStateInitiator::class) +class WriteExternalStateAcceptor(val session: FlowSession) : FlowLogic() { + @Suspendable + override fun call(): SignedTransaction { + val address = session.receive().unwrap { it } + val signTransactionFlow = object : SignTransactionFlow(session) { + override fun checkTransaction(stx: SignedTransaction) = requireThat { + val lTx = stx.tx.toLedgerTransaction(serviceHub) + val externalStates = lTx.outputsOfType() + "One External State as output" using (externalStates.size == 1) + val externalState = externalStates[0] + val view = State.View.newBuilder() + .setMeta(State.Meta.parseFrom(externalState.meta)) + .setData(ByteString.copyFrom(externalState.state)) + .build() + "Proof of state should be valid" using verifyView(view, address, serviceHub).isRight() + } + } + try { + val txId = subFlow(signTransactionFlow).id + println("Issuer signed transaction.") + return subFlow(ReceiveFinalityFlow(session, expectedTxId = txId)) + } catch (e: Exception) { + println("Error signing write external state transaction: ${e.message}\n") + return subFlow(ReceiveFinalityFlow(session)) + } + } +} + /** * The GetExternalBoLByLinearId flow is used to read an External State and parse @@ -198,3 +246,38 @@ class GetExternalStateByLinearId( } } + +/** + * The GetExternalBoLByLinearId flow is used to read an External State and parse + * the View Data. + * + * @property externalStateLinearId the linearId for the ExternalState. + */ +@StartableByRPC +class GetExternalStateAndRefByLinearId( + val externalStateLinearId: String +) : FlowLogic>() { + /** + * The call() method captures the logic to read external state written into the vault, + * and parse the payload and proof based on the protocol. + * + * @return Returns JSON string in ByteArray containing: payload, signatures, and proof message. + */ + @Suspendable + override fun call(): StateAndRef { + println("Getting External State for linearId $externalStateLinearId stored in vault\n.") + val linearId = UniqueIdentifier.fromString(externalStateLinearId) + //val linearId = externalStateLinearId + val states = serviceHub.vaultService.queryBy( + QueryCriteria.LinearStateQueryCriteria(linearId = listOf(linearId)) + ).states + + if (states.isEmpty()) { + println("Error: Could not find external state with linearId $linearId") + throw IllegalArgumentException("Error: Could not find external state with linearId $linearId") + } else { + return states.first() + } + } + +} diff --git a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetExchangeManager.kt b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetExchangeManager.kt index f4a291f7ff..0bca5c31fe 100644 --- a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetExchangeManager.kt +++ b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetExchangeManager.kt @@ -70,7 +70,7 @@ class LockAssetCommand : CliktCommand( try { val params = param!!.split(":").toTypedArray() var id: Any - val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=PartyA,L=London,C=GB"))!! + val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! hash.setSerializedHashBase64(hashBase64!!) if (fungible) { id = AssetManager.createFungibleHTLC( @@ -133,7 +133,7 @@ class ClaimAssetCommand : CliktCommand(help = "Claim a locked asset. Only Recipi password = "test", rpcPort = config["CORDA_PORT"]!!.toInt()) try { - val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=PartyA,L=London,C=GB"))!! + val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! hash.setPreimage(secret!!) val res = AssetManager.claimAssetInHTLC( rpc.proxy, @@ -169,7 +169,7 @@ class UnlockAssetCommand : CliktCommand(help = "Unlocks a locked asset after tim password = "test", rpcPort = config["CORDA_PORT"]!!.toInt()) try { - val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=PartyA,L=London,C=GB"))!! + val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! val res = AssetManager.reclaimAssetInHTLC( rpc.proxy, contractId!!, diff --git a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetStateManager.kt b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetStateManager.kt index b3c67207e1..9a08ef5ce0 100644 --- a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetStateManager.kt +++ b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetStateManager.kt @@ -24,6 +24,9 @@ import net.corda.core.identity.CordaX500Name import java.lang.Exception import net.corda.core.messaging.startFlow +//X500 Name for Issuer in sample corda testnet +val ISSUER_DN="O=PartyA,L=London,C=GB" + /** * The CLI command used to trigger a CreateState flow. * diff --git a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetTransferManager.kt b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetTransferManager.kt index ff7a0c2587..e69f0fd948 100644 --- a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetTransferManager.kt +++ b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/AssetTransferManager.kt @@ -90,7 +90,7 @@ class PledgeAssetCommand : CliktCommand(name="pledge-asset", // "thisParty" is set to the token "issuer" in case fungible house token; since we are using the same // SDK function claimPledgeFungibleAsset and Interop application for both the "Simple Asset" and // the "Fungible house token" corDapps, we pass the Identity of the party submitting the claim here. - val thisParty: Party = rpc.proxy.nodeInfo().legalIdentities.get(0) + val issuer: Party = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! var obs = listOf() if (observer != null) { @@ -111,7 +111,7 @@ class PledgeAssetCommand : CliktCommand(name="pledge-asset", nTimeout, "com.cordaSimpleApplication.flow.RetrieveStateAndRef", AssetContract.Commands.Delete(), - thisParty, + issuer, obs ) } else { @@ -125,7 +125,7 @@ class PledgeAssetCommand : CliktCommand(name="pledge-asset", nTimeout, "com.cordaSimpleApplication.flow.RetrieveBondAssetStateAndRef", BondAssetContract.Commands.Delete(), - thisParty, + issuer, obs ) } @@ -241,6 +241,7 @@ class ReclaimAssetCommand : CliktCommand(name="reclaim-pledged-asset", help = "R // SDK function claimPledgeFungibleAsset and Interop application for both the "Simple Asset" and // the "Fungible house token" corDapps, we pass the Identity of the party submitting the claim here. val thisParty: Party = rpc.proxy.nodeInfo().legalIdentities.get(0) + val issuer: Party = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! val params = param!!.split(":").toTypedArray() if (params.size != 2) { @@ -266,7 +267,7 @@ class ReclaimAssetCommand : CliktCommand(name="reclaim-pledged-asset", help = "R //val networkConfig: JSONObject = getRemoteNetworkConfig(assetPledgeState.localNetworkId) //val exportRelayAddress: String = networkConfig.getString("relayEndpoint") - val claimStatusLinearId: String = requestStateFromRemoteNetwork(exportRelayAddress!!, externalStateAddress, rpc.proxy, config) + val claimStatusLinearId: String = requestStateFromRemoteNetwork(exportRelayAddress!!, externalStateAddress, rpc.proxy, config, listOf(issuer)) var obs = listOf() if (observer != null) { @@ -344,6 +345,7 @@ class ClaimRemoteAssetCommand : CliktCommand(name="claim-remote-asset", help = " // SDK function claimPledgeFungibleAsset and Interop application for both the "Simple Asset" and // the "Fungible house token" corDapps, we pass the Identity of the party submitting the claim here. val thisParty: Party = rpc.proxy.nodeInfo().legalIdentities.get(0) + val issuer: Party = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! val recipientCert: String = fetchCertBase64Helper(rpc.proxy) val params = param!!.split(":").toTypedArray() if (params.size != 2) { @@ -368,7 +370,7 @@ class ClaimRemoteAssetCommand : CliktCommand(name="claim-remote-asset", help = " // below from the remote-network-config.json file //val networkConfig: JSONObject = getRemoteNetworkConfig(importNetworkId) //val importRelayAddress: String = networkConfig.getString("relayEndpoint") - val pledgeStatusLinearId: String = requestStateFromRemoteNetwork(importRelayAddress!!, externalStateAddress, rpc.proxy, config) + val pledgeStatusLinearId: String = requestStateFromRemoteNetwork(importRelayAddress!!, externalStateAddress, rpc.proxy, config, listOf(issuer)) var res: Any if (transferCategory!!.contains("token.")) { @@ -604,7 +606,8 @@ fun requestStateFromRemoteNetwork( localRelayAddress: String, externalStateAddress: String, proxy: CordaRPCOps, - config: Map) : String + config: Map, + externalStateParticipants: List) : String { var linearId: String = "" val networkName = System.getenv("NETWORK_NAME") ?: "Corda_Network" @@ -615,6 +618,7 @@ fun requestStateFromRemoteNetwork( localRelayAddress, externalStateAddress, networkName, + externalStateParticipants, config["RELAY_TLS"]!!.toBoolean(), config["RELAY_TLSCA_TRUST_STORE"]!!, config["RELAY_TLSCA_TRUST_STORE_PASSWORD"]!!, diff --git a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/HouseTokenExchangeManager.kt b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/HouseTokenExchangeManager.kt index 4ddd134edb..3c531cf174 100644 --- a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/HouseTokenExchangeManager.kt +++ b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/HouseTokenExchangeManager.kt @@ -78,7 +78,7 @@ class LockHouseTokenCommand : CliktCommand(name="lock", try { val params = param!!.split(":").toTypedArray() var id: Any - val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=PartyA,L=London,C=GB"))!! + val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! val issuedTokenType = rpc.proxy.startFlow(::GetIssuedTokenType, "house").returnValue.get() println("TokenType: $issuedTokenType") var obs = listOf() @@ -151,7 +151,7 @@ class ClaimHouseTokenCommand : CliktCommand(name="claim", help = "Claim a locked rpcPort = config["CORDA_PORT"]!!.toInt()) try { val issuedTokenType = rpc.proxy.startFlow(::GetIssuedTokenType, "house").returnValue.get() - val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=PartyA,L=London,C=GB"))!! + val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! var obs = listOf() if (observer != null) { obs += rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(observer!!))!! @@ -193,7 +193,7 @@ class UnlockHouseTokenCommand : CliktCommand(name="unlock", help = "Unlocks a lo password = "test", rpcPort = config["CORDA_PORT"]!!.toInt()) try { - val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=PartyA,L=London,C=GB"))!! + val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! val issuedTokenType = rpc.proxy.startFlow(::GetIssuedTokenType, "house").returnValue.get() println("TokenType: $issuedTokenType") var obs = listOf() diff --git a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/HouseTokenTransferManager.kt b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/HouseTokenTransferManager.kt index 8fb42e249a..c96d6aa145 100644 --- a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/HouseTokenTransferManager.kt +++ b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/HouseTokenTransferManager.kt @@ -90,7 +90,7 @@ class PledgeHouseTokenCommand : CliktCommand(name="pledge-asset", try { val params = param!!.split(":").toTypedArray() var result: Either - val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=PartyA,L=London,C=GB"))!! + val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! val issuedTokenType = rpc.proxy.startFlow(::GetIssuedTokenType, "house").returnValue.get() println("TokenType: $issuedTokenType") @@ -181,7 +181,7 @@ class ReclaimHouseTokenCommand : CliktCommand(name="reclaim-pledged-asset", help password = "test", rpcPort = config["CORDA_PORT"]!!.toInt()) try { - val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=PartyA,L=London,C=GB"))!! + val issuer = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! val issuedTokenType = rpc.proxy.startFlow(::GetIssuedTokenType, "house").returnValue.get() println("TokenType: $issuedTokenType") @@ -207,7 +207,7 @@ class ReclaimHouseTokenCommand : CliktCommand(name="reclaim-pledged-asset", help //val networkConfig: JSONObject = getRemoteNetworkConfig(assetPledgeState.localNetworkId) //val exportRelayAddress: String = networkConfig.getString("relayEndpoint") - val claimStatusLinearId: String = requestStateFromRemoteNetwork(exportRelayAddress!!, externalStateAddress, rpc.proxy, config) + val claimStatusLinearId: String = requestStateFromRemoteNetwork(exportRelayAddress!!, externalStateAddress, rpc.proxy, config, listOf(issuer)) var obs = listOf() if (observer != null) { @@ -264,7 +264,7 @@ class ClaimRemoteHouseTokenCommand : CliktCommand(name="claim-remote-asset", hel password = "test", rpcPort = config["CORDA_PORT"]!!.toInt()) try { - val issuer: Party = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse("O=PartyA,L=London,C=GB"))!! + val issuer: Party = rpc.proxy.wellKnownPartyFromX500Name(CordaX500Name.parse(ISSUER_DN))!! val issuedTokenType = rpc.proxy.startFlow(::GetIssuedTokenType, "house").returnValue.get() println("TokenType: $issuedTokenType") val recipientCert: String = fetchCertBase64Helper(rpc.proxy) @@ -292,7 +292,7 @@ class ClaimRemoteHouseTokenCommand : CliktCommand(name="claim-remote-asset", hel // below from the remote-network-config.json file //val networkConfig: JSONObject = getRemoteNetworkConfig(importNetworkId) //val importRelayAddress: String = networkConfig.getString("relayEndpoint") - val pledgeStatusLinearId: String = requestStateFromRemoteNetwork(importRelayAddress!!, externalStateAddress, rpc.proxy, config) + val pledgeStatusLinearId: String = requestStateFromRemoteNetwork(importRelayAddress!!, externalStateAddress, rpc.proxy, config, listOf(issuer)) val res = AssetTransferSDK.claimPledgedFungibleAsset( rpc.proxy, diff --git a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/InteropManager.kt b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/InteropManager.kt index a52d7a4c47..3af05e9540 100644 --- a/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/InteropManager.kt +++ b/weaver/samples/corda/corda-simple-application/clients/src/main/kotlin/com/cordaSimpleApplication/client/InteropManager.kt @@ -17,6 +17,7 @@ import java.lang.Exception import kotlinx.coroutines.* import net.corda.core.messaging.startFlow import java.util.* +import net.corda.core.identity.Party import com.cordaSimpleApplication.flow.CreateState import com.cordaSimpleApplication.state.SimpleState @@ -54,6 +55,7 @@ class RequestStateCommand : CliktCommand(help = "Requests state from a foreign n localRelayAddress, externalStateAddress, networkName, + listOf(), config["RELAY_TLS"]!!.toBoolean(), config["RELAY_TLSCA_TRUST_STORE"]!!, config["RELAY_TLSCA_TRUST_STORE_PASSWORD"]!!, diff --git a/weaver/sdks/corda/src/main/kotlin/org/hyperledger/cacti/weaver/sdk/corda/InteroperableHelper.kt b/weaver/sdks/corda/src/main/kotlin/org/hyperledger/cacti/weaver/sdk/corda/InteroperableHelper.kt index d5e091e94d..b5f7b0b36c 100644 --- a/weaver/sdks/corda/src/main/kotlin/org/hyperledger/cacti/weaver/sdk/corda/InteroperableHelper.kt +++ b/weaver/sdks/corda/src/main/kotlin/org/hyperledger/cacti/weaver/sdk/corda/InteroperableHelper.kt @@ -27,6 +27,7 @@ import java.util.* import org.slf4j.LoggerFactory import net.corda.core.messaging.startFlow import net.corda.core.messaging.CordaRPCOps +import net.corda.core.identity.Party import org.hyperledger.cacti.weaver.imodule.corda.flows.CreateExternalRequest import org.hyperledger.cacti.weaver.imodule.corda.flows.WriteExternalStateInitiator @@ -148,6 +149,7 @@ class InteroperableHelper { localRelayEndpoint: String, externalStateAddress: String, networkName: String, + externalStateParticipants: List = listOf() , useTlsForRelay: Boolean = false, relayTlsTrustStorePath: String = "", relayTlsTrustStorePassword: String = "", @@ -176,17 +178,17 @@ class InteroperableHelper { Left(it) }, { networkQuery -> logger.debug("Network query: $networkQuery") - var eitherErrorResult: Either = Left(Error("Unknown Error")) - runBlocking { + var eitherErrorResult = runBlocking { val ack = async { client.requestState(networkQuery) }.await() pollForState(ack.requestId, client).fold({ logger.error("Error in Interop Flow: ${it.message}\n") - eitherErrorResult = Left(Error("Error in Interop Flow: ${it.message}\n")) + Left(Error("Error in Interop Flow: ${it.message}\n")) }, { state -> - eitherErrorResult = writeExternalStateToVault( + writeExternalStateToVault( proxy, state, - externalStateAddress) + externalStateAddress, + externalStateParticipants) }) } eitherErrorResult @@ -373,13 +375,14 @@ class InteroperableHelper { fun writeExternalStateToVault( proxy: CordaRPCOps, requestState: State.RequestState, - address: String + address: String, + externalStateParticipants: List = listOf() ): Either { return try { logger.debug("Sending response to Corda for view verification.\n") val stateId = runCatching { val viewBase64String = Base64.getEncoder().encodeToString(requestState.view.toByteArray()) - proxy.startFlow(::WriteExternalStateInitiator, viewBase64String, address) + proxy.startFlow(::WriteExternalStateInitiator, viewBase64String, address, externalStateParticipants) .returnValue.get() }.fold({ it.map { linearId ->