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.GameProposalCreateReq;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateRes; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateRes;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalListRes; 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.Empty;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardListRes; 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.GameProposalActionAcceptRes;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionRes; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionRes;
@ -66,10 +68,10 @@ public class CordaClient {
/** /**
* Obtain list of unconsumed (active) GameProposals * Obtain list of unconsumed (active) GameProposals
* @param holdingIdentity * @param myHoldingIdentity
* @return GameProposals list in JSON form * @return GameProposals list in JSON form
*/ */
public List<GameProposal> gameProposalList(HoldingIdentity holdingIdentity) { public List<GameProposal> gameProposalList(HoldingIdentity myHoldingIdentity) {
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
"gp.list-" + UUID.randomUUID(), "gp.list-" + UUID.randomUUID(),
@ -78,7 +80,7 @@ public class CordaClient {
); );
final GameProposalListRes listFlowResult = cordaFlowExecute( final GameProposalListRes listFlowResult = cordaFlowExecute(
holdingIdentity, myHoldingIdentity,
requestBody, requestBody,
GameProposalListRes.class GameProposalListRes.class
); );
@ -130,7 +132,7 @@ public class CordaClient {
"djmil.cordacheckers.gameproposal.ActionFlow", "djmil.cordacheckers.gameproposal.ActionFlow",
new GameProposalActionReq( new GameProposalActionReq(
gameProposalUuid.toString(), gameProposalUuid.toString(),
Action.REJECT Command.REJECT
) )
); );
@ -157,7 +159,7 @@ public class CordaClient {
"djmil.cordacheckers.gameproposal.ActionFlow", "djmil.cordacheckers.gameproposal.ActionFlow",
new GameProposalActionReq( new GameProposalActionReq(
gameProposalUuid.toString(), gameProposalUuid.toString(),
Action.ACCEPT Command.ACCEPT
) )
); );
@ -197,6 +199,34 @@ public class CordaClient {
return listFlowResult.successStatus(); 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) { private <T> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowResultType) {
try { try {
@ -283,4 +313,5 @@ public class CordaClient {
throw new RuntimeException ("CordaClient.cordaFlowPoll: retry limit"); 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; package djmil.cordacheckers.cordaclient.dao.flow.arguments;
public record GameProposalActionReq(String gameProposalUuid, Action action) { public record GameProposalActionReq(String gameProposalUuid, Command command) {
public enum Action { public enum Command {
ACCEPT, ACCEPT,
REJECT, REJECT,
CANCEL 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.Command;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; 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 { public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
private final static Logger log = LoggerFactory.getLogger(GameBoardContract.class); 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 @Override
public void verify(UtxoLedgerTransaction trx) { public void verify(UtxoLedgerTransaction trx) {
log.info("GameBoardContract.verify()"); log.info("GameBoardContract.verify()");
requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); final List<Command> commandList = trx.getCommands();
Command command = trx.getCommands().get(0); 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 @Suspendable
@ -36,20 +48,17 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
requireThat(commandList.size() == 1, REQUIRE_SINGLE_COMMAND); requireThat(commandList.size() == 1, REQUIRE_SINGLE_COMMAND);
final Command command = commandList.get(0); final Command command = commandList.get(0);
if (command instanceof GameProposalCommand && (GameProposalCommand)command == GameProposalCommand.ACCEPT) {
if (command instanceof Action && (Action)command == Action.ACCEPT) {
return trx.getInputStates(GameProposalState.class) return trx.getInputStates(GameProposalState.class)
.stream() .stream()
.reduce( (a, b) -> {throw new IllegalStateException(SINGLE_STATE_EXPECTED);} ) .reduce( (a, b) -> {throw new IllegalStateException(SINGLE_STATE_EXPECTED);} )
.get(); .get();
} else } else
if (command instanceof GameBoardContract.Move) if (command instanceof GameBoardCommand) {
{ return trx.getReferenceStates(GameProposalState.class)
List<GameProposalState> refStates = trx.getReferenceStates(GameProposalState.class); .stream()
if (refStates.size() == 1) { .reduce( (a, b) -> {throw new IllegalStateException(SINGLE_STATE_EXPECTED);} )
return (GameProposalState) refStates.get(0); .get();
}
} }
throw new IllegalStateException(NO_REFERANCE_GAMEPROPOSAL_STATE_FOR_TRXID +trx.getId()); 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.GameBoardState;
import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.GameProposalState;
import net.corda.v5.base.exceptions.CordaRuntimeException; 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; import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
private final static Logger log = LoggerFactory.getLogger(GameProposalContract.class); 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 @Override
public void verify(UtxoLedgerTransaction trx) { public void verify(UtxoLedgerTransaction trx) {
log.info("GameProposalContract.verify() called"); log.info("GameProposalContract.verify() called");
requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND);
switch ((trx.getCommands(Action.class).get(0))) { switch ((trx.getCommands(GameProposalCommand.class).get(0))) {
case CREATE: { case CREATE: {
requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_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 org.slf4j.LoggerFactory;
import djmil.cordacheckers.FlowResult; import djmil.cordacheckers.FlowResult;
import djmil.cordacheckers.contracts.GameProposalCommand;
import djmil.cordacheckers.states.GameBoardState; import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.GameProposalState;
import net.corda.v5.application.flows.ClientRequestBody; 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.Duration;
import java.time.Instant; import java.time.Instant;
import static djmil.cordacheckers.contracts.GameProposalContract.Action;
public class ActionFlow implements ClientStartableFlow { public class ActionFlow implements ClientStartableFlow {
private final static Logger log = LoggerFactory.getLogger(CreateFlow.class); private final static Logger log = LoggerFactory.getLogger(ActionFlow.class);
@CordaInject @CordaInject
public JsonMarshallingService jsonMarshallingService; public JsonMarshallingService jsonMarshallingService;
@ -43,23 +42,25 @@ public class ActionFlow implements ClientStartableFlow {
public String call(ClientRequestBody requestBody) { public String call(ClientRequestBody requestBody) {
try { try {
final ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class); 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 StateAndRef<GameProposalState> utxoGameProposal = findUnconsumedGameProposalState(args.getGameProposalUuid());
final UtxoSignedTransaction trx = prepareSignedTransaction(action, utxoGameProposal); final UtxoSignedTransaction trx = prepareSignedTransaction(command, utxoGameProposal);
final SecureHash trxId = this.flowEngine 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(); GameBoardState newGb = (GameBoardState)trx.getOutputStateAndRefs().get(0).getState().getContractState();
return new FlowResult(newGb.getId(), trxId) return new FlowResult(newGb.getId(), trxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
return new FlowResult(action+"ED", trxId) // REJECT+ED return new FlowResult(command+"ED", trxId) // REJECT+ED
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
} }
catch (Exception e) { catch (Exception e) {
@ -86,17 +87,17 @@ public class ActionFlow implements ClientStartableFlow {
@Suspendable @Suspendable
private UtxoSignedTransaction prepareSignedTransaction( private UtxoSignedTransaction prepareSignedTransaction(
Action action, GameProposalCommand command,
StateAndRef<GameProposalState> utxoGameProposal StateAndRef<GameProposalState> utxoGameProposal
) { ) {
UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder() UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder()
.setNotary(utxoGameProposal.getState().getNotaryName()) .setNotary(utxoGameProposal.getState().getNotaryName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addInputState(utxoGameProposal.getRef()) .addInputState(utxoGameProposal.getRef())
.addCommand(action) .addCommand(command)
.addSignatories(utxoGameProposal.getState().getContractState().getParticipants()); .addSignatories(utxoGameProposal.getState().getContractState().getParticipants());
if (action == Action.ACCEPT) { if (command == GameProposalCommand.ACCEPT) {
trxBuilder = trxBuilder trxBuilder = trxBuilder
.addOutputState(new GameBoardState(utxoGameProposal)); .addOutputState(new GameBoardState(utxoGameProposal));
//A state cannot be both an input and a reference input in the same transaction //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 java.util.UUID;
import djmil.cordacheckers.contracts.GameProposalContract; import djmil.cordacheckers.contracts.GameProposalCommand;
public class ActionFlowArgs { public class ActionFlowArgs {
private UUID gameProposalUuid; private UUID gameProposalUuid;
private String action; private String command;
// Serialisation service requires a default constructor // Serialisation service requires a default constructor
public ActionFlowArgs() { public ActionFlowArgs() {
this.gameProposalUuid = null; this.gameProposalUuid = null;
this.action = null; this.command = null;
} }
public GameProposalContract.Action getAction() { public GameProposalCommand getCommand() {
return GameProposalContract.Action.valueOf(this.action); return GameProposalCommand.valueOf(this.command);
} }
public UUID getGameProposalUuid() { public UUID getGameProposalUuid() {

View File

@ -3,6 +3,7 @@ package djmil.cordacheckers.gameproposal;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import djmil.cordacheckers.contracts.GameProposalCommand;
import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.GameProposalState;
import net.corda.v5.application.flows.CordaInject; import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.flows.InitiatedBy; 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.UtxoSignedTransaction;
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator;
import static djmil.cordacheckers.contracts.GameProposalContract.Action;
@InitiatedBy(protocol = "game-proposal") @InitiatedBy(protocol = "game-proposal")
public class CommitResponderFlow implements ResponderFlow { public class CommitResponderFlow implements ResponderFlow {
@ -35,11 +34,11 @@ public class CommitResponderFlow implements ResponderFlow {
public void call(FlowSession session) { public void call(FlowSession session) {
try { try {
UtxoTransactionValidator txValidator = ledgerTransaction -> { 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); final GameProposalState gameProposal = getGameProposal(ledgerTransaction);
checkParticipants(session, gameProposal, action); checkParticipants(session, gameProposal, command);
/* /*
* Other checks / actions ? * Other checks / actions ?
@ -63,23 +62,23 @@ public class CommitResponderFlow implements ResponderFlow {
void checkParticipants( void checkParticipants(
FlowSession session, FlowSession session,
GameProposalState gameProposal, GameProposalState gameProposal,
Action action GameProposalCommand command
) { ) {
final MemberX500Name myName = memberLookup.myInfo().getName(); final MemberX500Name myName = memberLookup.myInfo().getName();
final MemberX500Name otherName = session.getCounterparty(); 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() +"'"); 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()+"'"); throw new CordaRuntimeException("Bad GameProposal issuer: expected '"+otherName+"', actual '"+gameProposal.getIssuer()+"'");
} }
@Suspendable @Suspendable
GameProposalState getGameProposal(UtxoLedgerTransaction trx) { 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: case CREATE:
return (GameProposalState)trx.getOutputContractStates().get(0); return (GameProposalState)trx.getOutputContractStates().get(0);
@ -89,7 +88,7 @@ public class CommitResponderFlow implements ResponderFlow {
return (GameProposalState)trx.getInputContractStates().get(0); return (GameProposalState)trx.getInputContractStates().get(0);
default: 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 org.slf4j.LoggerFactory;
import djmil.cordacheckers.FlowResult; import djmil.cordacheckers.FlowResult;
import djmil.cordacheckers.contracts.GameProposalCommand;
import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.Piece; import djmil.cordacheckers.states.Piece;
import net.corda.v5.application.flows.ClientRequestBody; import net.corda.v5.application.flows.ClientRequestBody;
@ -22,7 +23,6 @@ import net.corda.v5.membership.MemberInfo;
import net.corda.v5.membership.NotaryInfo; import net.corda.v5.membership.NotaryInfo;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static djmil.cordacheckers.contracts.GameProposalContract.Action;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@ -53,14 +53,14 @@ public class CreateFlow implements ClientStartableFlow{
public String call(ClientRequestBody requestBody) { public String call(ClientRequestBody requestBody) {
try { try {
log.info("flow: Create Game Proposal"); log.info("flow: Create Game Proposal");
final Action actino = Action.CREATE; final GameProposalCommand command = GameProposalCommand.CREATE;
final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody); final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody);
final UtxoSignedTransaction trx = prepareSignedTransaction(newGameProposal); final UtxoSignedTransaction trx = prepareSignedTransaction(newGameProposal);
final SecureHash trxId = this.flowEngine 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) return new FlowResult(newGameProposal.getId(), trxId)
.toJsonEncodedString(jsonMarshallingService); .toJsonEncodedString(jsonMarshallingService);
@ -107,7 +107,7 @@ public class CreateFlow implements ClientStartableFlow{
.setNotary(notary.getName()) .setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(outputGameProposalState) .addOutputState(outputGameProposalState)
.addCommand(Action.CREATE) .addCommand(GameProposalCommand.CREATE)
.addSignatories(outputGameProposalState.getParticipants()) .addSignatories(outputGameProposalState.getParticipants())
.toSignedTransaction(); .toSignedTransaction();
} }