Move Contracts to individual classes

initial implementation for GameBoardMove
This commit is contained in:
djmil 2023-09-12 14:07:59 +02:00
parent 7f7722ecc0
commit 01fd273c3a
16 changed files with 483 additions and 103 deletions

View File

@ -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<GameProposal> gameProposalList(HoldingIdentity holdingIdentity) {
public List<GameProposal> 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> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowResultType) {
try {
@ -283,4 +313,5 @@ public class CordaClient {
throw new RuntimeException ("CordaClient.cordaFlowPoll: retry limit");
}
}

View File

@ -0,0 +1,7 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import java.util.UUID;
public record GameBoardMoveReq(UUID gameBoard, int from, int to) {
}

View File

@ -0,0 +1,7 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import djmil.cordacheckers.cordaclient.dao.GameBoard;
public record GameBoardMoveRes (GameBoard successStatus, String failureStatus) {
}

View File

@ -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

View File

@ -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<Integer> 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<Integer> move) {
this.type = Type.MOVE;
this.move = move;
}
public Type getType() {
return this.type;
}
public List<Integer> 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 ";
}

View File

@ -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<Command> 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<GameProposalState> 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());

View File

@ -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<GameProposalState> utxoGameProposal) {
return getInitiator(utxoGameProposal.getState().getContractState());
}
public MemberX500Name getRespondent(StateAndRef<GameProposalState> utxoGameProposal) {
return getRespondent(utxoGameProposal.getState().getContractState());
}
public static final String UNSUPPORTED_VALUE_OF = "Unsupported GameProposalCommand value: ";
}

View File

@ -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<GameProposalState> utxoGameProposal) {
return getInitiator(utxoGameProposal.getState().getContractState());
}
public MemberX500Name getRespondent(StateAndRef<GameProposalState> 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);

View File

@ -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);
// }
// }
}

View File

@ -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<SecureHash> {
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<FlowSession> sessionsList = Arrays.asList(session);
final SecureHash trxId = ledgerService
.finalize(this.signedTransaction, sessionsList)
.getTransaction()
.getId();
log.info("GameBoard commit " +trxId);
return trxId;
}
}

View File

@ -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<GameBoardState> 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<GameBoardState> 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<GameBoardState> 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();
}
}

View File

@ -0,0 +1,24 @@
package djmil.cordacheckers.gameboard;
import java.util.List;
import java.util.UUID;
public class MoveFlowArgs {
private UUID gameBoardUuid;
private List<Integer> move;
// Serialisation service requires a default constructor
public MoveFlowArgs() {
this.gameBoardUuid = null;
this.move = null;
}
public UUID getGameBoardUuid() {
return this.gameBoardUuid;
}
public List<Integer> getMove() {
return this.move;
}
}

View File

@ -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<GameProposalState> 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<GameProposalState> 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

View File

@ -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() {

View File

@ -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);
}
}

View File

@ -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();
}