diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java index e199cdd..b581008 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java @@ -29,9 +29,11 @@ import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateRes; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalListRes; -import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq.Action; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq.Command; import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardListRes; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardMoveReq; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardMoveRes; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionAcceptRes; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionRes; @@ -66,10 +68,10 @@ public class CordaClient { /** * Obtain list of unconsumed (active) GameProposals - * @param holdingIdentity + * @param myHoldingIdentity * @return GameProposals list in JSON form */ - public List gameProposalList(HoldingIdentity holdingIdentity) { + public List gameProposalList(HoldingIdentity myHoldingIdentity) { final RequestBody requestBody = new RequestBody( "gp.list-" + UUID.randomUUID(), @@ -78,7 +80,7 @@ public class CordaClient { ); final GameProposalListRes listFlowResult = cordaFlowExecute( - holdingIdentity, + myHoldingIdentity, requestBody, GameProposalListRes.class ); @@ -130,7 +132,7 @@ public class CordaClient { "djmil.cordacheckers.gameproposal.ActionFlow", new GameProposalActionReq( gameProposalUuid.toString(), - Action.REJECT + Command.REJECT ) ); @@ -157,7 +159,7 @@ public class CordaClient { "djmil.cordacheckers.gameproposal.ActionFlow", new GameProposalActionReq( gameProposalUuid.toString(), - Action.ACCEPT + Command.ACCEPT ) ); @@ -197,6 +199,34 @@ public class CordaClient { return listFlowResult.successStatus(); } + public GameBoard gameBoardMove( + HoldingIdentity myHoldingIdentity, + UUID gameBoardUuid, + int from, int to + ) { + final RequestBody requestBody = new RequestBody( + "gb.move-" +UUID.randomUUID(), + "djmil.cordacheckers.gameboard.MoveFlow", + new GameBoardMoveReq( + gameBoardUuid, + from, to + ) + ); + + final GameBoardMoveRes moveResult = cordaFlowExecute( + myHoldingIdentity, + requestBody, + GameBoardMoveRes.class + ); + + if (moveResult.failureStatus() != null) { + System.out.println("GameBoard.MoveFlow failed: " + moveResult.failureStatus()); + throw new RuntimeException("GameBoard: MoveFlow execution has failed"); + } + + return moveResult.successStatus(); + } + private T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class flowResultType) { try { @@ -283,4 +313,5 @@ public class CordaClient { throw new RuntimeException ("CordaClient.cordaFlowPoll: retry limit"); } + } diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameBoardMoveReq.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameBoardMoveReq.java new file mode 100644 index 0000000..00e9ac6 --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameBoardMoveReq.java @@ -0,0 +1,7 @@ +package djmil.cordacheckers.cordaclient.dao.flow.arguments; + +import java.util.UUID; + +public record GameBoardMoveReq(UUID gameBoard, int from, int to) { + +} diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameBoardMoveRes.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameBoardMoveRes.java new file mode 100644 index 0000000..906bb9a --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameBoardMoveRes.java @@ -0,0 +1,7 @@ +package djmil.cordacheckers.cordaclient.dao.flow.arguments; + +import djmil.cordacheckers.cordaclient.dao.GameBoard; + +public record GameBoardMoveRes (GameBoard successStatus, String failureStatus) { + +} diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionReq.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionReq.java index 6dc8c0c..5434581 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionReq.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionReq.java @@ -1,7 +1,7 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; -public record GameProposalActionReq(String gameProposalUuid, Action action) { - public enum Action { +public record GameProposalActionReq(String gameProposalUuid, Command command) { + public enum Command { ACCEPT, REJECT, CANCEL diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardCommand.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardCommand.java new file mode 100644 index 0000000..8e44d79 --- /dev/null +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardCommand.java @@ -0,0 +1,45 @@ +package djmil.cordacheckers.contracts; + +import java.util.List; + +import net.corda.v5.base.exceptions.CordaRuntimeException; +import net.corda.v5.ledger.utxo.Command; + +public class GameBoardCommand implements Command { + public static enum Type { + SURRENDER, + DRAW, + VICTORY, + MOVE; + } + + private final Type type; + private final List move; // [0] from, [1] to + + public GameBoardCommand(Type type) { + if (type == Type.MOVE) + throw new CordaRuntimeException (BAD_ACTIONMOVE_CONSTRUCTOR); + + this.type = type; + this.move = null; + } + + public GameBoardCommand(List move) { + this.type = Type.MOVE; + this.move = move; + } + + public Type getType() { + return this.type; + } + + public List getMove() { + if (type != Type.MOVE) + throw new CordaRuntimeException (NO_MOVES_FOR_ACTIONTYPE +type); + + return this.move; + } + + static final String BAD_ACTIONMOVE_CONSTRUCTOR = "Bad constructor for Action.MOVE"; + static final String NO_MOVES_FOR_ACTIONTYPE = ".getMove() not possible for "; +} diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java index c3347a2..ecf393a 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java @@ -11,23 +11,35 @@ import net.corda.v5.base.exceptions.CordaRuntimeException; import net.corda.v5.ledger.utxo.Command; import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; -import static djmil.cordacheckers.contracts.GameProposalContract.Action; - public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { private final static Logger log = LoggerFactory.getLogger(GameBoardContract.class); - public static class Move implements Command { } - public static class Surrender implements Command { } - public static class ClaimVictory implements Command { } - public static class ProposeDraw implements Command { } - @Override public void verify(UtxoLedgerTransaction trx) { log.info("GameBoardContract.verify()"); - requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); - Command command = trx.getCommands().get(0); + final List commandList = trx.getCommands(); + requireThat(commandList.size() == 1, REQUIRE_SINGLE_COMMAND); + + final Command command = commandList.get(0); + + if (command instanceof GameProposalCommand) { + log.info("GameBoardContract.verify() as GameProposalCommand "+(GameProposalCommand)command); + } else + if (command instanceof GameBoardCommand) { + log.info("GameBoardContract.verify() as GameBoardCommand "+((GameBoardCommand)command).getType()); + switch (((GameBoardCommand)command).getType()) { + case SURRENDER: + break; + case DRAW: + break; + case VICTORY: + break; + case MOVE: + break; + } + } } @Suspendable @@ -36,20 +48,17 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { requireThat(commandList.size() == 1, REQUIRE_SINGLE_COMMAND); final Command command = commandList.get(0); - - if (command instanceof Action && (Action)command == Action.ACCEPT) { + if (command instanceof GameProposalCommand && (GameProposalCommand)command == GameProposalCommand.ACCEPT) { return trx.getInputStates(GameProposalState.class) .stream() .reduce( (a, b) -> {throw new IllegalStateException(SINGLE_STATE_EXPECTED);} ) .get(); - } else - if (command instanceof GameBoardContract.Move) - { - List refStates = trx.getReferenceStates(GameProposalState.class); - if (refStates.size() == 1) { - return (GameProposalState) refStates.get(0); - } + if (command instanceof GameBoardCommand) { + return trx.getReferenceStates(GameProposalState.class) + .stream() + .reduce( (a, b) -> {throw new IllegalStateException(SINGLE_STATE_EXPECTED);} ) + .get(); } throw new IllegalStateException(NO_REFERANCE_GAMEPROPOSAL_STATE_FOR_TRXID +trx.getId()); diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalCommand.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalCommand.java new file mode 100644 index 0000000..213ab12 --- /dev/null +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalCommand.java @@ -0,0 +1,49 @@ +package djmil.cordacheckers.contracts; + +import djmil.cordacheckers.states.GameProposalState; +import net.corda.v5.base.types.MemberX500Name; +import net.corda.v5.ledger.utxo.Command; +import net.corda.v5.ledger.utxo.StateAndRef; + +public enum GameProposalCommand 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_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_VALUE_OF + this.name()); + } + } + + public MemberX500Name getInitiator(StateAndRef utxoGameProposal) { + return getInitiator(utxoGameProposal.getState().getContractState()); + } + + public MemberX500Name getRespondent(StateAndRef utxoGameProposal) { + return getRespondent(utxoGameProposal.getState().getContractState()); + } + + public static final String UNSUPPORTED_VALUE_OF = "Unsupported GameProposalCommand value: "; +} 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 11c418d..d7a8c6c 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java @@ -6,65 +6,19 @@ import org.slf4j.LoggerFactory; import djmil.cordacheckers.states.GameBoardState; 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 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 getInitiator(StateAndRef utxoGameProposal) { - return getInitiator(utxoGameProposal.getState().getContractState()); - } - - public MemberX500Name getRespondent(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); - switch ((trx.getCommands(Action.class).get(0))) { + switch ((trx.getCommands(GameProposalCommand.class).get(0))) { case CREATE: { requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE); diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/CommitResponderFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/CommitResponderFlow.java new file mode 100644 index 0000000..454aa8f --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/CommitResponderFlow.java @@ -0,0 +1,97 @@ +package djmil.cordacheckers.gameboard; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import djmil.cordacheckers.contracts.GameBoardCommand; +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.UtxoLedgerTransaction; +import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; +import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; + +@InitiatedBy(protocol = "game-board") +public class CommitResponderFlow implements ResponderFlow { + + private final static Logger log = LoggerFactory.getLogger(CommitResponderFlow.class); + + @CordaInject + public MemberLookup memberLookup; + + @CordaInject + public UtxoLedgerService utxoLedgerService; + + @Suspendable + @Override + public void call(FlowSession session) { + try { + UtxoTransactionValidator txValidator = ledgerTransaction -> { + final GameBoardCommand command = ledgerTransaction.getCommands(GameBoardCommand.class).get(0); + + // TODO + //final GameProposalState gameProposal = getGameProposal(ledgerTransaction); + + //checkParticipants(session, gameProposal, command); + + /* + * Other checks / actions ? + */ + + log.info("Verified the transaction - " + ledgerTransaction.getId()); + }; + + UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService + .receiveFinality(session, txValidator) + .getTransaction(); + + log.info("Finished responder flow - " + finalizedSignedTransaction.getId()); + } + catch(Exception e) { + log.warn("Responder flow failed: ", e); + } + } + + @Suspendable + void checkParticipants( + FlowSession session, + GameProposalState gameProposal, + GameBoardCommand command + ) { + final MemberX500Name myName = memberLookup.myInfo().getName(); + final MemberX500Name otherName = session.getCounterparty(); + + // TODO + // if (command.getRespondent(gameProposal).compareTo(myName) != 0) + // throw new CordaRuntimeException("Bad GameProposal acquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'"); + + // if (command.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/gameboard/CommitSubFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/CommitSubFlow.java new file mode 100644 index 0000000..9d70fca --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/CommitSubFlow.java @@ -0,0 +1,60 @@ +package djmil.cordacheckers.gameboard; + +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-board") +public class CommitSubFlow implements SubFlow { + + private final static Logger log = LoggerFactory.getLogger(CommitSubFlow.class); + private final UtxoSignedTransaction signedTransaction; + + public CommitSubFlow(UtxoSignedTransaction signedTransaction) { + this.signedTransaction = signedTransaction; + } + + @CordaInject + public UtxoLedgerService ledgerService; + + @CordaInject + public FlowMessaging flowMessaging; + + @Override + @Suspendable + public SecureHash call() { + log.info("GameBoard commit started"); + + final MemberX500Name respondenName = null; // TODO + + final FlowSession session = flowMessaging.initiateFlow(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("GameBoard commit " +trxId); + return trxId; + } +} diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlow.java new file mode 100644 index 0000000..2de6503 --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlow.java @@ -0,0 +1,97 @@ +package djmil.cordacheckers.gameboard; + +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import djmil.cordacheckers.FlowResult; +import djmil.cordacheckers.contracts.GameBoardCommand; +import djmil.cordacheckers.states.GameBoardState; +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.FlowEngine; +import net.corda.v5.application.marshalling.JsonMarshallingService; +import net.corda.v5.base.annotations.Suspendable; +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; +import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder; + +import java.time.Duration; +import java.time.Instant; + +public class MoveFlow implements ClientStartableFlow { + + private final static Logger log = LoggerFactory.getLogger(MoveFlow.class); + + @CordaInject + public JsonMarshallingService jsonMarshallingService; + + @CordaInject + public UtxoLedgerService ledgerService; + + @CordaInject + public FlowEngine flowEngine; + + @Override + @Suspendable + public String call(ClientRequestBody requestBody) { + try { + final MoveFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, MoveFlowArgs.class); + final GameBoardCommand command = new GameBoardCommand(args.getMove()); + + final StateAndRef utxoGameBoard = findUnconsumedGameBoardState(args.getGameBoardUuid()); + + final GameBoardState newGameBoard = null; // TODO + + final UtxoSignedTransaction trx = prepareSignedTransaction(command, utxoGameBoard, newGameBoard); + + final SecureHash trxId = this.flowEngine + .subFlow( new CommitSubFlow(trx) ); + + return new FlowResult(newGameBoard, trxId) // TODO return players perspective GB + .toJsonEncodedString(jsonMarshallingService); + } + catch (Exception e) { + log.warn("GameBoardAction flow failed to process utxo request body " + requestBody + + " because: " + e.getMessage()); + return new FlowResult(e).toJsonEncodedString(jsonMarshallingService); + } + } + + @Suspendable + private StateAndRef findUnconsumedGameBoardState (UUID gameProposalUuid) { + /* + * Get list of all unconsumed aka 'active' GameBoardState, then filter by UUID. + * Note, this is an inefficient way to perform this operation if there are a large + * number of 'active' GameProposals exists in storage. + */ + return this.ledgerService + .findUnconsumedStatesByType(GameBoardState.class) + .stream() + .filter(sar -> sar.getState().getContractState().getId().equals(gameProposalUuid)) + .reduce((a, b) -> {throw new IllegalStateException("Multiple states: " +a +", " +b);}) + .get(); + } + + @Suspendable + private UtxoSignedTransaction prepareSignedTransaction( + GameBoardCommand command, + StateAndRef utxoGameProposal, + GameBoardState newGameBoard + ) { + UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder() + .setNotary(utxoGameProposal.getState().getNotaryName()) + .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) + .addInputState(utxoGameProposal.getRef()) + .addOutputState(newGameBoard) + .addCommand(command) + .addSignatories(utxoGameProposal.getState().getContractState().getParticipants()); + + return trxBuilder.toSignedTransaction(); + } + +} diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlowArgs.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlowArgs.java new file mode 100644 index 0000000..ca0a3e0 --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/MoveFlowArgs.java @@ -0,0 +1,24 @@ +package djmil.cordacheckers.gameboard; + +import java.util.List; +import java.util.UUID; + +public class MoveFlowArgs { + private UUID gameBoardUuid; + private List move; + + // Serialisation service requires a default constructor + public MoveFlowArgs() { + this.gameBoardUuid = null; + this.move = null; + } + + public UUID getGameBoardUuid() { + return this.gameBoardUuid; + } + + public List getMove() { + return this.move; + } + +} 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 a2bbd8d..5d96b1a 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlow.java @@ -6,6 +6,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import djmil.cordacheckers.FlowResult; +import djmil.cordacheckers.contracts.GameProposalCommand; import djmil.cordacheckers.states.GameBoardState; import djmil.cordacheckers.states.GameProposalState; import net.corda.v5.application.flows.ClientRequestBody; @@ -23,11 +24,9 @@ import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder; import java.time.Duration; import java.time.Instant; -import static djmil.cordacheckers.contracts.GameProposalContract.Action; - public class ActionFlow implements ClientStartableFlow { - private final static Logger log = LoggerFactory.getLogger(CreateFlow.class); + private final static Logger log = LoggerFactory.getLogger(ActionFlow.class); @CordaInject public JsonMarshallingService jsonMarshallingService; @@ -43,23 +42,25 @@ public class ActionFlow implements ClientStartableFlow { public String call(ClientRequestBody requestBody) { try { final ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class); - final Action action = args.getAction(); + final GameProposalCommand command = args.getCommand(); + + System.out.println("Game Proposal command" + command); final StateAndRef utxoGameProposal = findUnconsumedGameProposalState(args.getGameProposalUuid()); - final UtxoSignedTransaction trx = prepareSignedTransaction(action, utxoGameProposal); + final UtxoSignedTransaction trx = prepareSignedTransaction(command, utxoGameProposal); final SecureHash trxId = this.flowEngine - .subFlow( new CommitSubFlow(trx, action.getRespondent(utxoGameProposal)) ); + .subFlow( new CommitSubFlow(trx, command.getRespondent(utxoGameProposal)) ); - if (action == Action.ACCEPT) { + if (command == GameProposalCommand.ACCEPT) { GameBoardState newGb = (GameBoardState)trx.getOutputStateAndRefs().get(0).getState().getContractState(); return new FlowResult(newGb.getId(), trxId) .toJsonEncodedString(jsonMarshallingService); } - return new FlowResult(action+"ED", trxId) // REJECT+ED + return new FlowResult(command+"ED", trxId) // REJECT+ED .toJsonEncodedString(jsonMarshallingService); } catch (Exception e) { @@ -86,17 +87,17 @@ public class ActionFlow implements ClientStartableFlow { @Suspendable private UtxoSignedTransaction prepareSignedTransaction( - Action action, + GameProposalCommand command, StateAndRef utxoGameProposal ) { UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder() .setNotary(utxoGameProposal.getState().getNotaryName()) .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .addInputState(utxoGameProposal.getRef()) - .addCommand(action) + .addCommand(command) .addSignatories(utxoGameProposal.getState().getContractState().getParticipants()); - if (action == Action.ACCEPT) { + if (command == GameProposalCommand.ACCEPT) { trxBuilder = trxBuilder .addOutputState(new GameBoardState(utxoGameProposal)); //A state cannot be both an input and a reference input in the same transaction diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlowArgs.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlowArgs.java index ae9f455..1210f6f 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlowArgs.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlowArgs.java @@ -2,20 +2,20 @@ package djmil.cordacheckers.gameproposal; import java.util.UUID; -import djmil.cordacheckers.contracts.GameProposalContract; +import djmil.cordacheckers.contracts.GameProposalCommand; public class ActionFlowArgs { private UUID gameProposalUuid; - private String action; + private String command; // Serialisation service requires a default constructor public ActionFlowArgs() { this.gameProposalUuid = null; - this.action = null; + this.command = null; } - public GameProposalContract.Action getAction() { - return GameProposalContract.Action.valueOf(this.action); + public GameProposalCommand getCommand() { + return GameProposalCommand.valueOf(this.command); } public UUID getGameProposalUuid() { diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java index e1a57e8..83c68a7 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java @@ -3,6 +3,7 @@ package djmil.cordacheckers.gameproposal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import djmil.cordacheckers.contracts.GameProposalCommand; import djmil.cordacheckers.states.GameProposalState; import net.corda.v5.application.flows.CordaInject; import net.corda.v5.application.flows.InitiatedBy; @@ -17,8 +18,6 @@ import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; -import static djmil.cordacheckers.contracts.GameProposalContract.Action; - @InitiatedBy(protocol = "game-proposal") public class CommitResponderFlow implements ResponderFlow { @@ -35,11 +34,11 @@ public class CommitResponderFlow implements ResponderFlow { public void call(FlowSession session) { try { UtxoTransactionValidator txValidator = ledgerTransaction -> { - final Action action = ledgerTransaction.getCommands(Action.class).get(0); + final GameProposalCommand command = ledgerTransaction.getCommands(GameProposalCommand.class).get(0); final GameProposalState gameProposal = getGameProposal(ledgerTransaction); - checkParticipants(session, gameProposal, action); + checkParticipants(session, gameProposal, command); /* * Other checks / actions ? @@ -63,23 +62,23 @@ public class CommitResponderFlow implements ResponderFlow { void checkParticipants( FlowSession session, GameProposalState gameProposal, - Action action + GameProposalCommand command ) { final MemberX500Name myName = memberLookup.myInfo().getName(); final MemberX500Name otherName = session.getCounterparty(); - if (action.getRespondent(gameProposal).compareTo(myName) != 0) + if (command.getRespondent(gameProposal).compareTo(myName) != 0) throw new CordaRuntimeException("Bad GameProposal acquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'"); - if (action.getInitiator(gameProposal).compareTo(otherName) != 0) + if (command.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); + final GameProposalCommand command = trx.getCommands(GameProposalCommand.class).get(0); - switch (action) { + switch (command) { case CREATE: return (GameProposalState)trx.getOutputContractStates().get(0); @@ -89,7 +88,7 @@ public class CommitResponderFlow implements ResponderFlow { return (GameProposalState)trx.getInputContractStates().get(0); default: - throw new RuntimeException(Action.UNSUPPORTED_ACTION_VALUE_OF +action); + throw new RuntimeException(GameProposalCommand.UNSUPPORTED_VALUE_OF +command); } } 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 076a5d8..fb7e4cb 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import djmil.cordacheckers.FlowResult; +import djmil.cordacheckers.contracts.GameProposalCommand; import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.Piece; import net.corda.v5.application.flows.ClientRequestBody; @@ -22,7 +23,6 @@ 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; @@ -53,14 +53,14 @@ public class CreateFlow implements ClientStartableFlow{ public String call(ClientRequestBody requestBody) { try { log.info("flow: Create Game Proposal"); - final Action actino = Action.CREATE; + final GameProposalCommand command = GameProposalCommand.CREATE; final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody); final UtxoSignedTransaction trx = prepareSignedTransaction(newGameProposal); final SecureHash trxId = this.flowEngine - .subFlow(new CommitSubFlow(trx, actino.getRespondent(newGameProposal))); + .subFlow(new CommitSubFlow(trx, command.getRespondent(newGameProposal))); return new FlowResult(newGameProposal.getId(), trxId) .toJsonEncodedString(jsonMarshallingService); @@ -107,7 +107,7 @@ public class CreateFlow implements ClientStartableFlow{ .setNotary(notary.getName()) .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .addOutputState(outputGameProposalState) - .addCommand(Action.CREATE) + .addCommand(GameProposalCommand.CREATE) .addSignatories(outputGameProposalState.getParticipants()) .toSignedTransaction(); }