From e235ecb942a7e53cc1f32648974c0c76a2a48287 Mon Sep 17 00:00:00 2001 From: djmil Date: Thu, 7 Sep 2023 21:33:37 +0200 Subject: [PATCH] Corda: CommitSubFlow --- .../flow/arguments/GameProposalActionRes.java | 2 +- .../flow/arguments/GameProposalCreateRes.java | 4 +- .../flow/arguments/GameProposalListRes.java | 3 + .../contracts/GameProposalContract.java | 114 +++++++++++------- .../java/djmil/cordacheckers/FlowResult.java | 10 ++ .../gameproposal/ActionFlow.java | 53 ++++---- ...esponder.java => CommitResponderFlow.java} | 50 ++++++-- .../gameproposal/CommitSubFlow.java | 60 +++++++++ .../gameproposal/CreateFlow.java | 46 +++---- .../gameproposal/CreateResponder.java | 72 ----------- 10 files changed, 225 insertions(+), 189 deletions(-) rename corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/{ActionResponder.java => CommitResponderFlow.java} (55%) create mode 100644 corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitSubFlow.java delete mode 100644 corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateResponder.java diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionRes.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionRes.java index 6b1df3b..e290878 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionRes.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionRes.java @@ -1,5 +1,5 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; -public record GameProposalActionRes(String successStatus, String failureStatus) { +public record GameProposalActionRes(String successStatus, String transactionId, String failureStatus) { } diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateRes.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateRes.java index a0775b2..4246dc6 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateRes.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateRes.java @@ -2,9 +2,9 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; import java.util.UUID; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -@JsonSerialize +@JsonIgnoreProperties(ignoreUnknown = true) public record GameProposalCreateRes(UUID successStatus, String failureStatus) { } diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalListRes.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalListRes.java index c8956f0..b7e134e 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalListRes.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalListRes.java @@ -2,8 +2,11 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + import djmil.cordacheckers.cordaclient.dao.GameProposal; +@JsonIgnoreProperties(ignoreUnknown = true) public record GameProposalListRes(List successStatus, String failureStatus) { } diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java index c94b456..9d70f7d 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java @@ -3,66 +3,96 @@ package djmil.cordacheckers.contracts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import djmil.cordacheckers.states.GameProposalResolutionState; +//import djmil.cordacheckers.states.GameProposalResolutionState; import djmil.cordacheckers.states.GameProposalState; import net.corda.v5.base.exceptions.CordaRuntimeException; +import net.corda.v5.base.types.MemberX500Name; import net.corda.v5.ledger.utxo.Command; +import net.corda.v5.ledger.utxo.StateAndRef; import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { private final static Logger log = LoggerFactory.getLogger(GameProposalContract.class); - public static class Create implements Command { } - public static class Accept implements Command { } - public static class Reject implements Command { } - public static class Cancel implements Command { } + public static enum Action implements Command { + CREATE, + ACCEPT, + REJECT, + CANCEL; + + public MemberX500Name getInitiator(GameProposalState gameProposalState) { + switch (this) { + case CREATE: + case CANCEL: + return gameProposalState.getIssuer(); + case ACCEPT: + case REJECT: + return gameProposalState.getAcquier(); + default: + throw new RuntimeException(UNSUPPORTED_ACTION_VALUE_OF + this.name()); + } + } + + public MemberX500Name getRespondent(GameProposalState gameProposalState) { + switch (this) { + case CREATE: + case CANCEL: + return gameProposalState.getAcquier(); + case ACCEPT: + case REJECT: + return gameProposalState.getIssuer(); + default: + throw new RuntimeException(UNSUPPORTED_ACTION_VALUE_OF + this.name()); + } + } + + public MemberX500Name getSender(StateAndRef utxoGameProposal) { + return getInitiator(utxoGameProposal.getState().getContractState()); + } + + public MemberX500Name getReceiver(StateAndRef utxoGameProposal) { + return getRespondent(utxoGameProposal.getState().getContractState()); + } + + public static final String UNSUPPORTED_ACTION_VALUE_OF = "Unsupported Action value: "; + } @Override public void verify(UtxoLedgerTransaction trx) { log.info("GameProposalContract.verify() called"); requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); - Command command = trx.getCommands().get(0); - if (command instanceof Create) { - requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); - requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE); - - GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0); - requireThat(outputState != null, CREATE_OUTPUT_STATE); + switch ((Action)(trx.getCommands().get(0))) { + case CREATE: { + requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); + requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE); - requireThat(outputState.getRecipientColor() != null, NON_NULL_RECIPIENT_COLOR); - } else - if (command instanceof Accept) { - // TODO outputState -> Game - throw new CordaRuntimeException("Unimplemented!"); - } else - if (command instanceof Reject) { - requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE); - requireThat(trx.getOutputContractStates().size() == 1, REJECT_OUTPUT_STATE); + GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0); + requireThat(outputState != null, CREATE_OUTPUT_STATE); - GameProposalState inputState = trx.getInputStates(GameProposalState.class).get(0); - requireThat(inputState != null, REJECT_INPUT_STATE); - - GameProposalResolutionState outputState = trx.getOutputStates(GameProposalResolutionState.class).get(0); - requireThat(outputState != null, REJECT_OUTPUT_STATE); + requireThat(outputState.getRecipientColor() != null, NON_NULL_RECIPIENT_COLOR); + break; } - requireThat(outputState.outcome == GameProposalResolutionState.Resolution.REJECT, REJECT_OUTPUT_OUTCOME); - } else - if (command instanceof Cancel) { - requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE); - requireThat(trx.getOutputContractStates().size() == 1, CANCEL_OUTPUT_STATE); + case ACCEPT: + throw new CordaRuntimeException("Unimplemented!"); + //break; - GameProposalState inputState = trx.getInputStates(GameProposalState.class).get(0); - requireThat(inputState != null, CANCEL_INPUT_STATE); - - GameProposalResolutionState outputState = trx.getOutputStates(GameProposalResolutionState.class).get(0); - requireThat(outputState != null, CANCEL_OUTPUT_STATE); + case REJECT: + requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE); + requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE); + requireThat(trx.getInputStates(GameProposalState.class).get(0) != null, REJECT_INPUT_STATE); + break; - requireThat(outputState.outcome == GameProposalResolutionState.Resolution.CANCEL, CANCEL_OUTPUT_OUTCOME); - } else { - throw new CordaRuntimeException(UNKNOWN_COMMAND); + case CANCEL: + requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE); + requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE); + requireThat(trx.getInputStates(GameProposalState.class).get(0) != null, CANCEL_INPUT_STATE); + break; + + default: + throw new CordaRuntimeException(UNKNOWN_COMMAND); } } @@ -80,10 +110,8 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { static final String NON_NULL_RECIPIENT_COLOR = "GameProposal.recipientColor field can not be null"; static final String REJECT_INPUT_STATE = "Reject command should have exactly one GameProposal input state"; - static final String REJECT_OUTPUT_STATE = "Reject command should have exactly one GameProposalResolution output states"; - static final String REJECT_OUTPUT_OUTCOME = "Reject output state should have Resolution value set to REJECT"; + static final String REJECT_OUTPUT_STATE = "Reject command should have no output states"; static final String CANCEL_INPUT_STATE = "Cancel command should have exactly one GameProposal input state"; - static final String CANCEL_OUTPUT_STATE = "Cancel command should have exactly one GameProposalResolution output states"; - static final String CANCEL_OUTPUT_OUTCOME = "Cancel output state should have Resolution value set to CANCEL"; + static final String CANCEL_OUTPUT_STATE = "Cancel command should have no output states"; } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/FlowResult.java b/corda/workflows/src/main/java/djmil/cordacheckers/FlowResult.java index 5c995dc..47015f9 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/FlowResult.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/FlowResult.java @@ -1,18 +1,28 @@ package djmil.cordacheckers; import net.corda.v5.application.marshalling.JsonMarshallingService; +import net.corda.v5.crypto.SecureHash; public class FlowResult { public final Object successStatus; + public final String transactionId; public final String failureStatus; public FlowResult(Object success) { this.successStatus = success; + this.transactionId = null; + this.failureStatus = null; + } + + public FlowResult(Object success, SecureHash transactionId) { + this.successStatus = success; + this.transactionId = transactionId.toString(); this.failureStatus = null; } public FlowResult(Exception exception) { this.successStatus = null; + this.transactionId = null; this.failureStatus = exception.getMessage(); } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlow.java index 93f6290..05116fc 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlow.java @@ -1,6 +1,5 @@ package djmil.cordacheckers.gameproposal; -import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -9,18 +8,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import djmil.cordacheckers.FlowResult; -import djmil.cordacheckers.contracts.GameProposalContract; import djmil.cordacheckers.states.GameProposalState; import net.corda.v5.application.flows.ClientRequestBody; import net.corda.v5.application.flows.ClientStartableFlow; import net.corda.v5.application.flows.CordaInject; -import net.corda.v5.application.flows.InitiatingFlow; +import net.corda.v5.application.flows.FlowEngine; import net.corda.v5.application.marshalling.JsonMarshallingService; -import net.corda.v5.application.membership.MemberLookup; -import net.corda.v5.application.messaging.FlowMessaging; -import net.corda.v5.application.messaging.FlowSession; import net.corda.v5.base.annotations.Suspendable; import net.corda.v5.base.exceptions.CordaRuntimeException; +import net.corda.v5.crypto.SecureHash; import net.corda.v5.ledger.utxo.StateAndRef; import net.corda.v5.ledger.utxo.UtxoLedgerService; import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; @@ -29,7 +25,8 @@ import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder; import java.time.Duration; import java.time.Instant; -@InitiatingFlow(protocol = "game-proposal-action") +import static djmil.cordacheckers.contracts.GameProposalContract.Action; + public class ActionFlow implements ClientStartableFlow { private final static Logger log = LoggerFactory.getLogger(CreateFlow.class); @@ -41,22 +38,24 @@ public class ActionFlow implements ClientStartableFlow { public UtxoLedgerService ledgerService; @CordaInject - public FlowMessaging flowMessaging; - - @CordaInject - public MemberLookup memberLookup; + public FlowEngine flowEngine; @Override @Suspendable public String call(ClientRequestBody requestBody) { try { - ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class); + final ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class); + final Action action = args.getAction(); - StateAndRef inputState = findUnconsumedGameProposalState(args.getGameProposalUuid()); + final StateAndRef inputState = findUnconsumedGameProposalState(args.getGameProposalUuid()); - String trxResult = doTrunsaction(args.getAction(), inputState /*, outputState*/); + final UtxoSignedTransaction trx = prepareSignedTransaction(action, inputState); - return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService); + final SecureHash trxId = this.flowEngine + .subFlow( new CommitSubFlow(trx, action.getReceiver(inputState)) ); + + return new FlowResult(action+"ED", trxId) // REJECT+ED + .toJsonEncodedString(jsonMarshallingService); } catch (Exception e) { log.warn("GameProposalAction flow failed to process utxo request body " + requestBody + @@ -87,30 +86,24 @@ public class ActionFlow implements ClientStartableFlow { } @Suspendable - private String doTrunsaction( - GameProposalContract.Action action, + private UtxoSignedTransaction prepareSignedTransaction( + Action action, StateAndRef inputState - /* GameProposalResolutionState outputState*/ ) { - UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder() + UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder() .setNotary(inputState.getState().getNotaryName()) .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .addInputState(inputState.getRef()) - //.addOutputState(outputState) .addCommand(action) .addSignatories(inputState.getState().getContractState().getParticipants()); - UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(); + if (action == Action.ACCEPT) { + // TODO + // .addOutputState(outputState) + throw new RuntimeException(action +" unimplemented"); + } - FlowSession session = flowMessaging.initiateFlow( - action.getReceiver(inputState) - ); - - List sessionsList = Arrays.asList(session); - - ledgerService.finalize(signedTransaction, sessionsList); - - return action+"ED"; // REJECT+ED + return trxBuilder.toSignedTransaction(); } } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionResponder.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java similarity index 55% rename from corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionResponder.java rename to corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java index 6047d79..ee41ec6 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionResponder.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java @@ -3,7 +3,6 @@ package djmil.cordacheckers.gameproposal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import djmil.cordacheckers.contracts.GameProposalContract; import djmil.cordacheckers.states.GameProposalState; import net.corda.v5.application.flows.CordaInject; import net.corda.v5.application.flows.InitiatedBy; @@ -14,12 +13,15 @@ import net.corda.v5.base.annotations.Suspendable; import net.corda.v5.base.exceptions.CordaRuntimeException; import net.corda.v5.base.types.MemberX500Name; import net.corda.v5.ledger.utxo.UtxoLedgerService; +import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; -@InitiatedBy(protocol = "game-proposal-action") -public class ActionResponder implements ResponderFlow { - private final static Logger log = LoggerFactory.getLogger(CreateResponder.class); +import static djmil.cordacheckers.contracts.GameProposalContract.Action; + +@InitiatedBy(protocol = "game-proposal") +public class CommitResponderFlow implements ResponderFlow { + private final static Logger log = LoggerFactory.getLogger(CommitResponderFlow.class); @CordaInject public MemberLookup memberLookup; @@ -32,12 +34,16 @@ public class ActionResponder implements ResponderFlow { public void call(FlowSession session) { try { UtxoTransactionValidator txValidator = ledgerTransaction -> { - GameProposalState gameProposal = (GameProposalState) ledgerTransaction.getInputContractStates().get(0); + final Action action = ledgerTransaction.getCommands(Action.class).get(0); - GameProposalContract.Action action = ledgerTransaction.getCommands(GameProposalContract.Action.class).get(0); + final GameProposalState gameProposal = getGameProposal(ledgerTransaction); checkSessionParticipants(session, gameProposal, action); + /* + * Other checks / actions ? + */ + log.info("Verified the transaction - " + ledgerTransaction.getId()); }; @@ -47,9 +53,8 @@ public class ActionResponder implements ResponderFlow { log.info("Finished responder flow - " + finalizedSignedTransaction.getId()); } - catch(Exception e) - { - log.warn("Exceptionally finished responder flow", e); + catch(Exception e) { + log.warn("Responder flow failed: ", e); } } @@ -57,15 +62,34 @@ public class ActionResponder implements ResponderFlow { void checkSessionParticipants( FlowSession session, GameProposalState gameProposal, - GameProposalContract.Action action + Action action ) { final MemberX500Name myName = memberLookup.myInfo().getName(); final MemberX500Name otherName = session.getCounterparty(); - if (action.getReceiver(gameProposal).compareTo(myName) != 0) - throw new CordaRuntimeException("Bad GameProposal aquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'"); + if (action.getRespondent(gameProposal).compareTo(myName) != 0) + throw new CordaRuntimeException("Bad GameProposal acquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'"); - if (action.getSender(gameProposal).compareTo(otherName) != 0) + if (action.getInitiator(gameProposal).compareTo(otherName) != 0) throw new CordaRuntimeException("Bad GameProposal issuer: expected '"+otherName+"', actual '"+gameProposal.getIssuer()+"'"); } + + @Suspendable + GameProposalState getGameProposal(UtxoLedgerTransaction trx) { + final Action action = trx.getCommands(Action.class).get(0); + + switch (action) { + case CREATE: + return (GameProposalState)trx.getOutputContractStates().get(0); + + case ACCEPT: + case REJECT: + case CANCEL: + return (GameProposalState)trx.getInputContractStates().get(0); + + default: + throw new RuntimeException(Action.UNSUPPORTED_ACTION_VALUE_OF +action); + } + + } } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitSubFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitSubFlow.java new file mode 100644 index 0000000..4de9c1f --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitSubFlow.java @@ -0,0 +1,60 @@ +package djmil.cordacheckers.gameproposal; + +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.corda.v5.application.flows.CordaInject; +import net.corda.v5.application.flows.InitiatingFlow; +import net.corda.v5.application.flows.SubFlow; +import net.corda.v5.application.messaging.FlowMessaging; +import net.corda.v5.application.messaging.FlowSession; +import net.corda.v5.base.annotations.Suspendable; +import net.corda.v5.base.types.MemberX500Name; +import net.corda.v5.crypto.SecureHash; +import net.corda.v5.ledger.utxo.UtxoLedgerService; +import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; + +@InitiatingFlow(protocol = "game-proposal") +public class CommitSubFlow implements SubFlow { + + private final static Logger log = LoggerFactory.getLogger(CommitSubFlow.class); + private final UtxoSignedTransaction signedTransaction; + private final MemberX500Name respondenName; + + public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name respondenName) { + this.signedTransaction = signedTransaction; + this.respondenName = respondenName; + } + + @CordaInject + public UtxoLedgerService ledgerService; + + @CordaInject + public FlowMessaging flowMessaging; + + @Override + @Suspendable + public SecureHash call() { + log.info("GamePropsal commit started"); + + final FlowSession session = flowMessaging.initiateFlow(this.respondenName); + + /* + * Calls the Corda provided finalise() function which gather signatures from the counterparty, + * notarises the transaction and persists the transaction to each party's vault. + */ + + final List sessionsList = Arrays.asList(session); + + final SecureHash trxId = ledgerService + .finalize(this.signedTransaction, sessionsList) + .getTransaction() + .getId(); + + log.info("GamePropsal commit " +trxId); + return trxId; + } +} diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java index b392990..9b3236f 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java @@ -4,7 +4,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import djmil.cordacheckers.FlowResult; -import djmil.cordacheckers.contracts.GameProposalContract; import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.Piece; import net.corda.v5.application.flows.ClientRequestBody; @@ -14,26 +13,23 @@ import net.corda.v5.application.flows.FlowEngine; import net.corda.v5.application.flows.InitiatingFlow; import net.corda.v5.application.marshalling.JsonMarshallingService; import net.corda.v5.application.membership.MemberLookup; -import net.corda.v5.application.messaging.FlowMessaging; -import net.corda.v5.application.messaging.FlowSession; import net.corda.v5.base.annotations.Suspendable; import net.corda.v5.base.types.MemberX500Name; +import net.corda.v5.crypto.SecureHash; import net.corda.v5.ledger.common.NotaryLookup; import net.corda.v5.ledger.utxo.UtxoLedgerService; import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; -import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder; import net.corda.v5.membership.MemberInfo; import net.corda.v5.membership.NotaryInfo; import static java.util.Objects.requireNonNull; +import static djmil.cordacheckers.contracts.GameProposalContract.Action; import java.time.Duration; import java.time.Instant; import java.util.Arrays; -import java.util.List; import java.util.UUID; -@InitiatingFlow(protocol = "game-proposal-create") public class CreateFlow implements ClientStartableFlow{ private final static Logger log = LoggerFactory.getLogger(CreateFlow.class); @@ -53,19 +49,22 @@ public class CreateFlow implements ClientStartableFlow{ @CordaInject public FlowEngine flowEngine; - @CordaInject - public FlowMessaging flowMessaging; - @Suspendable @Override public String call(ClientRequestBody requestBody) { try { log.info("flow: Create Game Proposal"); + final Action actino = Action.CREATE; - GameProposalState gameProposal = buildGameProposalStateFrom(requestBody); - String trxResult = doTrunsaction(gameProposal); + final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody); + + final UtxoSignedTransaction trx = prepareSignedTransaction(newGameProposal); - return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService); + final SecureHash trxId = this.flowEngine + .subFlow(new CommitSubFlow(trx, actino.getRespondent(newGameProposal))); + + return new FlowResult(newGameProposal.getId(), trxId) + .toJsonEncodedString(jsonMarshallingService); } catch (Exception e) { log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody + @@ -102,24 +101,15 @@ public class CreateFlow implements ClientStartableFlow{ } @Suspendable - private String doTrunsaction(GameProposalState gameProposal) { - NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next(); + private UtxoSignedTransaction prepareSignedTransaction(GameProposalState outputGameProposalState) { + final NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next(); - UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder() + return ledgerService.createTransactionBuilder() .setNotary(notary.getName()) .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) - .addOutputState(gameProposal) - .addCommand(GameProposalContract.Action.CREATE) - .addSignatories(gameProposal.getParticipants()); - - UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(); - - FlowSession session = flowMessaging.initiateFlow(gameProposal.getAcquier()); - - List sessionsList = Arrays.asList(session); - - ledgerService.finalize(signedTransaction, sessionsList); - - return gameProposal.getId().toString(); + .addOutputState(outputGameProposalState) + .addCommand(Action.CREATE) + .addSignatories(outputGameProposalState.getParticipants()) + .toSignedTransaction(); } } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateResponder.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateResponder.java deleted file mode 100644 index 4453295..0000000 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateResponder.java +++ /dev/null @@ -1,72 +0,0 @@ -package djmil.cordacheckers.gameproposal; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import djmil.cordacheckers.states.GameProposalState; -import net.corda.v5.application.flows.CordaInject; -import net.corda.v5.application.flows.InitiatedBy; -import net.corda.v5.application.flows.ResponderFlow; -import net.corda.v5.application.membership.MemberLookup; -import net.corda.v5.application.messaging.FlowSession; -import net.corda.v5.base.annotations.Suspendable; -import net.corda.v5.base.exceptions.CordaRuntimeException; -import net.corda.v5.base.types.MemberX500Name; -import net.corda.v5.ledger.utxo.UtxoLedgerService; -import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; -import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; - -@InitiatedBy(protocol = "game-proposal-create") -public class CreateResponder implements ResponderFlow { - private final static Logger log = LoggerFactory.getLogger(CreateResponder.class); - - @CordaInject - public MemberLookup memberLookup; - - @CordaInject - public UtxoLedgerService utxoLedgerService; - - @Suspendable - @Override - public void call(FlowSession session) { - try { - // Defines the lambda validator used in receiveFinality below. - UtxoTransactionValidator txValidator = ledgerTransaction -> { - GameProposalState gameProposal = (GameProposalState) ledgerTransaction.getOutputContractStates().get(0); - // Uses checkForBannedWords() and checkMessageFromMatchesCounterparty() functions - // to check whether to sign the transaction. - if (!checkParticipants(gameProposal, session.getCounterparty())) { - throw new CordaRuntimeException("Failed verification"); - } - - log.info("Verified the transaction - " + ledgerTransaction.getId()); - }; - - // Calls receiveFinality() function which provides the responder to the finalise() function - // in the Initiating Flow. Accepts a lambda validator containing the business logic to decide whether - // responder should sign the Transaction. - UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService - .receiveFinality(session, txValidator) - .getTransaction(); - - log.info("Finished responder flow - " + finalizedSignedTransaction.getId()); - } - // Soft fails the flow and log the exception. - catch(Exception e) - { - log.warn("Exceptionally finished responder flow", e); - } - } - - @Suspendable - Boolean checkParticipants(GameProposalState gameProposal, MemberX500Name counterpartyName) { - MemberX500Name myName = memberLookup.myInfo().getName(); - - if (gameProposal.getAcquier().compareTo(myName) == 0 && - gameProposal.getIssuer().compareTo(counterpartyName) == 0) - return true; - - return false; - } - -}