GameBoard SURRENDER update

- UtxoLedgerTrxUtil with option
- rename action to command
- lots of minor refectorings
This commit is contained in:
djmil 2023-09-13 15:01:01 +02:00
parent d9b885b550
commit 4c2569810a
24 changed files with 427 additions and 281 deletions

View File

@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
import djmil.cordacheckers.cordaclient.dao.Piece;
import djmil.cordacheckers.cordaclient.dao.GameBoard;
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
import djmil.cordacheckers.cordaclient.dao.GameProposal;
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
@ -29,14 +30,13 @@ 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.Command;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardCommandReq;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardCommandRes;
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;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandAcceptRes;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandReq;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandRes;
@Service
public class CordaClient {
@ -130,16 +130,16 @@ public class CordaClient {
final RequestBody requestBody = new RequestBody(
"gp.reject-" +UUID.randomUUID(),
"djmil.cordacheckers.gameproposal.CommandFlow",
new GameProposalActionReq(
new GameProposalCommandReq(
gameProposalUuid.toString(),
Command.REJECT
GameProposalCommandReq.Command.REJECT
)
);
final GameProposalActionRes actionResult = cordaFlowExecute(
final GameProposalCommandRes actionResult = cordaFlowExecute(
myHoldingIdentity,
requestBody,
GameProposalActionRes.class
GameProposalCommandRes.class
);
if (actionResult.failureStatus() != null) {
@ -150,23 +150,23 @@ public class CordaClient {
return actionResult.successStatus();
}
public UUID gameProposalAccept(
public UUID gameProposalAccept( // TODO shall return newGameBoard
HoldingIdentity myHoldingIdentity,
UUID gameProposalUuid
) {
final RequestBody requestBody = new RequestBody(
"gp.accept-" +UUID.randomUUID(),
"djmil.cordacheckers.gameproposal.CommandFlow",
new GameProposalActionReq(
new GameProposalCommandReq(
gameProposalUuid.toString(),
Command.ACCEPT
GameProposalCommandReq.Command.ACCEPT
)
);
final GameProposalActionAcceptRes actionResult = cordaFlowExecute(
final GameProposalCommandAcceptRes actionResult = cordaFlowExecute(
myHoldingIdentity,
requestBody,
GameProposalActionAcceptRes.class
GameProposalCommandAcceptRes.class
);
if (actionResult.failureStatus() != null) {
@ -199,24 +199,24 @@ public class CordaClient {
return listFlowResult.successStatus();
}
public GameBoard gameBoardMove(
public GameBoard gameBoardCommand(
HoldingIdentity myHoldingIdentity,
UUID gameBoardUuid,
int from, int to
GameBoardCommand command
) {
final RequestBody requestBody = new RequestBody(
"gb.move-" +UUID.randomUUID(),
"djmil.cordacheckers.gameboard.MoveFlow",
new GameBoardMoveReq(
"gb.command-" +command.getType() +UUID.randomUUID(),
"djmil.cordacheckers.gameboard.CommandFlow",
new GameBoardCommandReq(
gameBoardUuid,
from, to
command
)
);
final GameBoardMoveRes moveResult = cordaFlowExecute(
final GameBoardCommandRes moveResult = cordaFlowExecute(
myHoldingIdentity,
requestBody,
GameBoardMoveRes.class
GameBoardCommandRes.class
);
if (moveResult.failureStatus() != null) {

View File

@ -11,6 +11,7 @@ public record GameBoard(
Piece.Color opponentColor,
Boolean opponentMove,
Map<Integer, Piece> board,
GameBoardCommand previousCommand,
String message,
UUID id) implements CordaState {

View File

@ -0,0 +1,48 @@
package djmil.cordacheckers.cordaclient.dao;
import java.util.List;
public class GameBoardCommand {
public static enum Type {
MOVE,
SURRENDER,
REQUEST_DRAW,
REQUEST_VICTORY,
FINISH; // aka accept DRAW or VICTORY request
}
private final Type type;
private final List<Integer> move; // [0] from, [1] to
public GameBoardCommand() {
this.type = null;
this.move = null;
}
public GameBoardCommand(Type type) {
this.type = type;
this.move = null;
}
public GameBoardCommand(List<Integer> move) {
this.type = Type.MOVE;
this.move = move;
}
public Type getType() {
return type;
}
public List<Integer> getMove() {
return move;
}
@Override
public String toString() {
if (type == Type.MOVE)
return move.get(0) +"->" +move.get(1);
else
return type.name();
}
}

View File

@ -0,0 +1,8 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import java.util.UUID;
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
public record GameBoardCommandReq(UUID gameBoardUuid, GameBoardCommand command) {
}

View File

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

View File

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

View File

@ -1,7 +0,0 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import java.util.UUID;
public record GameProposalActionAcceptRes(UUID successStatus, String transactionId, String failureStatus) {
}

View File

@ -1,5 +0,0 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
public record GameProposalActionRes(String successStatus, String transactionId, String failureStatus) {
}

View File

@ -0,0 +1,7 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import java.util.UUID;
public record GameProposalCommandAcceptRes(UUID successStatus, String transactionId, String failureStatus) {
}

View File

@ -1,6 +1,6 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
public record GameProposalActionReq(String gameProposalUuid, Command command) {
public record GameProposalCommandReq(String gameProposalUuid, Command command) {
public enum Command {
ACCEPT,
REJECT,

View File

@ -0,0 +1,5 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
public record GameProposalCommandRes(String successStatus, String transactionId, String failureStatus) {
}

View File

@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.JsonMappingException;
import djmil.cordacheckers.cordaclient.dao.CordaState;
import djmil.cordacheckers.cordaclient.dao.GameBoard;
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
import djmil.cordacheckers.cordaclient.dao.GameProposal;
import djmil.cordacheckers.cordaclient.dao.Piece;
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
@ -56,8 +57,7 @@ public class CordaClientTest {
holdingIdentityResolver.getByUsername(gpIssuer),
holdingIdentityResolver.getByUsername(gpAcquier),
gpAcquierColor,
gpMessage
);
gpMessage);
List<GameProposal> gpListSender = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername(gpIssuer));
@ -87,8 +87,7 @@ public class CordaClientTest {
holdingIdentityResolver.getByUsername(gpIssuer),
holdingIdentityResolver.getByUsername(gpAcquier),
gpReceiverColor,
gpMessage
);
gpMessage);
System.out.println("Create GP UUID "+ gpUuid);
@ -100,8 +99,7 @@ public class CordaClientTest {
final String rejectRes = cordaClient.gameProposalReject(
holdingIdentityResolver.getByUsername(gpAcquier),
gpUuid
);
gpUuid);
assertThat(rejectRes).isEqualToIgnoringCase("REJECTED");
@ -147,8 +145,7 @@ public class CordaClientTest {
final UUID newGameBoardId = cordaClient.gameProposalAccept(
holdingIdentityResolver.getByUsername(gpAcquier),
gpUuid
);
gpUuid);
System.out.println("New GameBoard UUID "+newGameBoardId);
@ -186,6 +183,33 @@ public class CordaClientTest {
System.out.println("GB list: " +gbList);
}
@Test
void testGameBoardSurrender() throws JsonMappingException, JsonProcessingException {
final var hiAlice = holdingIdentityResolver.getByUsername("alice");
final var hiBob = holdingIdentityResolver.getByUsername("bob");
final var bobColor = Piece.Color.WHITE;
final UUID gpUuid = cordaClient.gameProposalCreate(
hiAlice, hiBob,
bobColor, "GameBoard SURRENDER test"
);
System.out.println("New GameProposal UUID "+ gpUuid);
final UUID newGameBoardId = cordaClient.gameProposalAccept(
hiBob, gpUuid
);
System.out.println("New GameBoard UUID "+ newGameBoardId);
GameBoard gbSurrender = cordaClient.gameBoardCommand(
hiBob, newGameBoardId,
new GameBoardCommand(GameBoardCommand.Type.SURRENDER)
);
System.out.println("SURRENDER GB: "+gbSurrender);
}
private <T extends CordaState> T findByUuid(List<T> statesList, UUID uuid) {
for (T state : statesList) {
if (state.id().compareTo(uuid) == 0)

View File

@ -1,43 +0,0 @@
package djmil.cordacheckers;
import net.corda.v5.ledger.utxo.Command;
import net.corda.v5.ledger.utxo.ContractState;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
public class UtxoLedgerTransactionUtil {
private final UtxoLedgerTransaction trx;
public UtxoLedgerTransactionUtil(UtxoLedgerTransaction trx) {
this.trx = trx;
}
public <T extends Command> T getSingleCommand(Class<T> clazz) {
return trx.getCommands(clazz)
.stream()
.reduce( (a, b) -> {throw new IllegalStateException(trx.getId() +EXPECTED_SINGLE_CLAZZ +clazz);} )
.get();
}
public <T extends ContractState> T getSingleReferenceState(Class<T> clazz) {
return trx.getReferenceStates(clazz)
.stream()
.reduce( (a, b) -> {throw new IllegalStateException(trx.getId() +EXPECTED_SINGLE_CLAZZ +clazz);} )
.get();
}
public <T extends ContractState> T getSingleInputState(Class<T> clazz) {
return trx.getInputStates(clazz)
.stream()
.reduce( (a, b) -> {throw new IllegalStateException(trx.getId() +EXPECTED_SINGLE_CLAZZ +clazz);} )
.get();
}
public <T extends ContractState> T getSingleOutputState(Class<T> clazz) {
return trx.getOutputStates(clazz)
.stream()
.reduce( (a, b) -> {throw new IllegalStateException(trx.getId() +EXPECTED_SINGLE_CLAZZ +clazz);} )
.get();
}
private static String EXPECTED_SINGLE_CLAZZ = ": expected single ";
}

View File

@ -7,15 +7,22 @@ import net.corda.v5.ledger.utxo.Command;
public class GameBoardCommand implements Command {
public static enum Type {
MOVE,
SURRENDER,
DRAW,
VICTORY,
MOVE;
REQUEST_DRAW,
REQUEST_VICTORY,
FINISH; // aka accept DRAW or VICTORY request
}
private final Type type;
private final List<Integer> move; // [0] from, [1] to
// Serialisation service requires a default constructor
public GameBoardCommand() {
this.type = null;
this.move = null;
}
public GameBoardCommand(Type type) {
if (type == Type.MOVE)
throw new CordaRuntimeException (BAD_ACTIONMOVE_CONSTRUCTOR);
@ -34,12 +41,8 @@ public class GameBoardCommand implements Command {
}
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

@ -1,9 +1,13 @@
package djmil.cordacheckers.contracts;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleReferenceState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import djmil.cordacheckers.UtxoLedgerTransactionUtil;
import djmil.cordacheckers.states.GameProposalState;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.exceptions.CordaRuntimeException;
@ -18,8 +22,7 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
public void verify(UtxoLedgerTransaction trx) {
log.info("GameBoardContract.verify() called");
final UtxoLedgerTransactionUtil trxUtil = new UtxoLedgerTransactionUtil(trx);
final Command command = trxUtil.getSingleCommand(Command.class);
final Command command = getSingleCommand(trx, Command.class);
if (command instanceof GameProposalCommand) {
log.info("GameBoardContract.verify() as GameProposalCommand "+(GameProposalCommand)command);
@ -27,13 +30,15 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
if (command instanceof GameBoardCommand) {
log.info("GameBoardContract.verify() as GameBoardCommand "+((GameBoardCommand)command).getType());
switch (((GameBoardCommand)command).getType()) {
case MOVE:
break;
case SURRENDER:
break;
case DRAW:
case REQUEST_DRAW:
break;
case VICTORY:
case REQUEST_VICTORY:
break;
case MOVE:
case FINISH:
break;
}
} else {
@ -43,14 +48,13 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
@Suspendable
public static GameProposalState getReferanceGameProposalState(UtxoLedgerTransaction trx) {
final UtxoLedgerTransactionUtil trxUtil = new UtxoLedgerTransactionUtil(trx);
final Command command = trxUtil.getSingleCommand(Command.class);
final Command command = getSingleCommand(trx, Command.class);
if (command instanceof GameProposalCommand && (GameProposalCommand)command == GameProposalCommand.ACCEPT) {
return trxUtil.getSingleInputState(GameProposalState.class);
return getSingleInputState(trx, GameProposalState.class);
} else
if (command instanceof GameBoardCommand) {
return trxUtil.getSingleReferenceState(GameProposalState.class);
return getSingleReferenceState(trx, GameProposalState.class);
}
throw new IllegalStateException(NO_REFERANCE_GAMEPROPOSAL_STATE_FOR_TRXID +trx.getId());

View File

@ -1,9 +1,12 @@
package djmil.cordacheckers.contracts;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import djmil.cordacheckers.UtxoLedgerTransactionUtil;
import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.GameProposalState;
import net.corda.v5.base.exceptions.CordaRuntimeException;
@ -17,15 +20,14 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
public void verify(UtxoLedgerTransaction trx) {
log.info("GameProposalContract.verify() called");
final UtxoLedgerTransactionUtil trxUtil = new UtxoLedgerTransactionUtil(trx);
final GameProposalCommand command = trxUtil.getSingleCommand(GameProposalCommand.class);
final GameProposalCommand command = getSingleCommand(trx, GameProposalCommand.class);
switch (command) {
case CREATE: {
requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE);
GameProposalState outputState = trxUtil.getSingleOutputState(GameProposalState.class);
GameProposalState outputState = getSingleOutputState(trx, GameProposalState.class);
requireThat(outputState.getAcquierColor() != null, NON_NULL_RECIPIENT_COLOR);
break; }
@ -34,8 +36,8 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
requireThat(trx.getInputContractStates().size() == 1, ACCEPT_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, ACCEPT_OUTPUT_STATE);
GameProposalState inGameProposal = trxUtil.getSingleInputState(GameProposalState.class);
GameBoardState outGameBoard = trxUtil.getSingleOutputState(GameBoardState.class);
GameProposalState inGameProposal = getSingleInputState(trx, GameProposalState.class);
GameBoardState outGameBoard = getSingleOutputState(trx, GameBoardState.class);
requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), ACCEPT_PARTICIPANTS);
break; }
@ -44,14 +46,14 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE);
requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE);
trxUtil.getSingleInputState(GameProposalState.class);
getSingleInputState(trx, GameProposalState.class);
break; }
case CANCEL:
requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE);
requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE);
trxUtil.getSingleInputState(GameProposalState.class);
getSingleInputState(trx, GameProposalState.class);
break;
default:

View File

@ -0,0 +1,56 @@
package djmil.cordacheckers.contracts;
import java.util.List;
import java.util.Optional;
import net.corda.v5.ledger.utxo.Command;
import net.corda.v5.ledger.utxo.ContractState;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
public class UtxoLedgerTransactionUtil {
public static <T extends Command> T getSingleCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
return single(utxoTrx.getCommands(clazz), clazz);
}
public static <T extends ContractState> T getSingleReferenceState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
return single(utxoTrx.getReferenceStates(clazz), clazz);
}
public static <T extends ContractState> T getSingleInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
return single(utxoTrx.getInputStates(clazz), clazz);
}
public static <T extends ContractState> T getSingleOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
return single(utxoTrx.getOutputStates(clazz), clazz);
}
public static <T extends Command> Optional<T> getOptionalCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
return optional(utxoTrx.getCommands(clazz), clazz);
}
public static <T extends ContractState> Optional<T> getOptionalReferenceState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
return optional(utxoTrx.getReferenceStates(clazz), clazz);
}
public static <T extends ContractState> Optional<T> getOptionalInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
return optional(utxoTrx.getInputStates(clazz), clazz);
}
public static <T extends ContractState> Optional<T> getOptionalOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
return optional(utxoTrx.getOutputStates(clazz), clazz);
}
private static <T> Optional<T> optional(List<T> list, Class<T> clazz) {
return list
.stream()
.reduce((a, b) -> {throw new IllegalStateException(MULTIPLE_INSTANCES_OF +clazz.getName());});
}
private static <T> T single(List<T> list, Class<T> clazz) {
return optional(list, clazz)
.orElseThrow( () -> new IllegalStateException(NO_INSTANCES_OF +clazz.getName()) );
}
private static String MULTIPLE_INSTANCES_OF = "Multiple instances of ";
private static String NO_INSTANCES_OF = "No instances of ";
}

View File

@ -0,0 +1,135 @@
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.contracts.GameBoardContract;
import djmil.cordacheckers.contracts.GameProposalCommand;
import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.GameProposalState;
import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow;
import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.flows.FlowEngine;
import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.application.membership.MemberLookup;
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.StateAndRef;
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.UtxoTransactionBuilder;
import java.time.Duration;
import java.time.Instant;
public class CommandFlow implements ClientStartableFlow {
private final static Logger log = LoggerFactory.getLogger(CommandFlow.class);
@CordaInject
public JsonMarshallingService jsonMarshallingService;
@CordaInject
public UtxoLedgerService utxoLedgerService;
@CordaInject
public FlowEngine flowEngine;
@CordaInject
public MemberLookup memberLookup;
@Override
@Suspendable
public String call(ClientRequestBody requestBody) {
try {
final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class);
//final GameCommand command = args.getCommand();
log.info("GameBoardCommandFlow: findUnconsumedGameBoardState");
final StateAndRef<GameBoardState> gbStateAndRef = findUnconsumedGameBoardState(args.getGameBoardUuid());
// final UtxoSignedTransaction trx = prepareSignedTransaction(command, utxoGameProposal);
// final SecureHash trxId = this.flowEngine
// .subFlow( new CommitSubFlow(trx, command.getRespondent(utxoGameProposal)) );
// if (command == GameProposalCommand.ACCEPT) {
// GameBoardState newGb = (GameBoardState)trx.getOutputStateAndRefs().get(0).getState().getContractState();
// return new FlowResult(newGb.getId(), trxId)
// .toJsonEncodedString(jsonMarshallingService);
// }
log.info("GameBoardCommandFlow: prepareGameBoardView");
GameBoardView gbView = prepareGameBoardView(gbStateAndRef);
return new FlowResult(gbView )
.toJsonEncodedString(jsonMarshallingService);
}
catch (Exception e) {
log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +
" because: " + e.getMessage());
return new FlowResult(e).toJsonEncodedString(jsonMarshallingService);
}
}
@Suspendable
private StateAndRef<GameBoardState> findUnconsumedGameBoardState (UUID gameBoardUuid) {
/*
* Get list of all unconsumed aka 'active' GameProposalStates, 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.utxoLedgerService
.findUnconsumedStatesByType(GameBoardState.class)
.stream()
.filter(sar -> sar.getState().getContractState().getId().equals(gameBoardUuid))
.reduce((a, b) -> {throw new IllegalStateException("Multiple states: " +a +", " +b);})
.get();
}
// @Suspendable
// private UtxoSignedTransaction prepareSignedTransaction(
// 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(command)
// .addSignatories(utxoGameProposal.getState().getContractState().getParticipants());
// 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
// //.addReferenceState(utxoGameProposal.getRef());
// }
// return trxBuilder.toSignedTransaction();
// }
@Suspendable
private GameBoardView prepareGameBoardView(StateAndRef<GameBoardState> stateAndRef) {
final MemberX500Name myName = memberLookup.myInfo().getName();
final SecureHash trxId = stateAndRef.getRef().getTransactionId();
final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService
.findLedgerTransaction(trxId);
log.info("GameBoardCommandFlow: createw gbView");
GameBoardView gbView = new GameBoardView(myName, utxoGameBoard);
gbView.previousCommand = new GameBoardCommand(GameBoardCommand.Type.SURRENDER);
return gbView;
// return new GameBoardView(myName, utxoGameBoard);
}
}

View File

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

View File

@ -0,0 +1,60 @@
package djmil.cordacheckers.gameboard;
import java.util.Map;
import java.util.UUID;
import djmil.cordacheckers.contracts.GameBoardCommand;
import djmil.cordacheckers.contracts.GameBoardContract;
import djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil;
import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.Piece;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
// GameBoard from the player's point of view
public class GameBoardView {
public final String opponentName;
public final Piece.Color opponentColor;
public final Boolean opponentMove;
public final Map<Integer, Piece> board;
public /*final*/ GameBoardCommand previousCommand;
public final String message;
public final UUID id;
// Serialisation service requires a default constructor
public GameBoardView() {
this.opponentName = null;
this.opponentColor = null;
this.opponentMove = null;
this.board = null;
this.previousCommand = null;
this.message = null;
this.id = null;
}
// A view from a perspective of a concrete player, on a ledger transaction that has
// produced new GameBoardState
public GameBoardView(MemberX500Name myName, UtxoLedgerTransaction utxoGameBoard) {
final GameProposalState referanceGameProposal = GameBoardContract
.getReferanceGameProposalState(utxoGameBoard);
this.opponentName = referanceGameProposal.getOpponentName(myName).getCommonName();
this.opponentColor = referanceGameProposal.getOpponentColor(myName);
final GameBoardState stateGameBoard = UtxoLedgerTransactionUtil
.getSingleOutputState(utxoGameBoard, GameBoardState.class);
this.opponentMove = this.opponentColor == stateGameBoard.getMoveColor();
this.board = stateGameBoard.getBoard();
this.message = stateGameBoard.getMessage();
this.id = stateGameBoard.getId();
this.previousCommand = UtxoLedgerTransactionUtil
.getOptionalCommand(utxoGameBoard, GameBoardCommand.class)
.orElseGet(() -> null);
}
}

View File

@ -43,10 +43,10 @@ public class ListFlow implements ClientStartableFlow {
final var unconsumedGameBoardList = utxoLedgerService
.findUnconsumedStatesByType(GameBoardState.class);
List<ListItem> res = new LinkedList<ListItem>();
List<GameBoardView> res = new LinkedList<GameBoardView>();
for (StateAndRef<GameBoardState> gbState :unconsumedGameBoardList) {
// NOTE: lambda operations (aka .map) can not be used with UtxoLedgerService instances
res.add(prepareListItem(gbState));
res.add(prepareGameBoardView(gbState));
}
return new FlowResult(res)
@ -59,7 +59,7 @@ public class ListFlow implements ClientStartableFlow {
}
@Suspendable
private ListItem prepareListItem(StateAndRef<GameBoardState> stateAndRef) {
private GameBoardView prepareGameBoardView(StateAndRef<GameBoardState> stateAndRef) {
final MemberX500Name myName = memberLookup.myInfo().getName();
final SecureHash trxId = stateAndRef.getRef().getTransactionId();
@ -67,14 +67,9 @@ public class ListFlow implements ClientStartableFlow {
final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService
.findLedgerTransaction(trxId);
final GameProposalState referanceGameProposal = GameBoardContract
.getReferanceGameProposalState(utxoGameBoard);
var newGbView = new GameBoardView(myName, utxoGameBoard);
return new ListItem(
stateAndRef.getState().getContractState(),
referanceGameProposal.getOpponentName(myName),
referanceGameProposal.getOpponentColor(myName)
);
return newGbView;
}
}

View File

@ -1,41 +0,0 @@
package djmil.cordacheckers.gameboard;
import java.util.UUID;
import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.Piece;
import net.corda.v5.base.types.MemberX500Name;
// Class to hold results of the List flow.
// JsonMarshallingService can only serialize simple classes that the underlying Jackson serializer recognises,
// hence creating a DTO style object which consists only of Strings and a UUIDs. It is possible to create custom
// serializers for the JsonMarshallingService in the future.
public class ListItem {
public final String opponentName;
public final Piece.Color opponentColor;
public final Boolean opponentMove;
public final Object board;
public final String message;
public final UUID id;
// Serialisation service requires a default constructor
public ListItem() {
this.opponentName = null;
this.opponentColor = null;
this.opponentMove = null;
this.board = null;
this.message = null;
this.id = null;
}
public ListItem(GameBoardState gameBoard, MemberX500Name opponentName, Piece.Color opponentColor) {
this.opponentName = opponentName.getCommonName();
this.opponentColor = opponentColor;
this.opponentMove = opponentColor == gameBoard.getMoveColor();
this.board = gameBoard.getBoard();
this.message = gameBoard.getMessage();
this.id = gameBoard.getId();
}
}

View File

@ -1,97 +0,0 @@
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

@ -44,8 +44,6 @@ public class CommandFlow implements ClientStartableFlow {
final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class);
final GameProposalCommand command = args.getCommand();
System.out.println("Game Proposal command" + command);
final StateAndRef<GameProposalState> utxoGameProposal = findUnconsumedGameProposalState(args.getGameProposalUuid());
final UtxoSignedTransaction trx = prepareSignedTransaction(command, utxoGameProposal);