GameBoard command SURRENDER
- produces GameResultState - .addReferanceState() was changed with Opponent interface for States
This commit is contained in:
parent
d7b6ce1f25
commit
a34ea39dfb
@ -23,6 +23,7 @@ import djmil.cordacheckers.cordaclient.dao.Piece;
|
|||||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
|
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
||||||
|
import djmil.cordacheckers.cordaclient.dao.GameResult;
|
||||||
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
||||||
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
|
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.RequestBody;
|
import djmil.cordacheckers.cordaclient.dao.flow.RequestBody;
|
||||||
@ -32,7 +33,7 @@ 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.Empty;
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardCommandReq;
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardCommandReq;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardCommandRes;
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardResGameResult;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardListRes;
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardListRes;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandAcceptRes;
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandAcceptRes;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandReq;
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandReq;
|
||||||
@ -199,29 +200,28 @@ public class CordaClient {
|
|||||||
return listFlowResult.successStatus();
|
return listFlowResult.successStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameBoard gameBoardCommand(
|
public GameResult gameBoardSurrender(
|
||||||
HoldingIdentity myHoldingIdentity,
|
HoldingIdentity myHoldingIdentity,
|
||||||
UUID gameBoardUuid,
|
UUID gameBoardUuid
|
||||||
GameBoardCommand command
|
|
||||||
) {
|
) {
|
||||||
final RequestBody requestBody = new RequestBody(
|
final RequestBody requestBody = new RequestBody(
|
||||||
"gb.command-" +command.getType() +UUID.randomUUID(),
|
"gb.surrender-" +UUID.randomUUID(),
|
||||||
"djmil.cordacheckers.gameboard.CommandFlow",
|
"djmil.cordacheckers.gameboard.CommandFlow",
|
||||||
new GameBoardCommandReq(
|
new GameBoardCommandReq(
|
||||||
gameBoardUuid,
|
gameBoardUuid,
|
||||||
command
|
new GameBoardCommand(GameBoardCommand.Type.SURRENDER)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
final GameBoardCommandRes moveResult = cordaFlowExecute(
|
final GameBoardResGameResult moveResult = cordaFlowExecute(
|
||||||
myHoldingIdentity,
|
myHoldingIdentity,
|
||||||
requestBody,
|
requestBody,
|
||||||
GameBoardCommandRes.class
|
GameBoardResGameResult.class
|
||||||
);
|
);
|
||||||
|
|
||||||
if (moveResult.failureStatus() != null) {
|
if (moveResult.failureStatus() != null) {
|
||||||
System.out.println("GameBoard.MoveFlow failed: " + moveResult.failureStatus());
|
System.out.println("GameBoard.CommandFlow failed: " + moveResult.failureStatus());
|
||||||
throw new RuntimeException("GameBoard: MoveFlow execution has failed");
|
throw new RuntimeException("GameBoard: CommandFlow execution has failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
return moveResult.successStatus();
|
return moveResult.successStatus();
|
||||||
|
@ -13,6 +13,8 @@ public record GameBoard(
|
|||||||
Map<Integer, Piece> board,
|
Map<Integer, Piece> board,
|
||||||
GameBoardCommand previousCommand,
|
GameBoardCommand previousCommand,
|
||||||
String message,
|
String message,
|
||||||
UUID id) implements CordaState {
|
UUID id)
|
||||||
|
|
||||||
|
implements CordaState {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ public class GameBoardCommand {
|
|||||||
public static enum Type {
|
public static enum Type {
|
||||||
MOVE,
|
MOVE,
|
||||||
SURRENDER,
|
SURRENDER,
|
||||||
REQUEST_DRAW,
|
DRAW,
|
||||||
REQUEST_VICTORY,
|
VICTORY,
|
||||||
FINISH; // aka accept DRAW or VICTORY request
|
ACCEPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Type type;
|
private final Type type;
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package djmil.cordacheckers.cordaclient.dao;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
|
||||||
|
@JsonDeserialize
|
||||||
|
public record GameResult(
|
||||||
|
String whitePlayerName,
|
||||||
|
String blackPlayerName,
|
||||||
|
Piece.Color victoryColor,
|
||||||
|
UUID id)
|
||||||
|
|
||||||
|
implements CordaState {
|
||||||
|
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
|
||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
|
||||||
|
|
||||||
public record GameBoardCommandRes(GameBoard successStatus, String failureStatus) {
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,7 @@
|
|||||||
|
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.cordaclient.dao.GameResult;
|
||||||
|
|
||||||
|
public record GameBoardResGameResult(GameResult successStatus, String failureStatus) {
|
||||||
|
|
||||||
|
}
|
@ -17,8 +17,8 @@ import com.fasterxml.jackson.databind.JsonMappingException;
|
|||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.CordaState;
|
import djmil.cordacheckers.cordaclient.dao.CordaState;
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
||||||
|
import djmil.cordacheckers.cordaclient.dao.GameResult;
|
||||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
import djmil.cordacheckers.cordaclient.dao.Piece;
|
||||||
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
||||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||||
@ -184,7 +184,7 @@ public class CordaClientTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGameBoardSurrender() throws JsonMappingException, JsonProcessingException {
|
void testGameBoardSurrender() throws JsonMappingException, JsonProcessingException, InvalidNameException {
|
||||||
final var hiAlice = holdingIdentityResolver.getByUsername("alice");
|
final var hiAlice = holdingIdentityResolver.getByUsername("alice");
|
||||||
final var hiBob = holdingIdentityResolver.getByUsername("bob");
|
final var hiBob = holdingIdentityResolver.getByUsername("bob");
|
||||||
final var bobColor = Piece.Color.WHITE;
|
final var bobColor = Piece.Color.WHITE;
|
||||||
@ -202,12 +202,17 @@ public class CordaClientTest {
|
|||||||
|
|
||||||
System.out.println("New GameBoard UUID "+ gbState.id());
|
System.out.println("New GameBoard UUID "+ gbState.id());
|
||||||
|
|
||||||
final GameBoard gbSurrender = cordaClient.gameBoardCommand(
|
assertThatThrownBy(() -> { // Alice can not surrender, since it is Bob's turn
|
||||||
hiBob, gbState.id(),
|
cordaClient.gameBoardSurrender(
|
||||||
new GameBoardCommand(GameBoardCommand.Type.SURRENDER)
|
hiAlice, gbState.id());
|
||||||
);
|
});
|
||||||
|
|
||||||
System.out.println("SURRENDER GB: "+gbSurrender);
|
final GameResult gameResult = cordaClient.gameBoardSurrender(
|
||||||
|
hiBob, gbState.id());
|
||||||
|
|
||||||
|
assertThat(gameResult.whitePlayerName()).isEqualTo(hiBob.getName());
|
||||||
|
assertThat(gameResult.blackPlayerName()).isEqualTo(hiAlice.getName());
|
||||||
|
assertThat(gameResult.victoryColor()).isEqualByComparingTo(Piece.Color.BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends CordaState> T findByUuid(List<T> statesList, UUID uuid) {
|
private <T extends CordaState> T findByUuid(List<T> statesList, UUID uuid) {
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
package djmil.cordacheckers.contracts;
|
package djmil.cordacheckers.contracts;
|
||||||
|
|
||||||
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
|
||||||
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
|
||||||
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.requireThat;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.states.GameBoardState;
|
||||||
|
import djmil.cordacheckers.states.GameResultState;
|
||||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||||
import net.corda.v5.base.annotations.CordaSerializable;
|
import net.corda.v5.base.annotations.CordaSerializable;
|
||||||
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.Command;
|
||||||
|
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||||
|
|
||||||
public class GameBoardCommand implements Command {
|
public class GameBoardCommand implements Command {
|
||||||
|
|
||||||
@ -54,5 +62,39 @@ public class GameBoardCommand implements Command {
|
|||||||
return this.move;
|
return this.move;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MemberX500Name getInitiator(GameBoardState gameBoardState) {
|
||||||
|
return gameBoardState.getMovePlayerName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemberX500Name getRespondent(GameBoardState gameBoardState) {
|
||||||
|
return gameBoardState.getCounterpartyName(gameBoardState.getMovePlayerName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void validateSurrenderTrx(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);
|
||||||
|
|
||||||
|
requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWhitePlayerName()) == 0, IN_OUT_PARTICIPANTS);
|
||||||
|
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getBlackPlayerName()) == 0, IN_OUT_PARTICIPANTS);
|
||||||
|
}
|
||||||
|
|
||||||
static final String BAD_ACTIONMOVE_CONSTRUCTOR = "Bad constructor for Action.MOVE";
|
static final String BAD_ACTIONMOVE_CONSTRUCTOR = "Bad constructor for Action.MOVE";
|
||||||
|
|
||||||
|
static final String SURRENDER_INPUT_STATE = "SURRENDER command should have exactly one GameBoardState input state";
|
||||||
|
static final String SURRENDER_OUTPUT_STATE = "SURRENDER command should have exactly one GameResultState output state";
|
||||||
|
|
||||||
|
static final String IN_OUT_PARTICIPANTS = "InputState and OutputState participants do not match";
|
||||||
|
|
||||||
|
public static class CommandTypeException extends RuntimeException {
|
||||||
|
public CommandTypeException() {
|
||||||
|
super("Bad GameBoardCommand type");
|
||||||
|
}
|
||||||
|
public CommandTypeException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package djmil.cordacheckers.contracts;
|
package djmil.cordacheckers.contracts;
|
||||||
|
|
||||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputSar;
|
||||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleReferenceSar;
|
||||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleReferenceState;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -12,6 +11,7 @@ import djmil.cordacheckers.states.GameProposalState;
|
|||||||
import net.corda.v5.base.annotations.Suspendable;
|
import net.corda.v5.base.annotations.Suspendable;
|
||||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
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.StateAndRef;
|
||||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||||
|
|
||||||
public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
||||||
@ -33,7 +33,7 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new CordaRuntimeException(UNKNOWN_COMMAND);
|
throw new CordaRuntimeException("UNKNOWN_COMMAND");
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
if (command instanceof GameBoardCommand) {
|
if (command instanceof GameBoardCommand) {
|
||||||
@ -43,7 +43,8 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SURRENDER:
|
case SURRENDER:
|
||||||
break;
|
GameBoardCommand.validateSurrenderTrx(trx);
|
||||||
|
break;
|
||||||
|
|
||||||
case DRAW:
|
case DRAW:
|
||||||
break;
|
break;
|
||||||
@ -51,25 +52,14 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
|||||||
break;
|
break;
|
||||||
case ACCEPT:
|
case ACCEPT:
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
throw new GameBoardCommand.CommandTypeException();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new CordaRuntimeException(UNKNOWN_COMMAND);
|
throw new RuntimeException("Bad utxo command type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
|
||||||
public static GameProposalState getReferanceGameProposalState(UtxoLedgerTransaction trx) {
|
|
||||||
final Command command = getSingleCommand(trx, Command.class);
|
|
||||||
|
|
||||||
if (command instanceof GameProposalCommand && (GameProposalCommand)command == GameProposalCommand.ACCEPT) {
|
|
||||||
return getSingleInputState(trx, GameProposalState.class);
|
|
||||||
} else
|
|
||||||
if (command instanceof GameBoardCommand) {
|
|
||||||
return getSingleReferenceState(trx, GameProposalState.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException(NO_REFERANCE_GAMEPROPOSAL_STATE_FOR_TRXID +trx.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void requireThat(boolean asserted, String errorMessage) {
|
private static void requireThat(boolean asserted, String errorMessage) {
|
||||||
if(!asserted) {
|
if(!asserted) {
|
||||||
@ -78,7 +68,6 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static final String REQUIRE_SINGLE_COMMAND = "Require a single command";
|
static final String REQUIRE_SINGLE_COMMAND = "Require a single command";
|
||||||
static final String UNKNOWN_COMMAND = "Unsupported command";
|
|
||||||
|
|
||||||
static final String SINGLE_STATE_EXPECTED = "Single state expected";
|
static final String SINGLE_STATE_EXPECTED = "Single state expected";
|
||||||
static final String NO_REFERANCE_GAMEPROPOSAL_STATE_FOR_TRXID = "No reference GamePropsal state found for trx.id ";
|
static final String NO_REFERANCE_GAMEPROPOSAL_STATE_FOR_TRXID = "No reference GamePropsal state found for trx.id ";
|
||||||
|
@ -43,12 +43,12 @@ public enum GameProposalCommand implements Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemberX500Name getInitiator(StateAndRef<GameProposalState> utxoGameProposal) {
|
public MemberX500Name getInitiator(StateAndRef<GameProposalState> gameProposalSar) {
|
||||||
return getInitiator(utxoGameProposal.getState().getContractState());
|
return getInitiator(gameProposalSar.getState().getContractState());
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemberX500Name getRespondent(StateAndRef<GameProposalState> utxoGameProposal) {
|
public MemberX500Name getRespondent(StateAndRef<GameProposalState> gameProposalSar) {
|
||||||
return getRespondent(utxoGameProposal.getState().getContractState());
|
return getRespondent(gameProposalSar.getState().getContractState());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void validateCreateTrx(UtxoLedgerTransaction trx) {
|
public static void validateCreateTrx(UtxoLedgerTransaction trx) {
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package djmil.cordacheckers.contracts;
|
||||||
|
|
||||||
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||||
|
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||||
|
|
||||||
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.requireThat;
|
||||||
|
|
||||||
|
public class GameResultContract implements net.corda.v5.ledger.utxo.Contract {
|
||||||
|
|
||||||
|
private final static Logger log = LoggerFactory.getLogger(GameResultContract.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verify(UtxoLedgerTransaction trx) {
|
||||||
|
log.info("GameResultContract.verify() called");
|
||||||
|
|
||||||
|
requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND);
|
||||||
|
final GameBoardCommand command = getSingleCommand(trx, GameBoardCommand.class);
|
||||||
|
|
||||||
|
switch (command.getType()) {
|
||||||
|
case SURRENDER:
|
||||||
|
GameBoardCommand.validateSurrenderTrx(trx);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// case ACCEPT:
|
||||||
|
// break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new CordaRuntimeException(UNKNOWN_COMMAND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final String REQUIRE_SINGLE_COMMAND = "Require a single command";
|
||||||
|
static final String UNKNOWN_COMMAND = "Unsupported command";
|
||||||
|
}
|
@ -5,6 +5,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
import net.corda.v5.ledger.utxo.Command;
|
import net.corda.v5.ledger.utxo.Command;
|
||||||
import net.corda.v5.ledger.utxo.ContractState;
|
import net.corda.v5.ledger.utxo.ContractState;
|
||||||
|
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 UtxoLedgerTransactionUtil {
|
public class UtxoLedgerTransactionUtil {
|
||||||
@ -12,14 +13,18 @@ public class UtxoLedgerTransactionUtil {
|
|||||||
return single(utxoTrx.getCommands(clazz), clazz);
|
return single(utxoTrx.getCommands(clazz), clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T extends ContractState> T getSingleReferenceState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
public static <T extends ContractState> StateAndRef<T> getSingleReferenceSar(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||||
return single(utxoTrx.getReferenceStates(clazz), clazz);
|
return singleSar(utxoTrx.getReferenceStateAndRefs(clazz), clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T extends ContractState> T getSingleInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
public static <T extends ContractState> T getSingleInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||||
return single(utxoTrx.getInputStates(clazz), clazz);
|
return single(utxoTrx.getInputStates(clazz), clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T extends ContractState> StateAndRef<T> getSingleInputSar(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||||
|
return singleSar(utxoTrx.getInputStateAndRefs(clazz), clazz);
|
||||||
|
}
|
||||||
|
|
||||||
public static <T extends ContractState> T getSingleOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
public static <T extends ContractState> T getSingleOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||||
return single(utxoTrx.getOutputStates(clazz), clazz);
|
return single(utxoTrx.getOutputStates(clazz), clazz);
|
||||||
}
|
}
|
||||||
@ -57,6 +62,13 @@ public class UtxoLedgerTransactionUtil {
|
|||||||
.orElseThrow( () -> new IllegalStateException(NO_INSTANCES_OF +clazz.getName()) );
|
.orElseThrow( () -> new IllegalStateException(NO_INSTANCES_OF +clazz.getName()) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static <T extends ContractState> StateAndRef<T> singleSar(List<StateAndRef<T>> list, Class<T> clazz) {
|
||||||
|
return list
|
||||||
|
.stream()
|
||||||
|
.reduce((a, b) -> {throw new IllegalStateException(MULTIPLE_INSTANCES_OF +clazz.getName());})
|
||||||
|
.orElseThrow( () -> new IllegalStateException(NO_INSTANCES_OF +clazz.getName()) );
|
||||||
|
}
|
||||||
|
|
||||||
private static String MULTIPLE_INSTANCES_OF = "Multiple instances of ";
|
private static String MULTIPLE_INSTANCES_OF = "Multiple instances of ";
|
||||||
private static String NO_INSTANCES_OF = "No instances of ";
|
private static String NO_INSTANCES_OF = "No instances of ";
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package djmil.cordacheckers.states;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import net.corda.v5.base.annotations.CordaSerializable;
|
||||||
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
public interface Counterparty {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved;
|
||||||
|
|
||||||
|
public static class NotInvolved extends RuntimeException {
|
||||||
|
public <T> NotInvolved(MemberX500Name myName, Class<T> clazz, UUID uuid) {
|
||||||
|
super(myName +" not involved in " +clazz.getSimpleName() +" UUID " +uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,47 +9,67 @@ import java.util.UUID;
|
|||||||
import djmil.cordacheckers.contracts.GameBoardContract;
|
import djmil.cordacheckers.contracts.GameBoardContract;
|
||||||
import djmil.cordacheckers.states.Piece.Color;
|
import djmil.cordacheckers.states.Piece.Color;
|
||||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||||
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
import net.corda.v5.ledger.utxo.BelongsToContract;
|
import net.corda.v5.ledger.utxo.BelongsToContract;
|
||||||
import net.corda.v5.ledger.utxo.ContractState;
|
import net.corda.v5.ledger.utxo.ContractState;
|
||||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
|
||||||
|
|
||||||
@BelongsToContract(GameBoardContract.class)
|
@BelongsToContract(GameBoardContract.class)
|
||||||
public class GameBoardState implements ContractState {
|
public class GameBoardState implements ContractState, Counterparty {
|
||||||
|
|
||||||
|
private final MemberX500Name whitePlayerName;
|
||||||
|
private final MemberX500Name blackPlayerName;
|
||||||
|
|
||||||
|
private final Piece.Color moveColor;
|
||||||
|
private final Map<Integer, Piece> board;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
Map<Integer, Piece> board;
|
private final UUID id;
|
||||||
Piece.Color moveColor;
|
private final List<PublicKey> participants;
|
||||||
String message;
|
|
||||||
UUID id;
|
|
||||||
List<PublicKey> participants;
|
|
||||||
|
|
||||||
public GameBoardState(
|
public GameBoardState(
|
||||||
StateAndRef<GameProposalState> utxoGameBoard
|
GameProposalState gameBoard
|
||||||
) {
|
) {
|
||||||
this.board = new LinkedHashMap<Integer, Piece>(initialBoard);
|
this.whitePlayerName = gameBoard.getWhitePlayerName();
|
||||||
|
this.blackPlayerName = gameBoard.getBlackPlayerName();
|
||||||
|
|
||||||
|
// Initial GameBoard state
|
||||||
this.moveColor = Piece.Color.WHITE;
|
this.moveColor = Piece.Color.WHITE;
|
||||||
|
this.board = new LinkedHashMap<Integer, Piece>(initialBoard);
|
||||||
this.message = null;
|
this.message = null;
|
||||||
|
|
||||||
this.id = UUID.randomUUID();
|
this.id = UUID.randomUUID();
|
||||||
this.participants = utxoGameBoard.getState().getContractState().getParticipants();
|
this.participants = gameBoard.getParticipants();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConstructorForDeserialization
|
@ConstructorForDeserialization
|
||||||
public GameBoardState(Map<Integer, Piece> board, Color moveColor, String message, UUID id,
|
public GameBoardState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName,
|
||||||
List<PublicKey> participants) {
|
Color moveColor, Map<Integer, Piece> board, String message,
|
||||||
this.board = board;
|
UUID id, List<PublicKey> participants) {
|
||||||
|
this.whitePlayerName = whitePlayerName;
|
||||||
|
this.blackPlayerName = blackPlayerName;
|
||||||
this.moveColor = moveColor;
|
this.moveColor = moveColor;
|
||||||
|
this.board = board;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.participants = participants;
|
this.participants = participants;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Integer, Piece> getBoard() {
|
public MemberX500Name getWhitePlayerName() {
|
||||||
return board;
|
return whitePlayerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemberX500Name getBlackPlayerName() {
|
||||||
|
return blackPlayerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Piece.Color getMoveColor() {
|
public Piece.Color getMoveColor() {
|
||||||
return moveColor;
|
return moveColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<Integer, Piece> getBoard() {
|
||||||
|
return board;
|
||||||
|
}
|
||||||
|
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
@ -62,6 +82,34 @@ public class GameBoardState implements ContractState {
|
|||||||
return participants;
|
return participants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
|
||||||
|
if (whitePlayerName.compareTo(myName) == 0)
|
||||||
|
return blackPlayerName;
|
||||||
|
|
||||||
|
if (blackPlayerName.compareTo(myName) == 0)
|
||||||
|
return whitePlayerName;
|
||||||
|
|
||||||
|
throw new Counterparty.NotInvolved(myName, GameBoardState.class, this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 MemberX500Name getMovePlayerName() {
|
||||||
|
switch (moveColor) {
|
||||||
|
case WHITE:
|
||||||
|
return whitePlayerName;
|
||||||
|
case BLACK:
|
||||||
|
return blackPlayerName;
|
||||||
|
default:
|
||||||
|
throw new Piece.Color.UnknownException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final static Map<Integer, Piece> initialBoard = Map.ofEntries(
|
public final static Map<Integer, Piece> initialBoard = Map.ofEntries(
|
||||||
// Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php
|
// Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php
|
||||||
Map.entry( 1, new Piece(Piece.Color.BLACK, Piece.Type.MAN)),
|
Map.entry( 1, new Piece(Piece.Color.BLACK, Piece.Type.MAN)),
|
||||||
|
@ -11,14 +11,14 @@ import net.corda.v5.ledger.utxo.BelongsToContract;
|
|||||||
import net.corda.v5.ledger.utxo.ContractState;
|
import net.corda.v5.ledger.utxo.ContractState;
|
||||||
|
|
||||||
@BelongsToContract(GameProposalContract.class)
|
@BelongsToContract(GameProposalContract.class)
|
||||||
public class GameProposalState implements ContractState {
|
public class GameProposalState implements ContractState, Counterparty {
|
||||||
|
|
||||||
MemberX500Name issuer;
|
private final MemberX500Name issuer;
|
||||||
MemberX500Name acquier;
|
private final MemberX500Name acquier;
|
||||||
Piece.Color acquierColor;
|
private final Piece.Color acquierColor;
|
||||||
String message;
|
private final String message;
|
||||||
UUID id;
|
private final UUID id;
|
||||||
List<PublicKey> participants;
|
private final List<PublicKey> participants;
|
||||||
|
|
||||||
@ConstructorForDeserialization
|
@ConstructorForDeserialization
|
||||||
public GameProposalState(
|
public GameProposalState(
|
||||||
@ -69,20 +69,15 @@ public class GameProposalState implements ContractState {
|
|||||||
return acquierColor == Piece.Color.BLACK ? getAcquier() : getIssuer();
|
return acquierColor == Piece.Color.BLACK ? getAcquier() : getIssuer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemberX500Name getOpponentName(MemberX500Name myName) {
|
@Override
|
||||||
|
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
|
||||||
if (issuer.compareTo(myName) == 0)
|
if (issuer.compareTo(myName) == 0)
|
||||||
return acquier;
|
return acquier;
|
||||||
|
|
||||||
if (acquier.compareTo(myName) == 0)
|
if (acquier.compareTo(myName) == 0)
|
||||||
return issuer;
|
return issuer;
|
||||||
|
|
||||||
throw new RuntimeException(myName +" seems to be not involved in " + id +" game");
|
throw new Counterparty.NotInvolved(myName, GameProposalState.class, this.id);
|
||||||
}
|
|
||||||
|
|
||||||
public Piece.Color getOpponentColor(MemberX500Name myName) {
|
|
||||||
final MemberX500Name opponentName = getOpponentName(myName);
|
|
||||||
|
|
||||||
return getWhitePlayerName().compareTo(opponentName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
package djmil.cordacheckers.states;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.contracts.GameResultContract;
|
||||||
|
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||||
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
|
import net.corda.v5.ledger.utxo.BelongsToContract;
|
||||||
|
import net.corda.v5.ledger.utxo.ContractState;
|
||||||
|
|
||||||
|
@BelongsToContract(GameResultContract.class)
|
||||||
|
public class GameResultState implements ContractState, Counterparty {
|
||||||
|
|
||||||
|
private final MemberX500Name whitePlayerName;
|
||||||
|
private final MemberX500Name blackPlayerName;
|
||||||
|
|
||||||
|
private final Piece.Color victoryColor;
|
||||||
|
|
||||||
|
private final UUID id;
|
||||||
|
private final List<PublicKey> participants;
|
||||||
|
|
||||||
|
@ConstructorForDeserialization
|
||||||
|
public GameResultState(MemberX500Name whitePlayerName, MemberX500Name blackPlayerName, Piece.Color victoryColor,
|
||||||
|
UUID id, List<PublicKey> participants) {
|
||||||
|
this.whitePlayerName = whitePlayerName;
|
||||||
|
this.blackPlayerName = blackPlayerName;
|
||||||
|
this.victoryColor = victoryColor;
|
||||||
|
this.id = id;
|
||||||
|
this.participants = participants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameResultState(GameBoardState stateGameBoard, Piece.Color victoryColor) {
|
||||||
|
this.whitePlayerName = stateGameBoard.getWhitePlayerName();
|
||||||
|
this.blackPlayerName = stateGameBoard.getBlackPlayerName();
|
||||||
|
this.victoryColor = victoryColor;
|
||||||
|
|
||||||
|
this.id = UUID.randomUUID();
|
||||||
|
this.participants = stateGameBoard.getParticipants();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemberX500Name getWhitePlayerName() {
|
||||||
|
return whitePlayerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemberX500Name getBlackPlayerName() {
|
||||||
|
return blackPlayerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Piece.Color getVictoryColor() {
|
||||||
|
return victoryColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PublicKey> getParticipants() {
|
||||||
|
return participants;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved {
|
||||||
|
if (whitePlayerName.compareTo(myName) == 0)
|
||||||
|
return blackPlayerName;
|
||||||
|
|
||||||
|
if (blackPlayerName.compareTo(myName) == 0)
|
||||||
|
return whitePlayerName;
|
||||||
|
|
||||||
|
throw new Counterparty.NotInvolved(myName, GameResultState.class, this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -9,17 +9,34 @@ public class Piece {
|
|||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
public enum Type {
|
public enum Type {
|
||||||
MAN,
|
MAN,
|
||||||
KING,
|
KING;
|
||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
public enum Color {
|
public enum Color {
|
||||||
WHITE,
|
WHITE,
|
||||||
BLACK,
|
BLACK;
|
||||||
|
|
||||||
|
public static Color oppositOf(Color color) {
|
||||||
|
switch (color) {
|
||||||
|
case WHITE:
|
||||||
|
return BLACK;
|
||||||
|
case BLACK:
|
||||||
|
return WHITE;
|
||||||
|
default:
|
||||||
|
throw new UnknownException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UnknownException extends RuntimeException {
|
||||||
|
public UnknownException() {
|
||||||
|
super("Unknown Color value");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Color color;
|
private final Color color;
|
||||||
Type type;
|
private final Type type;
|
||||||
|
|
||||||
@ConstructorForDeserialization
|
@ConstructorForDeserialization
|
||||||
public Piece(Color color, Type type) {
|
public Piece(Color color, Type type) {
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
package djmil.cordacheckers.gameboard;
|
package djmil.cordacheckers.gameboard;
|
||||||
|
|
||||||
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||||
|
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -7,16 +14,19 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import djmil.cordacheckers.FlowResult;
|
import djmil.cordacheckers.FlowResult;
|
||||||
import djmil.cordacheckers.contracts.GameBoardCommand;
|
import djmil.cordacheckers.contracts.GameBoardCommand;
|
||||||
import djmil.cordacheckers.contracts.GameBoardContract;
|
import djmil.cordacheckers.contracts.GameBoardCommand.CommandTypeException;
|
||||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
import djmil.cordacheckers.gameresult.GameResultView;
|
||||||
import djmil.cordacheckers.states.GameBoardState;
|
import djmil.cordacheckers.states.GameBoardState;
|
||||||
import djmil.cordacheckers.states.GameProposalState;
|
import djmil.cordacheckers.states.GameResultState;
|
||||||
|
import djmil.cordacheckers.states.Piece;
|
||||||
import net.corda.v5.application.flows.ClientRequestBody;
|
import net.corda.v5.application.flows.ClientRequestBody;
|
||||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||||
import net.corda.v5.application.flows.CordaInject;
|
import net.corda.v5.application.flows.CordaInject;
|
||||||
import net.corda.v5.application.flows.FlowEngine;
|
import net.corda.v5.application.flows.InitiatingFlow;
|
||||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||||
import net.corda.v5.application.membership.MemberLookup;
|
import net.corda.v5.application.membership.MemberLookup;
|
||||||
|
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.annotations.Suspendable;
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
import net.corda.v5.crypto.SecureHash;
|
import net.corda.v5.crypto.SecureHash;
|
||||||
@ -26,9 +36,7 @@ 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.UtxoTransactionBuilder;
|
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
|
||||||
|
|
||||||
import java.time.Duration;
|
@InitiatingFlow(protocol = "game-board")
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
public class CommandFlow implements ClientStartableFlow {
|
public class CommandFlow implements ClientStartableFlow {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(CommandFlow.class);
|
private final static Logger log = LoggerFactory.getLogger(CommandFlow.class);
|
||||||
@ -40,37 +48,26 @@ public class CommandFlow implements ClientStartableFlow {
|
|||||||
public UtxoLedgerService utxoLedgerService;
|
public UtxoLedgerService utxoLedgerService;
|
||||||
|
|
||||||
@CordaInject
|
@CordaInject
|
||||||
public FlowEngine flowEngine;
|
public MemberLookup memberLookup;
|
||||||
|
|
||||||
@CordaInject
|
@CordaInject
|
||||||
public MemberLookup memberLookup;
|
public FlowMessaging flowMessaging;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Suspendable
|
@Suspendable
|
||||||
public String call(ClientRequestBody requestBody) {
|
public String call(ClientRequestBody requestBody) {
|
||||||
|
log.info("GameBoardCommandFlow started");
|
||||||
try {
|
try {
|
||||||
final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class);
|
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 StateAndRef<GameBoardState> utxoSarGameBoard = findUtxoGameBoard(args.getGameBoardUuid());
|
||||||
|
|
||||||
|
final SecureHash trxId = doTransaction(args.getCommand(), utxoSarGameBoard);
|
||||||
|
final Object trxResult = getTrxResult(trxId);
|
||||||
|
|
||||||
// final SecureHash trxId = this.flowEngine
|
return new FlowResult(trxResult, trxId)
|
||||||
// .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);
|
.toJsonEncodedString(jsonMarshallingService);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +
|
log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +
|
||||||
" because: " + e.getMessage());
|
" because: " + e.getMessage());
|
||||||
@ -79,7 +76,7 @@ log.info("GameBoardCommandFlow: findUnconsumedGameBoardState");
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private StateAndRef<GameBoardState> findUnconsumedGameBoardState (UUID gameBoardUuid) {
|
StateAndRef<GameBoardState> findUtxoGameBoard (UUID gameBoardUuid) {
|
||||||
/*
|
/*
|
||||||
* Get list of all unconsumed aka 'active' GameProposalStates, then filter by UUID.
|
* 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
|
* Note, this is an inefficient way to perform this operation if there are a large
|
||||||
@ -93,43 +90,111 @@ log.info("GameBoardCommandFlow: findUnconsumedGameBoardState");
|
|||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Suspendable
|
@Suspendable
|
||||||
// private UtxoSignedTransaction prepareSignedTransaction(
|
SecureHash doTransaction(
|
||||||
// GameProposalCommand command,
|
GameBoardCommand command,
|
||||||
// StateAndRef<GameProposalState> utxoGameProposal
|
StateAndRef<GameBoardState> utxoSarGameBoard
|
||||||
// ) {
|
) {
|
||||||
// UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder()
|
final GameBoardState stateGameBoard = utxoSarGameBoard.getState().getContractState();
|
||||||
// .setNotary(utxoGameProposal.getState().getNotaryName())
|
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||||
// .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
final MemberX500Name opponentName = stateGameBoard.getCounterpartyName(myName);
|
||||||
// .addInputState(utxoGameProposal.getRef())
|
|
||||||
// .addCommand(command)
|
|
||||||
// .addSignatories(utxoGameProposal.getState().getContractState().getParticipants());
|
|
||||||
|
|
||||||
// if (command == GameProposalCommand.ACCEPT) {
|
UtxoTransactionBuilder trxBuilder = utxoLedgerService.createTransactionBuilder()
|
||||||
// trxBuilder = trxBuilder
|
.setNotary(utxoSarGameBoard.getState().getNotaryName())
|
||||||
// .addOutputState(new GameBoardState(utxoGameProposal));
|
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||||
// //A state cannot be both an input and a reference input in the same transaction
|
.addInputState(utxoSarGameBoard.getRef())
|
||||||
// //.addReferenceState(utxoGameProposal.getRef());
|
.addCommand(command)
|
||||||
// }
|
.addSignatories(stateGameBoard.getParticipants());
|
||||||
|
|
||||||
// return trxBuilder.toSignedTransaction();
|
switch (command.getType()) {
|
||||||
// }
|
case SURRENDER:
|
||||||
|
case ACCEPT:
|
||||||
|
final Piece.Color winnerColor = winnerColor(command, stateGameBoard, myName);
|
||||||
|
trxBuilder = trxBuilder
|
||||||
|
.addOutputState( new GameResultState(stateGameBoard, winnerColor) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new CommandTypeException("GameBoard.CommandFlow doTransaction()");
|
||||||
|
}
|
||||||
|
|
||||||
|
return commit(
|
||||||
|
trxBuilder.toSignedTransaction(),
|
||||||
|
opponentName);
|
||||||
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private GameBoardView prepareGameBoardView(StateAndRef<GameBoardState> stateAndRef) {
|
Piece.Color winnerColor(GameBoardCommand command, GameBoardState stateGameBoard, MemberX500Name myName) {
|
||||||
|
|
||||||
|
switch (command.getType()) {
|
||||||
|
case SURRENDER:
|
||||||
|
return stateGameBoard.getCounterpartyColor(myName);
|
||||||
|
|
||||||
|
case ACCEPT:
|
||||||
|
//stateGameBoard.getMoveColor();
|
||||||
|
throw new RuntimeException("Unimplemented");
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new CommandTypeException("GameBoard.CommandFlow winnerColor()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
SecureHash commit(UtxoSignedTransaction candidateTrx, MemberX500Name counterpartyName) {
|
||||||
|
log.info("About to commit " +candidateTrx.getId());
|
||||||
|
|
||||||
|
final FlowSession session = flowMessaging.initiateFlow(counterpartyName);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 = utxoLedgerService
|
||||||
|
.finalize(candidateTrx, sessionsList)
|
||||||
|
.getTransaction()
|
||||||
|
.getId();
|
||||||
|
|
||||||
|
log.info("GameBoard utxo trx id " +trxId);
|
||||||
|
return trxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
Object getTrxResult(SecureHash trxId) {
|
||||||
|
final UtxoLedgerTransaction utxoTrx = utxoLedgerService
|
||||||
|
.findLedgerTransaction(trxId);
|
||||||
|
|
||||||
|
final var command = getSingleCommand(utxoTrx, GameBoardCommand.class);
|
||||||
|
|
||||||
|
switch (command.getType()) {
|
||||||
|
case SURRENDER:
|
||||||
|
case ACCEPT:
|
||||||
|
return viewGameResult(utxoTrx);
|
||||||
|
|
||||||
|
case MOVE:
|
||||||
|
case VICTORY: // request, shall be accepted by opponent
|
||||||
|
case DRAW: // request, shall be accepted by opponent
|
||||||
|
return viewGameBoard(utxoTrx);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new CommandTypeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
GameResultView viewGameResult(UtxoLedgerTransaction utxoGameResult) {
|
||||||
|
final GameResultState grState = getSingleOutputState(utxoGameResult, GameResultState.class);
|
||||||
|
|
||||||
|
return new GameResultView(grState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
GameBoardView viewGameBoard(UtxoLedgerTransaction utxoGameBoard) {
|
||||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||||
|
|
||||||
final SecureHash trxId = stateAndRef.getRef().getTransactionId();
|
return new GameBoardView(utxoGameBoard, myName);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
package djmil.cordacheckers.gameboard;
|
||||||
|
|
||||||
|
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.contracts.GameBoardCommand;
|
||||||
|
import djmil.cordacheckers.contracts.GameBoardCommand.CommandTypeException;
|
||||||
|
import djmil.cordacheckers.states.GameBoardState;
|
||||||
|
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.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 CommandResponderFlow implements ResponderFlow {
|
||||||
|
|
||||||
|
private final static Logger log = LoggerFactory.getLogger(CommandResponderFlow.class);
|
||||||
|
|
||||||
|
@CordaInject
|
||||||
|
public MemberLookup memberLookup;
|
||||||
|
|
||||||
|
@CordaInject
|
||||||
|
public UtxoLedgerService utxoLedgerService;
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
@Override
|
||||||
|
public void call(FlowSession session) {
|
||||||
|
try {
|
||||||
|
UtxoTransactionValidator txValidator = utxoGameBoard -> {
|
||||||
|
final var command = getSingleCommand(utxoGameBoard, GameBoardCommand.class);
|
||||||
|
final var stateGameBoard = getGameBoardState(utxoGameBoard, command);
|
||||||
|
|
||||||
|
checkParticipants(session, stateGameBoard, command);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Other checks / actions ?
|
||||||
|
*/
|
||||||
|
|
||||||
|
log.info("Verified the transaction - " + utxoGameBoard.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
|
||||||
|
GameBoardState getGameBoardState(UtxoLedgerTransaction utxoGameBoard, GameBoardCommand command) {
|
||||||
|
switch (command.getType()) {
|
||||||
|
case SURRENDER:
|
||||||
|
case ACCEPT:
|
||||||
|
return getSingleInputState(utxoGameBoard, GameBoardState.class);
|
||||||
|
|
||||||
|
case MOVE:
|
||||||
|
case VICTORY:
|
||||||
|
case DRAW:
|
||||||
|
return getSingleOutputState(utxoGameBoard, GameBoardState.class);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new CommandTypeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
void checkParticipants(FlowSession session, GameBoardState stateGameBoard, GameBoardCommand command) {
|
||||||
|
final var myName = memberLookup.myInfo().getName();
|
||||||
|
final var counterpartyName = session.getCounterparty();
|
||||||
|
|
||||||
|
if (command.getRespondent(stateGameBoard).compareTo(myName) != 0)
|
||||||
|
throw new CordaRuntimeException("Bad respondent");
|
||||||
|
|
||||||
|
if (command.getInitiator(stateGameBoard).compareTo(counterpartyName) !=0)
|
||||||
|
throw new CordaRuntimeException("Bad initiator");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,97 +0,0 @@
|
|||||||
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);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,10 +4,9 @@ import java.util.Map;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import djmil.cordacheckers.contracts.GameBoardCommand;
|
import djmil.cordacheckers.contracts.GameBoardCommand;
|
||||||
import djmil.cordacheckers.contracts.GameBoardContract;
|
|
||||||
import djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil;
|
import djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil;
|
||||||
|
import djmil.cordacheckers.states.Counterparty.NotInvolved;
|
||||||
import djmil.cordacheckers.states.GameBoardState;
|
import djmil.cordacheckers.states.GameBoardState;
|
||||||
import djmil.cordacheckers.states.GameProposalState;
|
|
||||||
import djmil.cordacheckers.states.Piece;
|
import djmil.cordacheckers.states.Piece;
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||||
@ -19,7 +18,7 @@ public class GameBoardView {
|
|||||||
public final Piece.Color opponentColor;
|
public final Piece.Color opponentColor;
|
||||||
public final Boolean opponentMove;
|
public final Boolean opponentMove;
|
||||||
public final Map<Integer, Piece> board;
|
public final Map<Integer, Piece> board;
|
||||||
public /*final*/ GameBoardCommand previousCommand;
|
public final GameBoardCommand previousCommand;
|
||||||
public final String message;
|
public final String message;
|
||||||
public final UUID id;
|
public final UUID id;
|
||||||
|
|
||||||
@ -36,17 +35,14 @@ public class GameBoardView {
|
|||||||
|
|
||||||
// A view from a perspective of a concrete player, on a ledger transaction that has
|
// A view from a perspective of a concrete player, on a ledger transaction that has
|
||||||
// produced new GameBoardState
|
// produced new GameBoardState
|
||||||
public GameBoardView(MemberX500Name myName, UtxoLedgerTransaction utxoGameBoard) {
|
public GameBoardView(UtxoLedgerTransaction utxoGameBoard, MemberX500Name myName) throws NotInvolved {
|
||||||
|
|
||||||
final GameProposalState referanceGameProposal = GameBoardContract
|
|
||||||
.getReferanceGameProposalState(utxoGameBoard);
|
|
||||||
|
|
||||||
this.opponentName = referanceGameProposal.getOpponentName(myName).getCommonName();
|
|
||||||
this.opponentColor = referanceGameProposal.getOpponentColor(myName);
|
|
||||||
|
|
||||||
final GameBoardState stateGameBoard = UtxoLedgerTransactionUtil
|
final GameBoardState stateGameBoard = UtxoLedgerTransactionUtil
|
||||||
.getSingleOutputState(utxoGameBoard, GameBoardState.class);
|
.getSingleOutputState(utxoGameBoard, GameBoardState.class);
|
||||||
|
|
||||||
|
this.opponentName = stateGameBoard.getCounterpartyName(myName).getCommonName();
|
||||||
|
this.opponentColor = stateGameBoard.getCounterpartyColor(myName);
|
||||||
|
|
||||||
this.opponentMove = this.opponentColor == stateGameBoard.getMoveColor();
|
this.opponentMove = this.opponentColor == stateGameBoard.getMoveColor();
|
||||||
this.board = stateGameBoard.getBoard();
|
this.board = stateGameBoard.getBoard();
|
||||||
this.message = stateGameBoard.getMessage();
|
this.message = stateGameBoard.getMessage();
|
||||||
@ -54,7 +50,7 @@ public class GameBoardView {
|
|||||||
|
|
||||||
this.previousCommand = UtxoLedgerTransactionUtil
|
this.previousCommand = UtxoLedgerTransactionUtil
|
||||||
.getOptionalCommand(utxoGameBoard, GameBoardCommand.class)
|
.getOptionalCommand(utxoGameBoard, GameBoardCommand.class)
|
||||||
.orElseGet(() -> null);
|
.orElseGet(() -> null); // there is no previous command for GameProposal.Accept case
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,8 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import djmil.cordacheckers.FlowResult;
|
import djmil.cordacheckers.FlowResult;
|
||||||
import djmil.cordacheckers.contracts.GameBoardContract;
|
|
||||||
import djmil.cordacheckers.states.GameBoardState;
|
import djmil.cordacheckers.states.GameBoardState;
|
||||||
import djmil.cordacheckers.states.GameProposalState;
|
import djmil.cordacheckers.states.Counterparty.NotInvolved;
|
||||||
import net.corda.v5.application.flows.ClientRequestBody;
|
import net.corda.v5.application.flows.ClientRequestBody;
|
||||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||||
import net.corda.v5.application.flows.CordaInject;
|
import net.corda.v5.application.flows.CordaInject;
|
||||||
@ -40,14 +39,11 @@ public class ListFlow implements ClientStartableFlow {
|
|||||||
public String call(ClientRequestBody requestBody) {
|
public String call(ClientRequestBody requestBody) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final var unconsumedGameBoardList = utxoLedgerService
|
final var utxoGameBoardList = utxoLedgerService
|
||||||
.findUnconsumedStatesByType(GameBoardState.class);
|
.findUnconsumedStatesByType(GameBoardState.class);
|
||||||
|
|
||||||
List<GameBoardView> res = new LinkedList<GameBoardView>();
|
|
||||||
for (StateAndRef<GameBoardState> gbState :unconsumedGameBoardList) {
|
|
||||||
// NOTE: lambda operations (aka .map) can not be used with UtxoLedgerService instances
|
// NOTE: lambda operations (aka .map) can not be used with UtxoLedgerService instances
|
||||||
res.add(prepareGameBoardView(gbState));
|
|
||||||
}
|
final List<GameBoardView> res = prepareGameBoardViewList(utxoGameBoardList);
|
||||||
|
|
||||||
return new FlowResult(res)
|
return new FlowResult(res)
|
||||||
.toJsonEncodedString(jsonMarshallingService);
|
.toJsonEncodedString(jsonMarshallingService);
|
||||||
@ -59,15 +55,29 @@ public class ListFlow implements ClientStartableFlow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private GameBoardView prepareGameBoardView(StateAndRef<GameBoardState> stateAndRef) {
|
private List<GameBoardView> prepareGameBoardViewList(List<StateAndRef<GameBoardState>> utxoGamaBoardList) {
|
||||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||||
|
List<GameBoardView> res = new LinkedList<GameBoardView>();
|
||||||
|
|
||||||
final SecureHash trxId = stateAndRef.getRef().getTransactionId();
|
for (StateAndRef<GameBoardState> sarGameBoard :utxoGamaBoardList) {
|
||||||
|
try {
|
||||||
|
res.add(prepareGameBoardView(sarGameBoard, myName));
|
||||||
|
} catch (NotInvolved e) {
|
||||||
|
log.warn(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
private GameBoardView prepareGameBoardView(StateAndRef<GameBoardState> sarGameBoard, MemberX500Name myName) throws NotInvolved {
|
||||||
|
final SecureHash trxId = sarGameBoard.getRef().getTransactionId();
|
||||||
|
|
||||||
final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService
|
final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService
|
||||||
.findLedgerTransaction(trxId);
|
.findLedgerTransaction(trxId);
|
||||||
|
|
||||||
return new GameBoardView(myName, utxoGameBoard);
|
return new GameBoardView(utxoGameBoard, myName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,11 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import djmil.cordacheckers.FlowResult;
|
import djmil.cordacheckers.FlowResult;
|
||||||
import djmil.cordacheckers.contracts.GameBoardCommand;
|
|
||||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
import djmil.cordacheckers.contracts.GameProposalCommand;
|
||||||
import djmil.cordacheckers.gameboard.GameBoardView;
|
import djmil.cordacheckers.gameboard.GameBoardView;
|
||||||
import djmil.cordacheckers.states.GameBoardState;
|
import djmil.cordacheckers.states.GameBoardState;
|
||||||
import djmil.cordacheckers.states.GameProposalState;
|
import djmil.cordacheckers.states.GameProposalState;
|
||||||
|
import djmil.cordacheckers.states.Counterparty.NotInvolved;
|
||||||
import net.corda.v5.application.flows.ClientRequestBody;
|
import net.corda.v5.application.flows.ClientRequestBody;
|
||||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||||
import net.corda.v5.application.flows.CordaInject;
|
import net.corda.v5.application.flows.CordaInject;
|
||||||
@ -52,21 +52,21 @@ public class CommandFlow implements ClientStartableFlow {
|
|||||||
final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class);
|
final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class);
|
||||||
final GameProposalCommand command = args.getCommand();
|
final GameProposalCommand command = args.getCommand();
|
||||||
|
|
||||||
final StateAndRef<GameProposalState> gpStateAndRef = findUnconsumedGameProposalState(args.getGameProposalUuid());
|
final StateAndRef<GameProposalState> utxoSarGameProposal = findUtxoGameProposal(args.getGameProposalUuid());
|
||||||
|
|
||||||
final UtxoSignedTransaction trxCandidate = prepareSignedTransaction(command, gpStateAndRef);
|
final UtxoSignedTransaction utxoDraft = prepareSignedTransaction(command, utxoSarGameProposal);
|
||||||
|
|
||||||
final SecureHash trxId = this.flowEngine
|
final SecureHash utxoTrxId = this.flowEngine
|
||||||
.subFlow( new CommitSubFlow(trxCandidate, command.getRespondent(gpStateAndRef)) );
|
.subFlow( new CommitSubFlow(utxoDraft, command.getRespondent(utxoSarGameProposal)) );
|
||||||
|
|
||||||
if (command == GameProposalCommand.ACCEPT) {
|
if (command == GameProposalCommand.ACCEPT) {
|
||||||
final GameBoardView gbView = prepareGameBoardView(trxId);
|
final GameBoardView viewGameBoard = prepareGameBoardView(utxoTrxId);
|
||||||
|
|
||||||
return new FlowResult(gbView, trxId)
|
return new FlowResult(viewGameBoard, utxoTrxId)
|
||||||
.toJsonEncodedString(jsonMarshallingService);
|
.toJsonEncodedString(jsonMarshallingService);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FlowResult(command+"ED", trxId) // REJECT+ED
|
return new FlowResult(command+"ED", utxoTrxId) // REJECT+ED
|
||||||
.toJsonEncodedString(jsonMarshallingService);
|
.toJsonEncodedString(jsonMarshallingService);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
@ -77,7 +77,7 @@ public class CommandFlow implements ClientStartableFlow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private StateAndRef<GameProposalState> findUnconsumedGameProposalState (UUID gpUuid) {
|
private StateAndRef<GameProposalState> findUtxoGameProposal (UUID uuidGameProposal) {
|
||||||
/*
|
/*
|
||||||
* Get list of all unconsumed aka 'active' GameProposalStates, then filter by UUID.
|
* 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
|
* Note, this is an inefficient way to perform this operation if there are a large
|
||||||
@ -86,7 +86,7 @@ public class CommandFlow implements ClientStartableFlow {
|
|||||||
return this.utxoLedgerService
|
return this.utxoLedgerService
|
||||||
.findUnconsumedStatesByType(GameProposalState.class)
|
.findUnconsumedStatesByType(GameProposalState.class)
|
||||||
.stream()
|
.stream()
|
||||||
.filter(sar -> sar.getState().getContractState().getId().equals(gpUuid))
|
.filter(sar -> sar.getState().getContractState().getId().equals(uuidGameProposal))
|
||||||
.reduce((a, b) -> {throw new IllegalStateException("Multiple states: " +a +", " +b);})
|
.reduce((a, b) -> {throw new IllegalStateException("Multiple states: " +a +", " +b);})
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
@ -94,33 +94,31 @@ public class CommandFlow implements ClientStartableFlow {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
private UtxoSignedTransaction prepareSignedTransaction(
|
private UtxoSignedTransaction prepareSignedTransaction(
|
||||||
GameProposalCommand command,
|
GameProposalCommand command,
|
||||||
StateAndRef<GameProposalState> gpStateAndRef
|
StateAndRef<GameProposalState> sarGameProposal
|
||||||
) {
|
) {
|
||||||
UtxoTransactionBuilder trxBuilder = utxoLedgerService.createTransactionBuilder()
|
UtxoTransactionBuilder trxBuilder = utxoLedgerService.createTransactionBuilder()
|
||||||
.setNotary(gpStateAndRef.getState().getNotaryName())
|
.setNotary(sarGameProposal.getState().getNotaryName())
|
||||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||||
.addInputState(gpStateAndRef.getRef())
|
.addInputState(sarGameProposal.getRef())
|
||||||
.addCommand(command)
|
.addCommand(command)
|
||||||
.addSignatories(gpStateAndRef.getState().getContractState().getParticipants());
|
.addSignatories(sarGameProposal.getState().getContractState().getParticipants());
|
||||||
|
|
||||||
if (command == GameProposalCommand.ACCEPT) {
|
if (command == GameProposalCommand.ACCEPT) {
|
||||||
trxBuilder = trxBuilder
|
trxBuilder = trxBuilder
|
||||||
.addOutputState(new GameBoardState(gpStateAndRef));
|
.addOutputState(new GameBoardState(sarGameProposal.getState().getContractState()));
|
||||||
//A state cannot be both an input and a reference input in the same transaction
|
|
||||||
//.addReferenceState(utxoGameProposal.getRef());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return trxBuilder.toSignedTransaction();
|
return trxBuilder.toSignedTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private GameBoardView prepareGameBoardView(SecureHash gbUtxoTrxId) {
|
private GameBoardView prepareGameBoardView(SecureHash utxoTrxId) throws NotInvolved {
|
||||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||||
|
|
||||||
final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService
|
final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService
|
||||||
.findLedgerTransaction(gbUtxoTrxId);
|
.findLedgerTransaction(utxoTrxId);
|
||||||
|
|
||||||
return new GameBoardView(myName, utxoGameBoard);
|
return new GameBoardView(utxoGameBoard, myName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ 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.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||||
|
|
||||||
@InitiatedBy(protocol = "game-proposal")
|
@InitiatedBy(protocol = "game-proposal")
|
||||||
public class CommitResponderFlow implements ResponderFlow {
|
public class CommitResponderFlow implements ResponderFlow {
|
||||||
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package djmil.cordacheckers.gameresult;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.states.GameResultState;
|
||||||
|
import djmil.cordacheckers.states.Piece;
|
||||||
|
|
||||||
|
public class GameResultView {
|
||||||
|
public final String whitePlayerName;
|
||||||
|
public final String blackPlayerName;
|
||||||
|
public final Piece.Color victoryColor;
|
||||||
|
public final UUID id;
|
||||||
|
|
||||||
|
// Serialisation service requires a default constructor
|
||||||
|
public GameResultView() {
|
||||||
|
this.whitePlayerName = null;
|
||||||
|
this.blackPlayerName = null;
|
||||||
|
this.victoryColor = null;
|
||||||
|
this.id = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameResultView(GameResultState gameResultState) {
|
||||||
|
this.whitePlayerName = gameResultState.getWhitePlayerName().getCommonName();
|
||||||
|
this.blackPlayerName = gameResultState.getBlackPlayerName().getCommonName();
|
||||||
|
this.victoryColor = gameResultState.getVictoryColor();
|
||||||
|
this.id = gameResultState.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user