GameStates refactoring

This commit is contained in:
djmil 2023-09-21 22:59:21 +02:00
parent 959ea0051d
commit 9004e8a408
16 changed files with 215 additions and 287 deletions

View File

@ -21,15 +21,15 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
switch (command.action) {
case GAME_PROPOSAL_ACCEPT:
GameCommand.validateGameProposalAccept(trx);
command.validateGameProposalAccept(trx);
break;
case GAME_BOARD_MOVE:
GameCommand.validateGameBoardMove(trx);
command.validateGameBoardMove(trx);
break;
case GAME_BOARD_SURRENDER:
GameCommand.validateGameBoardSurrender(trx);
command.validateGameBoardSurrender(trx);
break;
default:

View File

@ -1,5 +1,11 @@
package djmil.cordacheckers.contracts;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
import java.util.LinkedList;
import java.util.List;
import djmil.cordacheckers.states.GameBoardState;
import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.GameResultState;
@ -11,13 +17,6 @@ import net.corda.v5.ledger.utxo.Command;
import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
import java.lang.reflect.Array;
import java.util.LinkedList;
import java.util.List;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
public class GameCommand implements Command {
@CordaSerializable
public static enum Action {
@ -78,48 +77,49 @@ public class GameCommand implements Command {
/*
* Session initiator/respondent
*/
public MemberX500Name getObserver(StateAndRef<GameState> gameStateSar) {
public MemberX500Name getCounterparty(StateAndRef<GameState> gameStateSar) {
final GameState gameState = gameStateSar.getState().getContractState();
return gameState.getCounterpartyName(getActor(gameState));
return gameState.getOpponentName(getInitiator(gameState));
}
public MemberX500Name getActor(StateAndRef<GameState> gameStateSar) {
return getActor(gameStateSar.getState().getContractState());
public MemberX500Name getCounterparty(GameState gameState) {
return gameState.getOpponentName(getInitiator(gameState));
}
public MemberX500Name getObserver(GameState gameState) {
return gameState.getCounterpartyName(getActor(gameState));
public MemberX500Name getInitiator(StateAndRef<GameState> gameStateSar) {
return getInitiator(gameStateSar.getState().getContractState());
}
public MemberX500Name getActor(GameState gameState) {
public MemberX500Name getInitiator(GameState gameState) {
switch (action) {
case GAME_PROPOSAL_CREATE:
case GAME_PROPOSAL_CANCEL:
if (gameState instanceof GameProposalState)
return ((GameProposalState)gameState).getIssuer();
if (gameState instanceof GameProposalState)
return ((GameProposalState)gameState).getIssuerName();
break;
case GAME_PROPOSAL_REJECT:
if (gameState instanceof GameProposalState)
return ((GameProposalState)gameState).getAcquier();
return ((GameProposalState)gameState).getAcquierName();
break;
case GAME_PROPOSAL_ACCEPT:
if (gameState instanceof GameProposalState) // <<-- Session validation perspective
return ((GameProposalState)gameState).getAcquier();
return ((GameProposalState)gameState).getAcquierName();
if (gameState instanceof GameBoardState) // <<-- GameViewBuilder perspective
return ((GameBoardState)gameState).getMovePlayerName();
return ((GameBoardState)gameState).getActivePlayerName();
break;
case GAME_BOARD_SURRENDER:
if (gameState instanceof GameBoardState) // <<-- Session validation perspective
return ((GameBoardState)gameState).getActivePlayerName();
if (gameState instanceof GameResultState) // <<-- GameViewBuilder perspective
return ((GameResultState)gameState).getLooserName();
break;
case GAME_BOARD_MOVE:
if (gameState instanceof GameBoardState)
return ((GameBoardState)gameState).getMovePlayerName();
break;
case GAME_BOARD_SURRENDER:
if (gameState instanceof GameResultState)
return ((GameResultState)gameState).getOpponentName();
return ((GameBoardState)gameState).getActivePlayerName();
break;
case GAME_RESULT_CREATE:
@ -138,75 +138,79 @@ public class GameCommand implements Command {
* Transaction validation
*/
public static void validateGameProposalCreate(UtxoLedgerTransaction trx) {
public void validateGameProposalCreate(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE);
GameProposalState outputState = getSingleOutputState(trx, GameProposalState.class);
final GameProposalState gameProposal = getSingleOutputState(trx, GameProposalState.class);
requireThat(outputState.getAcquierColor() != null, NON_NULL_RECIPIENT_COLOR);
requireThat(gameProposal.getOpponentName(gameProposal.getIssuerName()).compareTo(gameProposal.getAcquierName()) == 0,
"GameProposal.Issuer must be either Black or White player");
}
public static void validateGameProposalAccept(UtxoLedgerTransaction trx) {
public void validateGameProposalAccept(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().size() == 1, ACCEPT_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, ACCEPT_OUTPUT_STATE);
GameProposalState inGameProposal = getSingleInputState(trx, GameProposalState.class);
GameBoardState outGameBoard = getSingleOutputState(trx, GameBoardState.class);
final GameProposalState inGameProposal = getSingleInputState(trx, GameProposalState.class);
final GameBoardState outGameBoard = getSingleOutputState(trx, GameBoardState.class);
requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), IN_OUT_PARTICIPANTS);
requireThat(inGameProposal.getParticipants().containsAll(outGameBoard.getParticipants()), IN_OUT_PARTICIPANTS);
}
public static void validateGameProposalReject(UtxoLedgerTransaction trx) {
public void validateGameProposalReject(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE);
requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE);
getSingleInputState(trx, GameProposalState.class);
}
public static void validateGameProposalCancel(UtxoLedgerTransaction trx) {
public void validateGameProposalCancel(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE);
requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE);
getSingleInputState(trx, GameProposalState.class);
}
public static void validateGameBoardSurrender(UtxoLedgerTransaction trx) {
public void validateGameBoardSurrender(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().size() == 1, SURRENDER_INPUT_STATE);
final var inGameBoardState = getSingleInputState(trx, GameBoardState.class);
requireThat(trx.getOutputContractStates().size() == 1, SURRENDER_OUTPUT_STATE);
final var outGameResultState = getSingleOutputState(trx, GameResultState.class);
List<MemberX500Name> inActors = new LinkedList<MemberX500Name>(List.of(
inGameBoardState.getWhitePlayerName(),
inGameBoardState.getBlackPlayerName())
inGameBoardState.getWhitePlayer(),
inGameBoardState.getBlackPlayer())
);
List<MemberX500Name> outActors = new LinkedList<MemberX500Name>(List.of(
outGameResultState.getWinnerName(),
outGameResultState.getOpponentName())
outGameResultState.getWhitePlayer(),
outGameResultState.getBlackPlayer())
);
requireThat(inActors.containsAll(outActors) && outActors.containsAll(inActors), IN_OUT_PARTICIPANTS);
final var activePlayerName = outGameResultState.getCounterpartyName(outGameResultState.getWinnerName());
requireThat(inGameBoardState.getMovePlayerName().compareTo(activePlayerName) == 0, SURRENDER_ACTOR + "expected " +inGameBoardState.getMovePlayerName() +" actual " +activePlayerName);
final var activePlayerName = getInitiator(inGameBoardState);
requireThat(inGameBoardState.getActivePlayerName().compareTo(activePlayerName) == 0, SURRENDER_ACTOR + "expected " +inGameBoardState.getActivePlayerName() +" actual " +activePlayerName);
final var expectedWinnerName = inGameBoardState.getOpponentName(activePlayerName);
requireThat(outGameResultState.getWinnerName().compareTo(expectedWinnerName) == 0,
"Expected winner "+expectedWinnerName.getCommonName() +", proposed winner " +outGameResultState.getWinnerName().getCommonName());
}
public static void validateGameBoardMove(UtxoLedgerTransaction trx) {
public void validateGameBoardMove(UtxoLedgerTransaction trx) {
requireThat(trx.getInputContractStates().size() == 1, MOVE_INPUT_STATE);
final var inGameBoardState = getSingleInputState(trx, GameBoardState.class);
requireThat(trx.getOutputContractStates().size() == 1, MOVE_OUTPUT_STATE);
final var outGameBoardState = getSingleOutputState(trx, GameBoardState.class);
requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameBoardState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS);
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameBoardState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS);
requireThat(inGameBoardState.getWhitePlayer().compareTo(outGameBoardState.getWhitePlayer()) == 0, IN_OUT_PARTICIPANTS);
requireThat(inGameBoardState.getBlackPlayer().compareTo(outGameBoardState.getBlackPlayer()) == 0, IN_OUT_PARTICIPANTS);
}
public static void validateGameResultCreate(UtxoLedgerTransaction trx) {
public void validateGameResultCreate(UtxoLedgerTransaction trx) {
throw new RuntimeException("Unimplemented");
}
@ -221,7 +225,6 @@ public class GameCommand implements Command {
static final String CREATE_INPUT_STATE = "Create command should have no input states";
static final String CREATE_OUTPUT_STATE = "Create command should output exactly one GameProposal state";
static final String NON_NULL_RECIPIENT_COLOR = "GameProposal.recipientColor field can not be null";
static final String REJECT_INPUT_STATE = "Reject command should have exactly one GameProposal input state";
static final String REJECT_OUTPUT_STATE = "Reject command should have no output states";

View File

@ -21,19 +21,19 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
switch (command.action) {
case GAME_PROPOSAL_CREATE:
GameCommand.validateGameProposalCreate(trx);
command.validateGameProposalCreate(trx);
break;
case GAME_PROPOSAL_ACCEPT:
GameCommand.validateGameProposalAccept(trx);
command.validateGameProposalAccept(trx);
break;
case GAME_PROPOSAL_REJECT:
GameCommand.validateGameProposalReject(trx);
command.validateGameProposalReject(trx);
break;
case GAME_PROPOSAL_CANCEL:
GameCommand.validateGameProposalCancel(trx);
command.validateGameProposalCancel(trx);
break;
default:

View File

@ -21,11 +21,11 @@ public class GameResultContract implements net.corda.v5.ledger.utxo.Contract {
switch (command.action) {
case GAME_BOARD_SURRENDER:
GameCommand.validateGameBoardSurrender(trx);
command.validateGameBoardSurrender(trx);
break;
case GAME_RESULT_CREATE:
GameCommand.validateGameResultCreate(trx);
command.validateGameResultCreate(trx);
break;
default:

View File

@ -14,19 +14,13 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
@BelongsToContract(GameBoardContract.class)
public class GameBoardState extends GameState {
private final MemberX500Name whitePlayerName;
private final MemberX500Name blackPlayerName;
private final Piece.Color moveColor;
private final Integer moveNumber;
private final Map<Integer, Piece> board;
public GameBoardState(GameProposalState gameProposalState) {
super(gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants);
this.whitePlayerName = gameProposalState.getWhitePlayerName();
this.blackPlayerName = gameProposalState.getBlackPlayerName();
super(gameProposalState.whitePlayer, gameProposalState.blackPlayer,
gameProposalState.gameUuid, gameProposalState.message, gameProposalState.participants);
// Initial GameBoard state
this.moveColor = Piece.Color.WHITE;
@ -34,43 +28,26 @@ public class GameBoardState extends GameState {
this.board = new LinkedHashMap<Integer, Piece>(initialBoard);
}
public GameBoardState(
GameBoardState oldGameBoardState, Map<Integer, Piece> newBoard, Piece.Color moveColor) {
super(oldGameBoardState.gameUuid, oldGameBoardState.message, oldGameBoardState.participants);
this.whitePlayerName = oldGameBoardState.getWhitePlayerName();
this.blackPlayerName = oldGameBoardState.getBlackPlayerName();
public GameBoardState(GameBoardState oldGameBoardState, Map<Integer, Piece> newBoard, Piece.Color moveColor) {
super(oldGameBoardState.whitePlayer, oldGameBoardState.blackPlayer,
oldGameBoardState.gameUuid, oldGameBoardState.message, oldGameBoardState.participants);
// Initial GameBoard state
this.moveColor = moveColor;
this.moveNumber = oldGameBoardState.getMoveNumber() +1;
this.board = newBoard;
}
@ConstructorForDeserialization
public GameBoardState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName,
public GameBoardState(MemberX500Name whitePlayer, MemberX500Name blackPlayer,
Color moveColor, Integer moveNumber, Map<Integer, Piece> board, String message,
UUID gameUuid, List<PublicKey> participants) {
super(gameUuid, message, participants);
super(whitePlayer, blackPlayer, gameUuid, message, participants);
this.whitePlayerName = whitePlayerName;
this.blackPlayerName = blackPlayerName;
this.moveColor = moveColor;
this.moveNumber = moveNumber;
this.board = board;
}
public MemberX500Name getMovePlayerName() {
switch (moveColor) {
case WHITE:
return whitePlayerName;
case BLACK:
return blackPlayerName;
default:
throw new Piece.Color.UnknownException();
}
}
public Piece.Color getMoveColor() {
return moveColor;
}
@ -79,26 +56,14 @@ public class GameBoardState extends GameState {
return moveNumber;
}
public MemberX500Name getActivePlayerName() {
return moveColor == Piece.Color.WHITE ? whitePlayer : blackPlayer;
}
public Map<Integer, Piece> getBoard() {
return board;
}
@Override
public MemberX500Name getWhitePlayerName() {
return whitePlayerName;
}
@Override
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
if (whitePlayerName.compareTo(myName) == 0)
return blackPlayerName;
if (blackPlayerName.compareTo(myName) == 0)
return whitePlayerName;
throw new GameState.NotInvolved(myName, GameBoardState.class, this.gameUuid);
}
// TODO: move to contract
public final static Map<Integer, Piece> initialBoard = Map.ofEntries(
// Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php

View File

@ -12,50 +12,21 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
@BelongsToContract(GameProposalContract.class)
public class GameProposalState extends GameState {
private final MemberX500Name issuer;
private final MemberX500Name acquier;
private final Piece.Color acquierColor;
private final MemberX500Name issuerName;
@ConstructorForDeserialization
public GameProposalState(
MemberX500Name issuer,
MemberX500Name acquier,
Piece.Color acquierColor,
String message,
UUID gameUuid,
List<PublicKey> participants
) {
super(gameUuid, message, participants);
this.issuer = issuer;
this.acquier = acquier;
this.acquierColor = acquierColor;
public GameProposalState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, MemberX500Name issuerName,
UUID gameUuid, String message, List<PublicKey> participants) {
super(whitePlayer, blackPlayer, gameUuid, message, participants);
this.issuerName = issuerName;
}
public MemberX500Name getIssuer() {
return issuer;
public MemberX500Name getIssuerName() {
return issuerName;
}
public MemberX500Name getAcquier() {
return acquier;
}
public Piece.Color getAcquierColor() {
return acquierColor;
}
public MemberX500Name getWhitePlayerName() {
return acquierColor == Piece.Color.WHITE ? getAcquier() : getIssuer();
}
@Override
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
if (issuer.compareTo(myName) == 0)
return acquier;
if (acquier.compareTo(myName) == 0)
return issuer;
throw new GameState.NotInvolved(myName, GameProposalState.class, this.gameUuid);
public MemberX500Name getAcquierName() {
return getOpponentName(issuerName);
}
}

View File

@ -13,52 +13,25 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
public class GameResultState extends GameState {
private final MemberX500Name winnerName;
private final MemberX500Name opponentName;
private final Piece.Color winnerColor;
@ConstructorForDeserialization
public GameResultState(MemberX500Name winnerName, MemberX500Name opponentName, Piece.Color winnerColor,
public GameResultState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, MemberX500Name winnerName,
UUID gameUuid, String message, List<PublicKey> participants) {
super(gameUuid, message, participants);
super(whitePlayer, blackPlayer, gameUuid, message, participants);
this.winnerName = winnerName;
this.opponentName = opponentName;
this.winnerColor = winnerColor;
}
public GameResultState(GameBoardState gameBoardState, MemberX500Name winnerName) {
super(gameBoardState.gameUuid, null, gameBoardState.participants);
this.opponentName = gameBoardState.getCounterpartyName(winnerName);
super(gameBoardState.whitePlayer, gameBoardState.blackPlayer, gameBoardState.gameUuid, null, gameBoardState.participants);
this.winnerName = winnerName;
this.winnerColor = gameBoardState.getWhitePlayerName().compareTo(winnerName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK;
}
public MemberX500Name getWinnerName() {
return winnerName;
}
public MemberX500Name getOpponentName() {
return opponentName;
}
public Piece.Color getWinnerColor() {
return winnerColor;
}
@Override
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
if (winnerName.compareTo(myName) == 0)
return opponentName;
if (opponentName.compareTo(myName) == 0)
return winnerName;
throw new GameState.NotInvolved(myName, GameResultState.class, this.gameUuid);
}
@Override
public MemberX500Name getWhitePlayerName() {
return winnerColor == Piece.Color.WHITE ? winnerName : opponentName;
public MemberX500Name getLooserName() {
return getOpponentName(getWinnerName());
}
}

View File

@ -4,30 +4,35 @@ import java.security.PublicKey;
import java.util.List;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import net.corda.v5.base.annotations.CordaSerializable;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.ContractState;
@CordaSerializable
public abstract class GameState implements ContractState {
final MemberX500Name whitePlayer;
final MemberX500Name blackPlayer;
final UUID gameUuid;
final String message;
final List<PublicKey> participants;
@NotNull
public abstract MemberX500Name getWhitePlayerName();
@NotNull
public abstract MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved;
GameState(UUID gameUuid, String message, List<PublicKey> participants) {
GameState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, UUID gameUuid,
String message, List<PublicKey> participants) {
this.whitePlayer = whitePlayer;
this.blackPlayer = blackPlayer;
this.gameUuid = gameUuid;
this.message = message;
this.participants = participants;
}
public MemberX500Name getWhitePlayer() {
return whitePlayer;
}
public MemberX500Name getBlackPlayer() {
return blackPlayer;
}
public UUID getGameUuid() {
return gameUuid;
}
@ -36,18 +41,25 @@ public abstract class GameState implements ContractState {
return message;
}
@Override
public List<PublicKey> getParticipants() {
return participants;
}
public MemberX500Name getBlackPlayerName() {
return getCounterpartyName(getWhitePlayerName());
public MemberX500Name getOpponentName(MemberX500Name playerName) throws NotInvolved {
if (playerName.compareTo(whitePlayer) == 0)
return blackPlayer;
if (playerName.compareTo(blackPlayer) == 0)
return whitePlayer;
throw new NotInvolved(playerName, this.getClass(), gameUuid);
}
public Piece.Color getCounterpartyColor(MemberX500Name myName) throws NotInvolved {
final MemberX500Name opponentName = getCounterpartyName(myName);
return getWhitePlayerName().compareTo(opponentName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK;
public Piece.Color getOpponentColor(MemberX500Name playerName) throws NotInvolved {
if (playerName.compareTo(whitePlayer) == 0)
return Piece.Color.BLACK;
if (playerName.compareTo(blackPlayer) == 0)
return Piece.Color.WHITE;
throw new NotInvolved(playerName, this.getClass(), gameUuid);
}
public static class NotInvolved extends RuntimeException {

View File

@ -52,7 +52,7 @@ public class SurrenderFlow implements ClientStartableFlow{
@Suspendable
@Override
public String call(ClientRequestBody requestBody) {
SecureHash gameStateUtxoTrxId = null;
SecureHash utxoTrxId = null;
try {
final GameCommand command = new GameCommand(GameCommand.Action.GAME_BOARD_SURRENDER);
@ -64,7 +64,7 @@ public class SurrenderFlow implements ClientStartableFlow{
final GameResultState outputState = prepareGameResultState(inputStateSar);
final UtxoSignedTransaction transactionCandidate = utxoLedgerService.createTransactionBuilder()
final UtxoSignedTransaction gameBoardSurrenderTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command)
.addInputState(inputStateSar.getRef())
.addOutputState(outputState)
@ -73,19 +73,19 @@ public class SurrenderFlow implements ClientStartableFlow{
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction();
gameStateUtxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(transactionCandidate, command.getObserver(outputState)));
utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameBoardSurrenderTrx, command.getCounterparty(inputStateSar)));
final View gameStateView = this.flowEngine
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
.subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
return new FlowResponce(gameStateView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
e.printStackTrace(System.out);
return new FlowResponce(e, gameStateUtxoTrxId)
return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
}
@ -96,7 +96,7 @@ public class SurrenderFlow implements ClientStartableFlow{
final GameBoardState gameBoard = (GameBoardState) gameState;
final MemberX500Name myName = memberLookup.myInfo().getName();
final MemberX500Name winnerName = gameBoard.getCounterpartyName(myName);
final MemberX500Name winnerName = gameBoard.getOpponentName(myName);
return new GameResultState(gameBoard, winnerName);
}

View File

@ -43,40 +43,41 @@ public class AcceptFlow implements ClientStartableFlow{
@Suspendable
@Override
public String call(ClientRequestBody requestBody) {
SecureHash gameStateUtxoTrxId = null;
SecureHash utxoTrxId = null;
try {
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_ACCEPT);
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody());
final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
final StateAndRef<GameState> inputState = this.flowEngine
.subFlow(new GetFlow(gameStateUuid));
final StateAndRef<GameState> gameProposalSar = this.flowEngine
.subFlow(new GetFlow(gameUuid));
final GameProposalState gameProposal = (GameProposalState)gameProposalSar.getState().getContractState();
final GameBoardState outputState = new GameBoardState((GameProposalState)inputState.getState().getContractState());
final GameBoardState gameBoard = new GameBoardState(gameProposal); // <<-- accepted
final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder()
final UtxoSignedTransaction gameProposalAcceptTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command)
.addInputState(inputState.getRef())
.addOutputState(outputState)
.addSignatories(inputState.getState().getContractState().getParticipants())
.setNotary(inputState.getState().getNotaryName())
.addInputState(gameProposalSar.getRef())
.addOutputState(gameBoard)
.addSignatories(gameProposal.getParticipants())
.setNotary(gameProposalSar.getState().getNotaryName())
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction();
gameStateUtxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState)));
utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameProposalAcceptTrx, command.getCounterparty(gameProposal)));
final View gameStateView = this.flowEngine
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
final View gameView = this.flowEngine
.subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
return new FlowResponce(gameView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
e.printStackTrace(System.out);
return new FlowResponce(e, gameStateUtxoTrxId)
return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
}

View File

@ -13,6 +13,7 @@ import djmil.cordacheckers.gamestate.FlowResponce;
import djmil.cordacheckers.gamestate.GetFlow;
import djmil.cordacheckers.gamestate.View;
import djmil.cordacheckers.gamestate.ViewBuilder;
import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.GameState;
import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow;
@ -41,36 +42,37 @@ public class CancelFlow implements ClientStartableFlow{
@Suspendable
@Override
public String call(ClientRequestBody requestBody) {
SecureHash gameStateUtxoTrxId = null;
SecureHash utxoTrxId = null;
try {
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_CANCEL);
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody());
final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
final StateAndRef<GameState> inputState = this.flowEngine
.subFlow(new GetFlow(gameStateUuid));
final StateAndRef<GameState> gameProposalSar = this.flowEngine
.subFlow(new GetFlow(gameUuid));
final GameProposalState gameProposal = (GameProposalState)gameProposalSar.getState().getContractState();
final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder()
final UtxoSignedTransaction gameProposalCancelTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command)
.addInputState(inputState.getRef())
.addSignatories(inputState.getState().getContractState().getParticipants())
.setNotary(inputState.getState().getNotaryName())
.addInputState(gameProposalSar.getRef())
.addSignatories(gameProposal.getParticipants())
.setNotary(gameProposalSar.getState().getNotaryName())
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction();
gameStateUtxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState)));
utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameProposalCancelTrx, command.getCounterparty(gameProposal)));
final View gameStateView = this.flowEngine
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
.subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
return new FlowResponce(gameStateView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
return new FlowResponce(e, gameStateUtxoTrxId)
return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
}

View File

@ -53,60 +53,63 @@ public class CreateFlow implements ClientStartableFlow{
@Suspendable
@Override
public String call(ClientRequestBody requestBody) {
SecureHash gameStateUtxoTrxId = null;
SecureHash utxoTrxId = null;
try {
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_CREATE);
final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody);
final GameProposalState gameProposal = buildGameProposalStateFrom(requestBody);
final UtxoSignedTransaction utxoCandidate = utxoLedgerService.createTransactionBuilder()
final UtxoSignedTransaction gameProposalCreateTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command)
.addOutputState(newGameProposal)
.addSignatories(newGameProposal.getParticipants())
.addOutputState(gameProposal)
.addSignatories(gameProposal.getParticipants())
.setNotary(notaryLookup.getNotaryServices().iterator().next().getName())
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction();
gameStateUtxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(utxoCandidate, command.getObserver(newGameProposal)));
utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameProposalCreateTrx, command.getCounterparty(gameProposal)));
final View gameStateView = this.flowEngine
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
final View gameView = this.flowEngine
.subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
return new FlowResponce(gameView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
return new FlowResponce(e, gameStateUtxoTrxId)
return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
}
@Suspendable
private GameProposalState buildGameProposalStateFrom(ClientRequestBody requestBody) {
CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class);
final CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class);
Piece.Color opponentColor = Piece.Color.valueOf(args.opponentColor);
final Piece.Color opponentColor = Piece.Color.valueOf(args.opponentColor);
if (opponentColor == null) {
throw new RuntimeException("Allowed values for opponentColor are: "
+ Piece.Color.WHITE.name() +", " + Piece.Color.BLACK.name()
+ ". Actual value was: " + args.opponentColor);
}
MemberInfo myInfo = memberLookup.myInfo();
MemberInfo opponentInfo = requireNonNull(
final MemberInfo myInfo = memberLookup.myInfo();
final MemberInfo opponentInfo = requireNonNull(
memberLookup.lookup(MemberX500Name.parse(args.opponentName)),
"MemberLookup can't find opponentName specified in flow arguments: " + args.opponentName
);
final MemberInfo whitePlayerInfo = opponentColor == Piece.Color.WHITE ? opponentInfo : myInfo;
final MemberInfo blackPlayerInfo = opponentColor == Piece.Color.BLACK ? opponentInfo : myInfo;
return new GameProposalState(
myInfo.getName(),
opponentInfo.getName(),
opponentColor,
args.message,
whitePlayerInfo.getName(),
blackPlayerInfo.getName(),
myInfo.getName(), // <<--- GameProposal issuer
UUID.randomUUID(),
args.message,
Arrays.asList(myInfo.getLedgerKeys().get(0), opponentInfo.getLedgerKeys().get(0))
);
}

View File

@ -13,6 +13,7 @@ import djmil.cordacheckers.gamestate.FlowResponce;
import djmil.cordacheckers.gamestate.GetFlow;
import djmil.cordacheckers.gamestate.View;
import djmil.cordacheckers.gamestate.ViewBuilder;
import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.GameState;
import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow;
@ -49,36 +50,37 @@ public class RejectFlow implements ClientStartableFlow{
@Suspendable
@Override
public String call(ClientRequestBody requestBody) {
SecureHash gameStateUtxoTrxId = null;
SecureHash utxoTrxId = null;
try {
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_REJECT);
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody());
final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
final StateAndRef<GameState> inputState = this.flowEngine
.subFlow(new GetFlow(gameStateUuid));
final StateAndRef<GameState> gameProposalSar = this.flowEngine
.subFlow(new GetFlow(gameUuid));
final GameProposalState gameProposal = (GameProposalState)gameProposalSar.getState().getContractState();
final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder()
final UtxoSignedTransaction gameProposalRejectTrx = utxoLedgerService.createTransactionBuilder()
.addCommand(command)
.addInputState(inputState.getRef())
.addSignatories(inputState.getState().getContractState().getParticipants())
.setNotary(inputState.getState().getNotaryName())
.addInputState(gameProposalSar.getRef())
.addSignatories(gameProposal.getParticipants())
.setNotary(gameProposalSar.getState().getNotaryName())
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.toSignedTransaction();
gameStateUtxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState)));
utxoTrxId = this.flowEngine
.subFlow(new CommitSubFlow(gameProposalRejectTrx, command.getCounterparty(gameProposal)));
final View gameStateView = this.flowEngine
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
.subFlow(new ViewBuilder(utxoTrxId));
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
return new FlowResponce(gameStateView, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
catch (Exception e) {
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
return new FlowResponce(e, gameStateUtxoTrxId)
return new FlowResponce(e, utxoTrxId)
.toJsonEncodedString(jsonMarshallingService);
}
}

View File

@ -41,7 +41,7 @@ public class CommitSubFlowResponder implements ResponderFlow {
UtxoTransactionValidator txValidator = trxToValidate -> {
try {
final GameCommand gameCommand = getSingleCommand(trxToValidate, GameCommand.class);
final GameState gameState = getGameStateFromTransaction(trxToValidate, gameCommand);
final GameState gameState = getActualGameStateFromTransaction(trxToValidate, gameCommand);
checkParticipants(session, gameCommand, gameState);
@ -72,10 +72,10 @@ public class CommitSubFlowResponder implements ResponderFlow {
* @param command
* @return the most recent (from perspective of session participants validation) GameState for a given transaction
*
* @see djmil.cordacheckers.gamestate.ViewBuilder#getGameStateFromTransaction(UtxoLedgerTransaction, GameCommand)
* @see djmil.cordacheckers.gamestate.ViewBuilder#getLatestGameStateFromTransaction(UtxoLedgerTransaction, GameCommand)
*/
@Suspendable
GameState getGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) {
GameState getActualGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) {
switch (command.action) {
case GAME_PROPOSAL_CREATE:
return getSingleOutputState(gameStateTransaction, GameProposalState.class);
@ -85,10 +85,12 @@ public class CommitSubFlowResponder implements ResponderFlow {
case GAME_PROPOSAL_CANCEL:
return getSingleInputState(gameStateTransaction, GameProposalState.class);
case GAME_BOARD_SURRENDER:
return getSingleInputState(gameStateTransaction, GameBoardState.class);
case GAME_BOARD_MOVE:
return getSingleOutputState(gameStateTransaction, GameBoardState.class);
case GAME_BOARD_SURRENDER:
case GAME_RESULT_CREATE:
return getSingleOutputState(gameStateTransaction, GameResultState.class);
}
@ -97,22 +99,17 @@ public class CommitSubFlowResponder implements ResponderFlow {
}
@Suspendable
void checkParticipants(FlowSession session, GameCommand gameCommand, GameState outputGameState) throws ParticipantException {
void checkParticipants(FlowSession session, GameCommand gameCommand, GameState gameState) throws ParticipantException {
final var myName = memberLookup.myInfo().getName();
final var outputStateConterparty = outputGameState.getCounterpartyName(myName);
final var sessionConterparty = session.getCounterparty();
final var actor = gameCommand.getActor(outputGameState);
final var observer = gameCommand.getObserver(outputGameState);
final var opponentName = gameState.getOpponentName(myName); // throws NotInvolved
final var conterpartyName = session.getCounterparty();
final var actorName = gameCommand.getInitiator(gameState);
if (outputStateConterparty.compareTo(sessionConterparty) != 0)
throw new ParticipantException("Counterparty", sessionConterparty, outputStateConterparty);
if (conterpartyName.compareTo(opponentName) != 0)
throw new ParticipantException("Counterparty", conterpartyName, opponentName);
if (actor.compareTo(sessionConterparty) != 0)
throw new ParticipantException("Actor", sessionConterparty, actor);
if (observer.compareTo(myName) != 0)
throw new ParticipantException("Observer", myName, observer);
if (actorName.compareTo(conterpartyName) != 0)
throw new ParticipantException("Actor", conterpartyName, actorName);
}
public static class ParticipantException extends Exception {

View File

@ -10,7 +10,6 @@ import djmil.cordacheckers.states.GameResultState;
import djmil.cordacheckers.states.Piece;
import net.corda.v5.base.types.MemberX500Name;
// GameBoard from the player's point of view
public class View {
public static enum Status {
@ -51,8 +50,8 @@ public class View {
public View(View.Status status, GameProposalState gameProposal, MemberX500Name myName) {
this.status = status;
this.opponentName = gameProposal.getCounterpartyName(myName).getCommonName();
this.opponentColor = gameProposal.getCounterpartyColor(myName);
this.opponentName = gameProposal.getOpponentName(myName).getCommonName();
this.opponentColor = gameProposal.getOpponentColor(myName);
this.board = null;
this.moveNumber = null;
this.previousMove = null;
@ -62,8 +61,8 @@ public class View {
public View(View.Status status, GameBoardState gameBoard, MemberX500Name myName) {
this.status = status;
this.opponentName = gameBoard.getCounterpartyName(myName).getCommonName();
this.opponentColor = gameBoard.getCounterpartyColor(myName);
this.opponentName = gameBoard.getOpponentName(myName).getCommonName();
this.opponentColor = gameBoard.getOpponentColor(myName);
this.board = gameBoard.getBoard();
this.moveNumber = gameBoard.getMoveNumber();
this.previousMove = null;
@ -73,8 +72,8 @@ public class View {
public View(View.Status status, GameBoardState gameBoard, List<Integer> previousMove, MemberX500Name myName) {
this.status = status;
this.opponentName = gameBoard.getCounterpartyName(myName).getCommonName();
this.opponentColor = gameBoard.getCounterpartyColor(myName);
this.opponentName = gameBoard.getOpponentName(myName).getCommonName();
this.opponentColor = gameBoard.getOpponentColor(myName);
this.board = gameBoard.getBoard();
this.moveNumber = gameBoard.getMoveNumber();
this.previousMove = previousMove;
@ -84,8 +83,8 @@ public class View {
public View(View.Status status, GameResultState gameResult, MemberX500Name myName) {
this.status = status;
this.opponentName = gameResult.getCounterpartyName(myName).getCommonName();
this.opponentColor = gameResult.getCounterpartyColor(myName);
this.opponentName = gameResult.getOpponentName(myName).getCommonName();
this.opponentColor = gameResult.getOpponentColor(myName);
this.board = null;
this.moveNumber = null;
this.previousMove = null;

View File

@ -48,7 +48,7 @@ public class ViewBuilder implements SubFlow<View> {
MemberX500Name myName = memberLookup.myInfo().getName();
final GameCommand command = getSingleCommand(gameStateUtxo, GameCommand.class);
final GameState state = getGameStateFromTransaction(gameStateUtxo, command);
final GameState state = getLatestGameStateFromTransaction(gameStateUtxo, command);
final View.Status viewStatus = action2status(command, state, myName);
switch (command.action) {
@ -81,10 +81,10 @@ public class ViewBuilder implements SubFlow<View> {
* @param command
* @return the most recent (from perspective of building a GameView) GameState for a given transaction
*
* @see djmil.cordacheckers.gamestate.CommitSubFlowResponder#getGameStateFromTransaction(UtxoLedgerTransaction, GameCommand)
* @see djmil.cordacheckers.gamestate.CommitSubFlowResponder#getActualGameStateFromTransaction(UtxoLedgerTransaction, GameCommand)
*/
@Suspendable
GameState getGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) {
GameState getLatestGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) {
switch (command.action) {
case GAME_PROPOSAL_CREATE:
return getSingleOutputState(gameStateTransaction, GameProposalState.class);
@ -107,7 +107,7 @@ public class ViewBuilder implements SubFlow<View> {
@Suspendable
View.Status action2status(GameCommand command, GameState state, MemberX500Name myName) {
final boolean myAction = command.getActor(state).compareTo(myName) == 0;
final boolean myAction = command.getInitiator(state).compareTo(myName) == 0;
switch (command.action) {
case GAME_PROPOSAL_CREATE: