Major refactoring
- abstract GameState as a base class for + GameProposale + GameBoard + GameResult - GameView persepective approach as result significant downplay of DTO classes - single GameCommand - single transaction Commit flow with dedicated responder - extensive use of orchestartion code flow pattern
This commit is contained in:
parent
dc702f7584
commit
959ea0051d
@ -14,31 +14,21 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameResult;
|
||||
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
||||
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.RequestBody;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalListRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardCommandReq;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardResGameResult;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardListRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardResGameBoard;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandAcceptRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandReq;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Req;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Rsp;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameBoardMove;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.RspGameState;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.RspGameStateList;
|
||||
|
||||
@Service
|
||||
public class CordaClient {
|
||||
@ -69,205 +59,110 @@ public class CordaClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain list of unconsumed (active) GameProposals
|
||||
* @param myHoldingIdentity
|
||||
* @return GameProposals list in JSON form
|
||||
* @param holdingIdentity
|
||||
* @return list of unconsumed (active) GameStateViews
|
||||
*/
|
||||
public List<GameProposal> gameProposalList(HoldingIdentity myHoldingIdentity) {
|
||||
|
||||
public List<GameState> gameStateList(HoldingIdentity holdingIdentity) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gp.list-" + UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameproposal.ListFlow",
|
||||
new Empty()
|
||||
);
|
||||
"gs.list-" + UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gamestate.ListFlow",
|
||||
new Req());
|
||||
|
||||
final GameProposalListRes listFlowResult = cordaFlowExecute(
|
||||
myHoldingIdentity,
|
||||
requestBody,
|
||||
GameProposalListRes.class
|
||||
);
|
||||
|
||||
if (listFlowResult.failureStatus() != null) {
|
||||
System.out.println("GameProposalCreateFlow failed: " + listFlowResult.failureStatus());
|
||||
throw new RuntimeException("GameProsal: CreateFlow execution has failed");
|
||||
return cordaFlowExecute(holdingIdentity, requestBody, RspGameStateList.class)
|
||||
.getResponce(requestBody);
|
||||
}
|
||||
|
||||
return listFlowResult.successStatus();
|
||||
public GameState gameStateGet(HoldingIdentity holdingIdentity, UUID gameStateUuid) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gs.get-" + UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gamestate.GetFlow",
|
||||
gameStateUuid);
|
||||
|
||||
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
|
||||
.getResponce(requestBody);
|
||||
}
|
||||
|
||||
public UUID gameProposalCreate(
|
||||
public GameState gameProposalCreate(
|
||||
HoldingIdentity issuer,
|
||||
HoldingIdentity acquier,
|
||||
Piece.Color acquierColor,
|
||||
String message
|
||||
) throws JsonMappingException, JsonProcessingException {
|
||||
) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gp.create-" + UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameproposal.CreateFlow",
|
||||
new GameProposalCreateReq(
|
||||
new ReqGameProposalCreate(
|
||||
acquier.x500Name(),
|
||||
acquierColor,
|
||||
message
|
||||
)
|
||||
);
|
||||
message));
|
||||
|
||||
final GameProposalCreateRes createResult = cordaFlowExecute(
|
||||
issuer,
|
||||
requestBody,
|
||||
GameProposalCreateRes.class
|
||||
);
|
||||
|
||||
if (createResult.failureStatus() != null) {
|
||||
System.out.println("GameProposal.CreateFlow failed: " + createResult.failureStatus());
|
||||
throw new RuntimeException("GameProsal: CreateFlow execution has failed");
|
||||
return cordaFlowExecute(issuer, requestBody,RspGameState.class)
|
||||
.getResponce(requestBody);
|
||||
}
|
||||
|
||||
return createResult.successStatus();
|
||||
}
|
||||
|
||||
public String gameProposalReject(
|
||||
HoldingIdentity myHoldingIdentity,
|
||||
UUID gameProposalUuid
|
||||
) {
|
||||
public GameState gameProposalReject(HoldingIdentity holdingIdentity, UUID gameProposalUuid) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gp.reject-" +UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameproposal.CommandFlow",
|
||||
new GameProposalCommandReq(
|
||||
gameProposalUuid.toString(),
|
||||
GameProposalCommandReq.Command.REJECT
|
||||
)
|
||||
);
|
||||
"djmil.cordacheckers.gameproposal.RejectFlow",
|
||||
gameProposalUuid);
|
||||
|
||||
final GameProposalCommandRes actionResult = cordaFlowExecute(
|
||||
myHoldingIdentity,
|
||||
requestBody,
|
||||
GameProposalCommandRes.class
|
||||
);
|
||||
|
||||
if (actionResult.failureStatus() != null) {
|
||||
System.out.println("GameProposal.CommandFlow failed: " + actionResult.failureStatus());
|
||||
throw new RuntimeException("GameProsal: CommandFlow execution has failed");
|
||||
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
|
||||
.getResponce(requestBody);
|
||||
}
|
||||
|
||||
return actionResult.successStatus();
|
||||
public GameState gameProposalCancel(HoldingIdentity holdingIdentity, UUID gameProposalUuid) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gp.reject-" +UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameproposal.CancelFlow",
|
||||
gameProposalUuid);
|
||||
|
||||
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
|
||||
.getResponce(requestBody);
|
||||
}
|
||||
|
||||
public GameBoard gameProposalAccept(
|
||||
HoldingIdentity myHoldingIdentity,
|
||||
UUID gameProposalUuid
|
||||
) {
|
||||
public GameState gameProposalAccept(HoldingIdentity holdingIdentity, UUID gameProposalUuid) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gp.accept-" +UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameproposal.CommandFlow",
|
||||
new GameProposalCommandReq(
|
||||
gameProposalUuid.toString(),
|
||||
GameProposalCommandReq.Command.ACCEPT
|
||||
)
|
||||
);
|
||||
"djmil.cordacheckers.gameproposal.AcceptFlow",
|
||||
gameProposalUuid);
|
||||
|
||||
final GameProposalCommandAcceptRes actionResult = cordaFlowExecute(
|
||||
myHoldingIdentity,
|
||||
requestBody,
|
||||
GameProposalCommandAcceptRes.class
|
||||
);
|
||||
|
||||
if (actionResult.failureStatus() != null) {
|
||||
System.out.println("GameProposal.CommandFlow failed: " + actionResult.failureStatus());
|
||||
throw new RuntimeException("GameProsal: CommandFlow execution has failed");
|
||||
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
|
||||
.getResponce(requestBody);
|
||||
}
|
||||
|
||||
return actionResult.successStatus();
|
||||
}
|
||||
|
||||
public List<GameBoard> gameBoardList(HoldingIdentity holdingIdentity) {
|
||||
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gb.list-" + UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameboard.ListFlow",
|
||||
new Empty()
|
||||
);
|
||||
|
||||
final GameBoardListRes listFlowResult = cordaFlowExecute(
|
||||
holdingIdentity,
|
||||
requestBody,
|
||||
GameBoardListRes.class
|
||||
);
|
||||
|
||||
if (listFlowResult.failureStatus() != null) {
|
||||
System.out.println("GameBoard.ListFlow failed: " + listFlowResult.failureStatus());
|
||||
throw new RuntimeException("GameBoard: ListFlow execution has failed");
|
||||
}
|
||||
|
||||
return listFlowResult.successStatus();
|
||||
}
|
||||
|
||||
public GameResult gameBoardSurrender(
|
||||
HoldingIdentity myHoldingIdentity,
|
||||
UUID gameBoardUuid
|
||||
) {
|
||||
public GameState gameBoardSurrender(HoldingIdentity holdingIdentity, UUID gameBoardUuid) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gb.surrender-" +UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameboard.CommandFlow",
|
||||
new GameBoardCommandReq(
|
||||
gameBoardUuid,
|
||||
new GameBoardCommand(GameBoardCommand.Type.SURRENDER)
|
||||
)
|
||||
);
|
||||
"djmil.cordacheckers.gameboard.SurrenderFlow",
|
||||
gameBoardUuid);
|
||||
|
||||
final GameBoardResGameResult moveResult = cordaFlowExecute(
|
||||
myHoldingIdentity,
|
||||
requestBody,
|
||||
GameBoardResGameResult.class
|
||||
);
|
||||
|
||||
if (moveResult.failureStatus() != null) {
|
||||
System.out.println("GameBoard.CommandFlow failed: " + moveResult.failureStatus());
|
||||
throw new RuntimeException("GameBoard: CommandFlow execution has failed");
|
||||
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
|
||||
.getResponce(requestBody);
|
||||
}
|
||||
|
||||
return moveResult.successStatus();
|
||||
}
|
||||
|
||||
public GameBoard gameBoardMove(
|
||||
HoldingIdentity myHoldingIdentity,
|
||||
UUID gameBoardUuid,
|
||||
List<Integer> move
|
||||
) {
|
||||
public GameState gameBoardMove(HoldingIdentity holdingIdentity, UUID gameBoardUuid, List<Integer> move) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gb.move-" +UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameboard.CommandFlow",
|
||||
new GameBoardCommandReq(
|
||||
new ReqGameBoardMove(
|
||||
gameBoardUuid,
|
||||
new GameBoardCommand(move))
|
||||
);
|
||||
move));
|
||||
|
||||
final GameBoardResGameBoard moveResult = cordaFlowExecute(
|
||||
myHoldingIdentity,
|
||||
requestBody,
|
||||
GameBoardResGameBoard.class
|
||||
);
|
||||
|
||||
if (moveResult.failureStatus() != null) {
|
||||
System.out.println("GameBoard.CommandFlow failed: " + moveResult.failureStatus());
|
||||
throw new RuntimeException("GameBoard: CommandFlow execution has failed");
|
||||
return cordaFlowExecute(holdingIdentity, requestBody, RspGameState.class)
|
||||
.getResponce(requestBody);
|
||||
}
|
||||
|
||||
return moveResult.successStatus();
|
||||
}
|
||||
|
||||
private <T> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowResultType) {
|
||||
|
||||
private <T extends Rsp<?>> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowReponceType) {
|
||||
try {
|
||||
final String requestBodyJson = this.jsonMapper.writeValueAsString(requestBody);
|
||||
final String requestBodyJson = this.jsonMapper
|
||||
.writeValueAsString(requestBody);
|
||||
|
||||
final ResponseBody startedFlow = cordaFlowPost(
|
||||
holdingIdentity,
|
||||
requestBodyJson
|
||||
);
|
||||
final ResponseBody startedFlow = cordaFlowPost(holdingIdentity, requestBodyJson);
|
||||
|
||||
final String flowResult = cordaFlowPoll(startedFlow);
|
||||
final String flowResponce = cordaFlowPoll(startedFlow);
|
||||
|
||||
return this.jsonMapper.readValue(flowResult, flowResultType);
|
||||
return this.jsonMapper
|
||||
.readValue(flowResponce, flowReponceType);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Unable to perform "+requestBody.flowClassName()
|
||||
@ -335,7 +230,6 @@ public class CordaClient {
|
||||
}
|
||||
|
||||
if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) {
|
||||
System.out.println("resp: "+ responseBody.flowResult());
|
||||
return responseBody.flowResult();
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
@JsonDeserialize
|
||||
public record GameBoard(
|
||||
String opponentName,
|
||||
Piece.Color opponentColor,
|
||||
Boolean opponentMove,
|
||||
Integer moveNumber,
|
||||
Map<Integer, Piece> board,
|
||||
GameBoardCommand previousCommand,
|
||||
String message,
|
||||
UUID gameUuid)
|
||||
|
||||
implements GameState {
|
||||
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GameBoardCommand {
|
||||
public static enum Type {
|
||||
MOVE,
|
||||
SURRENDER,
|
||||
VICTORY;
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final List<Integer> move; // [0] from, [1] to
|
||||
|
||||
public GameBoardCommand() {
|
||||
this.type = null;
|
||||
this.move = null;
|
||||
}
|
||||
|
||||
public GameBoardCommand(Type type) {
|
||||
this.type = type;
|
||||
this.move = null;
|
||||
}
|
||||
|
||||
public GameBoardCommand(List<Integer> move) {
|
||||
this.type = Type.MOVE;
|
||||
this.move = move;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public List<Integer> getMove() {
|
||||
return move;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (type == Type.MOVE)
|
||||
return move.get(0) +"->" +move.get(1);
|
||||
else
|
||||
return type.name();
|
||||
}
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
@JsonDeserialize
|
||||
public record GameProposal(
|
||||
String issuer,
|
||||
String acquier,
|
||||
Piece.Color acquierColor,
|
||||
String message,
|
||||
UUID gameUuid)
|
||||
|
||||
implements GameState {
|
||||
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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 gameUuid)
|
||||
|
||||
implements GameState {
|
||||
|
||||
}
|
@ -1,7 +1,30 @@
|
||||
package djmil.cordacheckers.cordaclient.dao;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface GameState {
|
||||
public UUID gameUuid();
|
||||
public record GameState(
|
||||
Status status,
|
||||
String opponentName,
|
||||
Piece.Color opponentColor,
|
||||
|
||||
Map<Integer, Piece> board,
|
||||
Integer moveNumber,
|
||||
List<Integer> previousMove,
|
||||
|
||||
String message,
|
||||
UUID uuid
|
||||
) {
|
||||
public static enum Status {
|
||||
GAME_PROPOSAL_WAIT_FOR_OPPONENT,
|
||||
GAME_PROPOSAL_WAIT_FOR_YOU,
|
||||
GAME_PROPOSAL_REJECTED,
|
||||
GAME_PROPOSAL_CANCELED,
|
||||
GAME_BOARD_WAIT_FOR_OPPONENT,
|
||||
GAME_BOARD_WAIT_FOR_YOU,
|
||||
GAME_RESULT_YOU_WON,
|
||||
GAME_RESULT_YOU_LOOSE;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,12 +4,16 @@ public class Piece {
|
||||
|
||||
public enum Type {
|
||||
MAN,
|
||||
KING,
|
||||
KING;
|
||||
}
|
||||
|
||||
public enum Color {
|
||||
WHITE,
|
||||
BLACK,
|
||||
BLACK;
|
||||
|
||||
public Color opposite() {
|
||||
return this == WHITE ? BLACK : WHITE;
|
||||
}
|
||||
}
|
||||
|
||||
Color color;
|
||||
|
@ -1,8 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import java.util.UUID;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
|
||||
|
||||
public record GameBoardCommandReq(UUID gameBoardUuid, GameBoardCommand command) {
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record GameBoardListRes(List<GameBoard> successStatus, String failureStatus) {
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||
|
||||
public record GameBoardResGameBoard(GameBoard successStatus, String failureStatus) {
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameResult;
|
||||
|
||||
public record GameBoardResGameResult(GameResult successStatus, String failureStatus) {
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||
|
||||
public record GameProposalCommandAcceptRes(GameBoard successStatus, String transactionId, String failureStatus) {
|
||||
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
public record GameProposalCommandReq(String gameProposalUuid, Command command) {
|
||||
public enum Command {
|
||||
ACCEPT,
|
||||
REJECT,
|
||||
CANCEL
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
public record GameProposalCommandRes(String successStatus, String transactionId, String failureStatus) {
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record GameProposalCreateRes(UUID successStatus, String failureStatus) {
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record GameProposalListRes(List<GameProposal> successStatus, String failureStatus) {
|
||||
|
||||
}
|
@ -3,6 +3,9 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
@JsonSerialize
|
||||
public class Empty {
|
||||
/**
|
||||
* Representation of a request without arguments
|
||||
*/
|
||||
public class Req {
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record ReqGameBoardMove(
|
||||
UUID gameBoardUuid,
|
||||
List<Integer> move
|
||||
) {
|
||||
|
||||
}
|
@ -2,6 +2,10 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
||||
|
||||
public record GameProposalCreateReq(String opponentName, Piece.Color opponentColor, String message) {
|
||||
public record ReqGameProposalCreate(
|
||||
String opponentName,
|
||||
Piece.Color opponentColor,
|
||||
String message
|
||||
) {
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.RequestBody;
|
||||
|
||||
public interface Rsp <T> {
|
||||
public T successStatus();
|
||||
public String failureStatus();
|
||||
|
||||
public default T getResponce(RequestBody requestBody) {
|
||||
if (failureStatus() != null) {
|
||||
final String msg = requestBody.flowClassName() +" requestId " +requestBody.clientRequestId() +" has failed: " +failureStatus();
|
||||
System.err.println(msg +". Reson " +failureStatus());
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
|
||||
return successStatus();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.RequestBody;
|
||||
|
||||
public record RspGameState(
|
||||
GameState successStatus,
|
||||
String transactionId,
|
||||
String failureStatus) implements Rsp<GameState> {
|
||||
|
||||
@Override
|
||||
public GameState getResponce(RequestBody requestBody) {
|
||||
if (failureStatus() != null) {
|
||||
final String msg = requestBody.flowClassName() +" requestId " +requestBody.clientRequestId() +" has failed";
|
||||
System.err.println(msg +". Reson " +failureStatus +". TransactionId " +transactionId);
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
|
||||
System.out.println("[OK] "+transactionId);
|
||||
return successStatus();
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||
|
||||
public record RspGameStateList(
|
||||
List<GameState> successStatus,
|
||||
String failureStatus) implements Rsp<List<GameState>> {
|
||||
|
||||
}
|
@ -2,12 +2,14 @@ package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@ -15,17 +17,13 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.CordaClient;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq;
|
||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.ReqGameProposalCreate;
|
||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||
import djmil.cordacheckers.user.User;
|
||||
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("gameproposal")
|
||||
@ -41,9 +39,9 @@ public class GameProposalController {
|
||||
public ResponseEntity<String> findAllUnconsumed(
|
||||
@AuthenticationPrincipal User player
|
||||
) {
|
||||
List<GameProposal> gpList = cordaClient.gameProposalList(player.getHoldingIdentity());
|
||||
List<GameState> gsList = cordaClient.gameStateList(player.getHoldingIdentity());
|
||||
|
||||
return ResponseEntity.ok(gpList.toString());
|
||||
return ResponseEntity.ok(gsList.toString());
|
||||
}
|
||||
|
||||
// @PostMapping()
|
||||
@ -55,7 +53,7 @@ public class GameProposalController {
|
||||
@PostMapping()
|
||||
public ResponseEntity<Void> createGameProposal(
|
||||
@AuthenticationPrincipal User sender,
|
||||
@RequestBody GameProposalCreateReq gpRequest,
|
||||
@RequestBody ReqGameProposalCreate gpRequest,
|
||||
UriComponentsBuilder ucb
|
||||
) throws JsonMappingException, JsonProcessingException {
|
||||
final HoldingIdentity gpSender = sender.getHoldingIdentity();
|
||||
@ -64,7 +62,7 @@ public class GameProposalController {
|
||||
final Piece.Color gpReceiverColor = gpRequest.opponentColor();
|
||||
|
||||
// TODO handle expectionns here
|
||||
UUID newGameProposalUuid = cordaClient.gameProposalCreate(
|
||||
GameState gameStateView = cordaClient.gameProposalCreate(
|
||||
gpSender,
|
||||
gpReceiver,
|
||||
gpReceiverColor,
|
||||
@ -73,7 +71,7 @@ public class GameProposalController {
|
||||
|
||||
URI locationOfNewGameProposal = ucb
|
||||
.path("gameproposal/{id}")
|
||||
.buildAndExpand(newGameProposalUuid)
|
||||
.buildAndExpand(gameStateView)
|
||||
.toUri();
|
||||
|
||||
return ResponseEntity
|
||||
|
@ -1,255 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameResult;
|
||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
||||
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||
|
||||
@SpringBootTest
|
||||
public class CordaClientTest {
|
||||
@Autowired
|
||||
CordaClient cordaClient;
|
||||
|
||||
@Autowired
|
||||
HoldingIdentityResolver holdingIdentityResolver;
|
||||
|
||||
@Test
|
||||
void testGetVirtualNodeList() throws InvalidNameException {
|
||||
List<VirtualNode> vNodes = cordaClient.getVirtualNodeList();
|
||||
|
||||
assertThat(vNodes.size()).isEqualTo(5); // default vNode config for CSDE
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGameProposalList() throws JsonProcessingException {
|
||||
List<GameProposal> gpList = cordaClient.gameProposalList(
|
||||
holdingIdentityResolver.getByUsername("Alice"));
|
||||
|
||||
System.out.println("testListGameProposals\n"+ gpList);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGameProposalCreate() throws JsonMappingException, JsonProcessingException {
|
||||
final String gpIssuer = "alice";
|
||||
final String gpAcquier = "bob";
|
||||
final Piece.Color gpAcquierColor = Piece.Color.WHITE;
|
||||
final String gpMessage = "GameProposal create test";
|
||||
|
||||
final UUID createdGpUuid = cordaClient.gameProposalCreate(
|
||||
holdingIdentityResolver.getByUsername(gpIssuer),
|
||||
holdingIdentityResolver.getByUsername(gpAcquier),
|
||||
gpAcquierColor,
|
||||
gpMessage);
|
||||
|
||||
List<GameProposal> gpListSender = cordaClient.gameProposalList(
|
||||
holdingIdentityResolver.getByUsername(gpIssuer));
|
||||
|
||||
assertThat(findByUuid(gpListSender, createdGpUuid)).isNotNull();
|
||||
|
||||
List<GameProposal> gpListReceiver = cordaClient.gameProposalList(
|
||||
holdingIdentityResolver.getByUsername(gpAcquier));
|
||||
|
||||
GameProposal gp;
|
||||
assertThat(gp = findByUuid(gpListReceiver, createdGpUuid)).isNotNull();
|
||||
|
||||
assertThat(gp.acquier()).isEqualToIgnoringCase(gpAcquier);
|
||||
assertThat(gp.issuer()).isEqualToIgnoringCase(gpIssuer);
|
||||
assertThat(gp.acquierColor()).isEqualByComparingTo(gpAcquierColor);
|
||||
assertThat(gp.message()).isEqualTo(gpMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGameProposalReject() throws JsonMappingException, JsonProcessingException {
|
||||
final String gpIssuer = "alice";
|
||||
final String gpAcquier = "bob";
|
||||
final Piece.Color gpReceiverColor = Piece.Color.WHITE;
|
||||
final String gpMessage = "GameProposal REJECT test";
|
||||
|
||||
final UUID gpUuid = cordaClient.gameProposalCreate(
|
||||
holdingIdentityResolver.getByUsername(gpIssuer),
|
||||
holdingIdentityResolver.getByUsername(gpAcquier),
|
||||
gpReceiverColor,
|
||||
gpMessage);
|
||||
|
||||
System.out.println("Create GP UUID "+ gpUuid);
|
||||
|
||||
assertThatThrownBy(() -> { // Issuer can not reject
|
||||
cordaClient.gameProposalReject(
|
||||
holdingIdentityResolver.getByUsername(gpIssuer),
|
||||
gpUuid);
|
||||
});
|
||||
|
||||
final String rejectRes = cordaClient.gameProposalReject(
|
||||
holdingIdentityResolver.getByUsername(gpAcquier),
|
||||
gpUuid);
|
||||
|
||||
assertThat(rejectRes).isEqualToIgnoringCase("REJECTED");
|
||||
|
||||
List<GameProposal> gpListSender = cordaClient.gameProposalList(
|
||||
holdingIdentityResolver.getByUsername(gpIssuer));
|
||||
|
||||
assertThat(findByUuid(gpListSender, gpUuid)).isNull();
|
||||
|
||||
List<GameProposal> gpListReceiver = cordaClient.gameProposalList(
|
||||
holdingIdentityResolver.getByUsername(gpAcquier));
|
||||
|
||||
assertThat(findByUuid(gpListReceiver, gpUuid)).isNull();
|
||||
|
||||
// GameProposal can not be rejected twice
|
||||
assertThatThrownBy(() -> {
|
||||
cordaClient.gameProposalReject(
|
||||
holdingIdentityResolver.getByUsername(gpIssuer),
|
||||
gpUuid);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGameProposalAccept() throws JsonMappingException, JsonProcessingException {
|
||||
final String gpIssuer = "alice";
|
||||
final String gpAcquier = "bob";
|
||||
final Piece.Color gpAcquierColor = Piece.Color.WHITE;
|
||||
final String gpMessage = "GameProposal ACCEPT test";
|
||||
|
||||
final UUID gpUuid = cordaClient.gameProposalCreate(
|
||||
holdingIdentityResolver.getByUsername(gpIssuer),
|
||||
holdingIdentityResolver.getByUsername(gpAcquier),
|
||||
gpAcquierColor,
|
||||
gpMessage
|
||||
);
|
||||
|
||||
System.out.println("New GameProposal UUID "+ gpUuid);
|
||||
|
||||
assertThatThrownBy(() -> { // Issuer can not accept
|
||||
cordaClient.gameProposalAccept(
|
||||
holdingIdentityResolver.getByUsername(gpIssuer),
|
||||
gpUuid);
|
||||
});
|
||||
|
||||
final GameBoard gbState = cordaClient.gameProposalAccept(
|
||||
holdingIdentityResolver.getByUsername(gpAcquier),
|
||||
gpUuid);
|
||||
|
||||
System.out.println("New GameBoard UUID "+gbState);
|
||||
|
||||
List<GameBoard> gbListIssuer = cordaClient.gameBoardList(
|
||||
holdingIdentityResolver.getByUsername(gpIssuer));
|
||||
|
||||
GameBoard gbAlice = findByUuid(gbListIssuer, gbState.gameUuid());
|
||||
assertThat(gbAlice).isNotNull();
|
||||
assertThat(gbAlice.opponentName()).isEqualToIgnoringCase(gpAcquier);
|
||||
assertThat(gbAlice.opponentColor()).isEqualByComparingTo(gpAcquierColor);
|
||||
assertThat(gbAlice.opponentMove()).isEqualTo(true);
|
||||
|
||||
|
||||
List<GameBoard> gbListAcquier = cordaClient.gameBoardList(
|
||||
holdingIdentityResolver.getByUsername(gpAcquier));
|
||||
|
||||
GameBoard bgBob = findByUuid(gbListAcquier, gbState.gameUuid());
|
||||
assertThat(bgBob).isNotNull();
|
||||
assertThat(bgBob.opponentName()).isEqualToIgnoringCase(gpIssuer);
|
||||
assertThat(bgBob.opponentColor()).isEqualByComparingTo(Piece.Color.BLACK);
|
||||
assertThat(bgBob.opponentMove()).isEqualTo(false);
|
||||
|
||||
assertThatThrownBy(() -> { // GameProposal can not be accepted twice
|
||||
cordaClient.gameProposalAccept(
|
||||
holdingIdentityResolver.getByUsername(gpAcquier),
|
||||
gpUuid);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGameBoardList() throws JsonMappingException, JsonProcessingException {
|
||||
List <GameBoard> gbList = cordaClient.gameBoardList(
|
||||
holdingIdentityResolver.getByUsername("bob"));
|
||||
|
||||
System.out.println("GB list: " +gbList);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGameBoardSurrender() throws JsonMappingException, JsonProcessingException, InvalidNameException {
|
||||
final var hiAlice = holdingIdentityResolver.getByUsername("alice");
|
||||
final var hiBob = holdingIdentityResolver.getByUsername("bob");
|
||||
final var bobColor = Piece.Color.WHITE;
|
||||
|
||||
final UUID gpUuid = cordaClient.gameProposalCreate(
|
||||
hiAlice, hiBob,
|
||||
bobColor, "GameBoard SURRENDER test"
|
||||
);
|
||||
|
||||
System.out.println("New GameProposal UUID "+ gpUuid);
|
||||
|
||||
final GameBoard gbState = cordaClient.gameProposalAccept(
|
||||
hiBob, gpUuid
|
||||
);
|
||||
|
||||
System.out.println("New GameBoard UUID "+ gbState.gameUuid());
|
||||
|
||||
assertThatThrownBy(() -> { // Alice can not surrender, since it is Bob's turn
|
||||
cordaClient.gameBoardSurrender(
|
||||
hiAlice, gbState.gameUuid());
|
||||
});
|
||||
|
||||
final GameResult gameResult = cordaClient.gameBoardSurrender(
|
||||
hiBob, gbState.gameUuid());
|
||||
|
||||
assertThat(gameResult.whitePlayerName()).isEqualTo(hiBob.getName());
|
||||
assertThat(gameResult.blackPlayerName()).isEqualTo(hiAlice.getName());
|
||||
assertThat(gameResult.victoryColor()).isEqualByComparingTo(Piece.Color.BLACK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGameBoardMove() throws JsonMappingException, JsonProcessingException, InvalidNameException {
|
||||
final var hiAlice = holdingIdentityResolver.getByUsername("alice");
|
||||
final var hiBob = holdingIdentityResolver.getByUsername("bob");
|
||||
final var bobColor = Piece.Color.WHITE;
|
||||
|
||||
final UUID gpUuid = cordaClient.gameProposalCreate(
|
||||
hiAlice, hiBob,
|
||||
bobColor, "GameBoard MOVE test"
|
||||
);
|
||||
|
||||
System.out.println("New GameProposal UUID "+ gpUuid);
|
||||
|
||||
final GameBoard gbState = cordaClient.gameProposalAccept(
|
||||
hiBob, gpUuid
|
||||
);
|
||||
|
||||
System.out.println("New GameBoard UUID "+ gbState.gameUuid());
|
||||
|
||||
assertThatThrownBy(() -> { // Alice can not move, since it is Bob's turn
|
||||
cordaClient.gameBoardMove(
|
||||
hiAlice, gbState.gameUuid(),
|
||||
Arrays.asList(1, 2));
|
||||
});
|
||||
|
||||
final GameBoard gameBoard = cordaClient.gameBoardMove(
|
||||
hiBob, gbState.gameUuid(), Arrays.asList(1, 2));
|
||||
}
|
||||
|
||||
private <T extends GameState> T findByUuid(List<T> statesList, UUID uuid) {
|
||||
for (T state : statesList) {
|
||||
if (state.gameUuid().compareTo(uuid) == 0)
|
||||
return state;
|
||||
};
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package djmil.cordacheckers.cordaclient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState.Status;
|
||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||
|
||||
@SpringBootTest
|
||||
public class GameBoardTests {
|
||||
@Autowired
|
||||
CordaClient cordaClient;
|
||||
|
||||
@Autowired
|
||||
HoldingIdentityResolver holdingIdentityResolver;
|
||||
|
||||
final String whitePlayerName = "alice";
|
||||
final String blackPlayerName = "bob";
|
||||
|
||||
@Test
|
||||
void testSurrender() {
|
||||
final var hiWhite = holdingIdentityResolver.getByUsername(whitePlayerName);
|
||||
final var hiBlack = holdingIdentityResolver.getByUsername(blackPlayerName);
|
||||
final String message = "GameBoard SURRENDER test";
|
||||
|
||||
final GameState game = cordaClient.gameProposalCreate(
|
||||
hiWhite, hiBlack, Piece.Color.BLACK, message);
|
||||
|
||||
System.out.println("Game UUID " +game.uuid());
|
||||
|
||||
final GameState acceptedGameBlackView = cordaClient.gameProposalAccept(
|
||||
hiBlack, game.uuid());
|
||||
|
||||
assertThat(acceptedGameBlackView.opponentColor()).isEqualByComparingTo(Piece.Color.WHITE);
|
||||
assertThat(acceptedGameBlackView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
|
||||
|
||||
assertThatThrownBy(() -> { // Black can not surrender, since it is opponent's turn
|
||||
cordaClient.gameBoardSurrender(
|
||||
hiBlack, game.uuid());
|
||||
});
|
||||
|
||||
final GameState surrendererGameView = cordaClient.gameBoardSurrender(
|
||||
hiWhite, game.uuid());
|
||||
|
||||
assertThat(surrendererGameView.opponentName()).isEqualToIgnoringCase(blackPlayerName);
|
||||
assertThat(surrendererGameView.opponentColor()).isEqualByComparingTo(Piece.Color.BLACK);
|
||||
assertThat(surrendererGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_LOOSE);
|
||||
|
||||
final GameState winnerGameView = cordaClient.gameStateGet(
|
||||
hiBlack, game.uuid());
|
||||
|
||||
assertThat(winnerGameView.opponentName()).isEqualToIgnoringCase(whitePlayerName);
|
||||
assertThat(winnerGameView.opponentColor()).isEqualByComparingTo(Piece.Color.WHITE);
|
||||
assertThat(winnerGameView.status()).isEqualByComparingTo(Status.GAME_RESULT_YOU_WON);
|
||||
}
|
||||
|
||||
// @Test
|
||||
// void testGameBoardMove() throws JsonMappingException, JsonProcessingException, InvalidNameException {
|
||||
// final var hiAlice = holdingIdentityResolver.getByUsername("alice");
|
||||
// final var hiBob = holdingIdentityResolver.getByUsername("bob");
|
||||
// final var bobColor = Piece.Color.WHITE;
|
||||
|
||||
// final UUID gpUuid = cordaClient.gameProposalCreate(
|
||||
// hiAlice, hiBob,
|
||||
// bobColor, "GameBoard MOVE test"
|
||||
// );
|
||||
|
||||
// System.out.println("New GameProposal UUID "+ gpUuid);
|
||||
|
||||
// final GameBoard gbState = cordaClient.gameProposalAccept(
|
||||
// hiBob, gpUuid
|
||||
// );
|
||||
|
||||
// System.out.println("New GameBoard UUID "+ gbState.gameUuid());
|
||||
|
||||
// assertThatThrownBy(() -> { // Alice can not move, since it is Bob's turn
|
||||
// cordaClient.gameBoardMove(
|
||||
// hiAlice, gbState.gameUuid(),
|
||||
// Arrays.asList(1, 2));
|
||||
// });
|
||||
|
||||
// final GameBoard gameBoard = cordaClient.gameBoardMove(
|
||||
// hiBob, gbState.gameUuid(), Arrays.asList(1, 2));
|
||||
// }
|
||||
|
||||
// private <T extends GameState> T findByUuid(List<T> statesList, UUID uuid) {
|
||||
// for (T state : statesList) {
|
||||
// if (state.uuid().compareTo(uuid) == 0)
|
||||
// return state;
|
||||
// };
|
||||
// return null;
|
||||
// }
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
package djmil.cordacheckers.cordaclient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState.Status;
|
||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||
|
||||
@SpringBootTest
|
||||
public class GameProposalTests {
|
||||
@Autowired
|
||||
CordaClient cordaClient;
|
||||
|
||||
@Autowired
|
||||
HoldingIdentityResolver holdingIdentityResolver;
|
||||
|
||||
final String issuer = "alice";
|
||||
final String acquier = "bob";
|
||||
final Piece.Color acquierColor = Piece.Color.WHITE;
|
||||
|
||||
@Test
|
||||
void testCreate() {
|
||||
final var hiIssuer = holdingIdentityResolver.getByUsername(issuer);
|
||||
final var hiAcquier = holdingIdentityResolver.getByUsername(acquier);
|
||||
final String message = "GameProposal CREATE test";
|
||||
|
||||
final GameState issuerGameView = cordaClient.gameProposalCreate(
|
||||
hiIssuer,
|
||||
hiAcquier,
|
||||
acquierColor,
|
||||
message);
|
||||
|
||||
System.out.println("Game UUID " +issuerGameView.uuid());
|
||||
|
||||
/*
|
||||
* Issuers perspective on a newly created GameProposal
|
||||
*/
|
||||
assertThat(issuerGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_WAIT_FOR_OPPONENT);
|
||||
assertThat(issuerGameView.opponentName()).isEqualToIgnoringCase(acquier);
|
||||
assertThat(issuerGameView.opponentColor()).isEqualByComparingTo(acquierColor);
|
||||
assertThat(issuerGameView.message()).isEqualToIgnoringCase(message);
|
||||
assertThat(issuerGameView.uuid()).isNotNull();
|
||||
|
||||
/*
|
||||
* Acquier shall be able to find newly created GameProposal
|
||||
*/
|
||||
GameState acquierGameView = assertDoesNotThrow( () -> {
|
||||
return cordaClient.gameStateList(hiAcquier)
|
||||
.stream()
|
||||
.filter(gameView -> gameView.uuid().equals(issuerGameView.uuid()))
|
||||
.reduce((a, b) -> {throw new IllegalStateException("Duplicate");})
|
||||
.get();
|
||||
});
|
||||
|
||||
assertThat(acquierGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_WAIT_FOR_YOU);
|
||||
assertThat(acquierGameView.opponentName()).isEqualToIgnoringCase(issuer);
|
||||
assertThat(acquierGameView.opponentColor()).isEqualByComparingTo(acquierColor.opposite());
|
||||
assertThat(acquierGameView.message()).isEqualToIgnoringCase(message);
|
||||
assertThat(acquierGameView.uuid()).isEqualByComparingTo(issuerGameView.uuid());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReject() {
|
||||
final var hiIssuer = holdingIdentityResolver.getByUsername(issuer);
|
||||
final var hiAcquier = holdingIdentityResolver.getByUsername(acquier);
|
||||
final String message = "GameProposal REJECT test";
|
||||
|
||||
final GameState game = cordaClient.gameProposalCreate(
|
||||
hiIssuer, hiAcquier, acquierColor, message);
|
||||
|
||||
System.out.println("Game UUID " +game.uuid());
|
||||
|
||||
assertThatThrownBy(() -> { // Issuer can not reject
|
||||
cordaClient.gameProposalReject(
|
||||
hiIssuer, game.uuid());
|
||||
});
|
||||
|
||||
final GameState pendingGameView = cordaClient.gameStateGet(
|
||||
hiAcquier, game.uuid());
|
||||
assertThat(pendingGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_WAIT_FOR_YOU);
|
||||
assertThat(pendingGameView.uuid()).isEqualByComparingTo(game.uuid());
|
||||
|
||||
final GameState rejectedGameView = cordaClient.gameProposalReject(
|
||||
hiAcquier, game.uuid());
|
||||
assertThat(rejectedGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_REJECTED);
|
||||
assertThat(rejectedGameView.uuid()).isEqualByComparingTo(game.uuid());
|
||||
|
||||
assertThatThrownBy(() -> { // Issuer has no pending GameProposal
|
||||
cordaClient.gameStateGet(
|
||||
hiIssuer, game.uuid());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCancel() {
|
||||
final var hiIssuer = holdingIdentityResolver.getByUsername(issuer);
|
||||
final var hiAcquier = holdingIdentityResolver.getByUsername(acquier);
|
||||
final String message = "GameProposal CANCEL test";
|
||||
|
||||
final GameState game = cordaClient.gameProposalCreate(
|
||||
hiIssuer, hiAcquier, acquierColor, message);
|
||||
|
||||
System.out.println("Game UUID " +game.uuid());
|
||||
|
||||
assertThatThrownBy(() -> { // Acquier can not cancel
|
||||
cordaClient.gameProposalCancel(
|
||||
hiAcquier, game.uuid());
|
||||
});
|
||||
|
||||
final GameState pendingGameView = cordaClient.gameStateGet(
|
||||
hiIssuer, game.uuid());
|
||||
assertThat(pendingGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_WAIT_FOR_OPPONENT);
|
||||
assertThat(pendingGameView.uuid()).isEqualByComparingTo(game.uuid());
|
||||
|
||||
final GameState canceledGameView = cordaClient.gameProposalCancel(
|
||||
hiIssuer, game.uuid());
|
||||
assertThat(canceledGameView.status()).isEqualByComparingTo(Status.GAME_PROPOSAL_CANCELED);
|
||||
assertThat(canceledGameView.uuid()).isEqualByComparingTo(game.uuid());
|
||||
|
||||
assertThatThrownBy(() -> { // Acquier has no pending GameProposal
|
||||
cordaClient.gameStateGet(
|
||||
hiAcquier, game.uuid());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAccept() {
|
||||
final var hiIssuer = holdingIdentityResolver.getByUsername(issuer);
|
||||
final var hiAcquier = holdingIdentityResolver.getByUsername(acquier);
|
||||
final String message = "GameProposal ACCEPT test";
|
||||
|
||||
final GameState game = cordaClient.gameProposalCreate(
|
||||
hiIssuer, hiAcquier, acquierColor, message);
|
||||
|
||||
System.out.println("Game UUID " +game.uuid());
|
||||
|
||||
assertThatThrownBy(() -> { // Issuer can not accept
|
||||
cordaClient.gameProposalAccept(
|
||||
hiIssuer, game.uuid());
|
||||
});
|
||||
|
||||
final GameState acceptedGameAcquierView = cordaClient.gameProposalAccept(
|
||||
hiAcquier, game.uuid());
|
||||
|
||||
assertThat(acceptedGameAcquierView.opponentName()).isEqualToIgnoringCase(issuer);
|
||||
assertThat(acceptedGameAcquierView.opponentColor()).isEqualByComparingTo(acquierColor.opposite());
|
||||
assertThat(acceptedGameAcquierView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_YOU);
|
||||
assertThat(acceptedGameAcquierView.uuid()).isEqualByComparingTo(game.uuid());
|
||||
|
||||
assertThatThrownBy(() -> { // same GameProposal can not be accepted twice
|
||||
cordaClient.gameProposalAccept(
|
||||
hiIssuer, game.uuid());
|
||||
});
|
||||
|
||||
final GameState acceptedGameIssuerView = cordaClient.gameStateGet(
|
||||
hiIssuer, game.uuid());
|
||||
|
||||
assertThat(acceptedGameIssuerView.opponentName()).isEqualToIgnoringCase(acquier);
|
||||
assertThat(acceptedGameIssuerView.opponentColor()).isEqualByComparingTo(acquierColor);
|
||||
assertThat(acceptedGameIssuerView.status()).isEqualByComparingTo(Status.GAME_BOARD_WAIT_FOR_OPPONENT);
|
||||
assertThat(acceptedGameIssuerView.uuid()).isEqualByComparingTo(game.uuid());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package djmil.cordacheckers.cordaclient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||
|
||||
@SpringBootTest
|
||||
public class GameStateTests {
|
||||
@Autowired
|
||||
CordaClient cordaClient;
|
||||
|
||||
@Autowired
|
||||
HoldingIdentityResolver holdingIdentityResolver;
|
||||
|
||||
@Test
|
||||
void testGetVirtualNodeList() throws InvalidNameException {
|
||||
List<VirtualNode> vNodes = cordaClient.getVirtualNodeList();
|
||||
|
||||
assertThat(vNodes.size()).isEqualTo(5); // default vNode config for CSDE
|
||||
}
|
||||
|
||||
@Test
|
||||
void testList() throws JsonProcessingException {
|
||||
List<GameState> gsList = cordaClient.gameStateList(
|
||||
holdingIdentityResolver.getByUsername("bob"));
|
||||
|
||||
System.out.println(gsList);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGet() throws JsonProcessingException {
|
||||
GameState gameStateView = cordaClient.gameStateGet(
|
||||
holdingIdentityResolver.getByUsername("bob"),
|
||||
UUID.fromString("cf357d0a-8f64-4599-b9b5-d263163812d4")
|
||||
);
|
||||
|
||||
System.out.println(gameStateView);
|
||||
}
|
||||
|
||||
}
|
@ -5,7 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.json.JsonTest;
|
||||
import org.springframework.boot.test.json.JacksonTester;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Req;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ -23,7 +23,7 @@ public class RequestBodyTest {
|
||||
RequestBody requestBody = new RequestBody(
|
||||
"list-2",
|
||||
"djmil.cordacheckers.gameproposal.ListFlow",
|
||||
new Empty()
|
||||
new Req()
|
||||
);
|
||||
|
||||
assertThat(jTester.write(requestBody)).isEqualToJson("requestBody/ListFlow.json");
|
||||
|
@ -1,128 +0,0 @@
|
||||
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 djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameResultState;
|
||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||
import net.corda.v5.base.annotations.CordaSerializable;
|
||||
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.transaction.UtxoLedgerTransaction;
|
||||
|
||||
public class GameBoardCommand implements Command {
|
||||
|
||||
@CordaSerializable
|
||||
public static enum Type {
|
||||
MOVE,
|
||||
SURRENDER,
|
||||
FINISH;
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final List<Integer> move; // [0] from, [1] to
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public GameBoardCommand() {
|
||||
this.type = null;
|
||||
this.move = null;
|
||||
}
|
||||
|
||||
@ConstructorForDeserialization
|
||||
public GameBoardCommand(Type type, List<Integer> move) {
|
||||
this.type = type;
|
||||
this.move = move;
|
||||
}
|
||||
|
||||
public GameBoardCommand(Type type) {
|
||||
if (type == Type.MOVE)
|
||||
throw new CordaRuntimeException (BAD_ACTIONMOVE_CONSTRUCTOR);
|
||||
|
||||
this.type = type;
|
||||
this.move = null;
|
||||
}
|
||||
|
||||
public GameBoardCommand(List<Integer> move) {
|
||||
this.type = Type.MOVE;
|
||||
this.move = move;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public List<Integer> getMove() {
|
||||
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.getWinnerName()) == 0, IN_OUT_PARTICIPANTS);
|
||||
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getOpponentName()) == 0, IN_OUT_PARTICIPANTS);
|
||||
}
|
||||
|
||||
public static void validateMoveTrx(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);
|
||||
}
|
||||
|
||||
public static void validateFinishTrx(UtxoLedgerTransaction trx) {
|
||||
requireThat(trx.getInputContractStates().size() == 1, FINAL_MOVE_INPUT_STATE);
|
||||
final var inGameBoardState = getSingleInputState(trx, GameBoardState.class);
|
||||
|
||||
requireThat(trx.getOutputContractStates().size() == 1, FINAL_MOVE_OUTPUT_STATE);
|
||||
final var outGameResultState = getSingleOutputState(trx, GameResultState.class);
|
||||
|
||||
requireThat(inGameBoardState.getWhitePlayerName().compareTo(outGameResultState.getWinnerName()) == 0, IN_OUT_PARTICIPANTS);
|
||||
requireThat(inGameBoardState.getBlackPlayerName().compareTo(outGameResultState.getOpponentName()) == 0, IN_OUT_PARTICIPANTS);
|
||||
}
|
||||
|
||||
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 MOVE_INPUT_STATE = "MOVE command should have exactly one GameBoardState input state";
|
||||
static final String MOVE_OUTPUT_STATE = "MOVE command should have exactly one GameBoardState output state";
|
||||
static final String MOVE_OUT_BOARD = "MOVE command: move checkers rules violation";
|
||||
static final String MOVE_OUT_COLOR = "MOVE command: moveColor checkers rules violation";
|
||||
|
||||
static final String FINAL_MOVE_INPUT_STATE = "FINAL_MOVE command should have exactly one GameBoardState input state";
|
||||
static final String FINAL_MOVE_OUTPUT_STATE = "FINAL_MOVE 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,12 +1,11 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import static djmil.cordacheckers.contracts.GameCommand.requireThat;
|
||||
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.Command;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
@ -17,52 +16,25 @@ public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
public void verify(UtxoLedgerTransaction trx) {
|
||||
log.info("GameBoardContract.verify() called");
|
||||
|
||||
requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND);
|
||||
final Command command = getSingleCommand(trx, Command.class);
|
||||
requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND);
|
||||
final GameCommand command = getSingleCommand(trx, GameCommand.class);
|
||||
|
||||
if (command instanceof GameProposalCommand) {
|
||||
log.info("GameBoardContract.verify() as GameProposalCommand "+(GameProposalCommand)command);
|
||||
switch ((GameProposalCommand)command) {
|
||||
case ACCEPT:
|
||||
GameProposalCommand.validateAcceptTrx(trx);
|
||||
switch (command.action) {
|
||||
case GAME_PROPOSAL_ACCEPT:
|
||||
GameCommand.validateGameProposalAccept(trx);
|
||||
break;
|
||||
|
||||
case GAME_BOARD_MOVE:
|
||||
GameCommand.validateGameBoardMove(trx);
|
||||
break;
|
||||
|
||||
case GAME_BOARD_SURRENDER:
|
||||
GameCommand.validateGameBoardSurrender(trx);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new CordaRuntimeException("UNKNOWN_COMMAND");
|
||||
}
|
||||
} else
|
||||
if (command instanceof GameBoardCommand) {
|
||||
log.info("GameBoardContract.verify() as GameBoardCommand "+((GameBoardCommand)command).getType());
|
||||
switch (((GameBoardCommand)command).getType()) {
|
||||
case MOVE:
|
||||
GameBoardCommand.validateMoveTrx(trx);
|
||||
break;
|
||||
|
||||
case SURRENDER:
|
||||
GameBoardCommand.validateSurrenderTrx(trx);
|
||||
break;
|
||||
|
||||
case FINISH:
|
||||
GameBoardCommand.validateFinishTrx(trx);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new GameBoardCommand.CommandTypeException();
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Bad utxo command type");
|
||||
throw new GameCommand.ActionException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void requireThat(boolean asserted, String errorMessage) {
|
||||
if(!asserted) {
|
||||
throw new CordaRuntimeException("Failed requirement: " + errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
static final String REQUIRE_SINGLE_COMMAND = "Require a single command";
|
||||
|
||||
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 ";
|
||||
}
|
||||
|
@ -0,0 +1,243 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import djmil.cordacheckers.states.GameResultState;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||
import net.corda.v5.base.annotations.CordaSerializable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
import 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 {
|
||||
GAME_PROPOSAL_CREATE,
|
||||
GAME_PROPOSAL_CANCEL,
|
||||
GAME_PROPOSAL_ACCEPT,
|
||||
GAME_PROPOSAL_REJECT,
|
||||
|
||||
GAME_BOARD_MOVE,
|
||||
GAME_BOARD_SURRENDER,
|
||||
|
||||
GAME_RESULT_CREATE;
|
||||
}
|
||||
|
||||
public final Action action;
|
||||
public final List<Integer> move; // [0] from, [1] to
|
||||
|
||||
public static class ActionException extends RuntimeException {
|
||||
public ActionException() {
|
||||
super("Bad GameCommand.Action value");
|
||||
}
|
||||
}
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public GameCommand() {
|
||||
this.action = null;
|
||||
this.move = null;
|
||||
}
|
||||
|
||||
public GameCommand(Action action) {
|
||||
if (action == Action.GAME_BOARD_MOVE)
|
||||
throw new ActionException();
|
||||
|
||||
this.action = action;
|
||||
this.move = null;
|
||||
}
|
||||
|
||||
public GameCommand(List<Integer> move) {
|
||||
this.action = Action.GAME_BOARD_MOVE;
|
||||
this.move = move;
|
||||
}
|
||||
|
||||
@ConstructorForDeserialization
|
||||
public GameCommand(Action action, List<Integer> move) {
|
||||
this.action = action;
|
||||
this.move = move;
|
||||
}
|
||||
|
||||
public Action getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public List<Integer> getMove() {
|
||||
return move;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Session initiator/respondent
|
||||
*/
|
||||
|
||||
public MemberX500Name getObserver(StateAndRef<GameState> gameStateSar) {
|
||||
final GameState gameState = gameStateSar.getState().getContractState();
|
||||
return gameState.getCounterpartyName(getActor(gameState));
|
||||
}
|
||||
|
||||
public MemberX500Name getActor(StateAndRef<GameState> gameStateSar) {
|
||||
return getActor(gameStateSar.getState().getContractState());
|
||||
}
|
||||
|
||||
public MemberX500Name getObserver(GameState gameState) {
|
||||
return gameState.getCounterpartyName(getActor(gameState));
|
||||
}
|
||||
|
||||
public MemberX500Name getActor(GameState gameState) {
|
||||
switch (action) {
|
||||
case GAME_PROPOSAL_CREATE:
|
||||
case GAME_PROPOSAL_CANCEL:
|
||||
if (gameState instanceof GameProposalState)
|
||||
return ((GameProposalState)gameState).getIssuer();
|
||||
break;
|
||||
|
||||
case GAME_PROPOSAL_REJECT:
|
||||
if (gameState instanceof GameProposalState)
|
||||
return ((GameProposalState)gameState).getAcquier();
|
||||
break;
|
||||
|
||||
case GAME_PROPOSAL_ACCEPT:
|
||||
if (gameState instanceof GameProposalState) // <<-- Session validation perspective
|
||||
return ((GameProposalState)gameState).getAcquier();
|
||||
if (gameState instanceof GameBoardState) // <<-- GameViewBuilder perspective
|
||||
return ((GameBoardState)gameState).getMovePlayerName();
|
||||
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();
|
||||
break;
|
||||
|
||||
case GAME_RESULT_CREATE:
|
||||
if (gameState instanceof GameResultState)
|
||||
return ((GameResultState)gameState).getWinnerName();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ActionException();
|
||||
}
|
||||
|
||||
throw new RuntimeException(action +": unexpected GameState type " +gameState.getClass());
|
||||
}
|
||||
|
||||
/*
|
||||
* Transaction validation
|
||||
*/
|
||||
|
||||
public static 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);
|
||||
|
||||
requireThat(outputState.getAcquierColor() != null, NON_NULL_RECIPIENT_COLOR);
|
||||
}
|
||||
|
||||
public static 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);
|
||||
|
||||
requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), IN_OUT_PARTICIPANTS);
|
||||
}
|
||||
|
||||
public static 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) {
|
||||
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) {
|
||||
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())
|
||||
);
|
||||
|
||||
List<MemberX500Name> outActors = new LinkedList<MemberX500Name>(List.of(
|
||||
outGameResultState.getWinnerName(),
|
||||
outGameResultState.getOpponentName())
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static 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);
|
||||
}
|
||||
|
||||
public static void validateGameResultCreate(UtxoLedgerTransaction trx) {
|
||||
throw new RuntimeException("Unimplemented");
|
||||
}
|
||||
|
||||
public static void requireThat(boolean asserted, String errorMessage) {
|
||||
if (!asserted) {
|
||||
throw new IllegalStateException("Failed requirement: " + errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
static final String REQUIRE_SINGLE_COMMAND = "Require a single command";
|
||||
static final String IN_OUT_PARTICIPANTS = "Input participants should represent output participants";
|
||||
|
||||
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";
|
||||
|
||||
static final String CANCEL_INPUT_STATE = "Cancel command should have exactly one GameProposal input state";
|
||||
static final String CANCEL_OUTPUT_STATE = "Cancel command should have no output states";
|
||||
|
||||
static final String ACCEPT_INPUT_STATE = "Accept command should have exactly one GameProposal input state";
|
||||
static final String ACCEPT_OUTPUT_STATE = "Accept command should have exactly one GameBoard output state";
|
||||
|
||||
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 SURRENDER_ACTOR = "Only active player can SURRENDER";
|
||||
|
||||
static final String MOVE_INPUT_STATE = "MOVE command should have exactly one GameBoardState input state";
|
||||
static final String MOVE_OUTPUT_STATE = "MOVE command should have exactly one GameBoardState output state";
|
||||
static final String MOVE_OUT_BOARD = "MOVE command: move checkers rules violation";
|
||||
static final String MOVE_OUT_COLOR = "MOVE command: moveColor checkers rules violation";
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.requireThat;
|
||||
|
||||
public enum GameProposalCommand implements Command {
|
||||
CREATE,
|
||||
ACCEPT,
|
||||
REJECT,
|
||||
CANCEL;
|
||||
|
||||
public MemberX500Name getInitiator(GameProposalState gameProposalState) {
|
||||
switch (this) {
|
||||
case CREATE:
|
||||
case CANCEL:
|
||||
return gameProposalState.getIssuer();
|
||||
case ACCEPT:
|
||||
case REJECT:
|
||||
return gameProposalState.getAcquier();
|
||||
default:
|
||||
throw new RuntimeException(UNSUPPORTED_VALUE_OF + this.name());
|
||||
}
|
||||
}
|
||||
|
||||
public MemberX500Name getRespondent(GameProposalState gameProposalState) {
|
||||
switch (this) {
|
||||
case CREATE:
|
||||
case CANCEL:
|
||||
return gameProposalState.getAcquier();
|
||||
case ACCEPT:
|
||||
case REJECT:
|
||||
return gameProposalState.getIssuer();
|
||||
default:
|
||||
throw new RuntimeException(UNSUPPORTED_VALUE_OF + this.name());
|
||||
}
|
||||
}
|
||||
|
||||
public MemberX500Name getInitiator(StateAndRef<GameProposalState> gameProposalSar) {
|
||||
return getInitiator(gameProposalSar.getState().getContractState());
|
||||
}
|
||||
|
||||
public MemberX500Name getRespondent(StateAndRef<GameProposalState> gameProposalSar) {
|
||||
return getRespondent(gameProposalSar.getState().getContractState());
|
||||
}
|
||||
|
||||
public static void validateCreateTrx(UtxoLedgerTransaction trx) {
|
||||
requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE);
|
||||
requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE);
|
||||
|
||||
GameProposalState outputState = getSingleOutputState(trx, GameProposalState.class);
|
||||
|
||||
requireThat(outputState.getAcquierColor() != null, NON_NULL_RECIPIENT_COLOR);
|
||||
}
|
||||
|
||||
public static void validateAcceptTrx(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);
|
||||
|
||||
requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), ACCEPT_PARTICIPANTS);
|
||||
}
|
||||
|
||||
public static void validateRejectTrx(UtxoLedgerTransaction trx) {
|
||||
requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE);
|
||||
requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE);
|
||||
|
||||
getSingleInputState(trx, GameProposalState.class);
|
||||
}
|
||||
|
||||
public static void validateCancelTrx(UtxoLedgerTransaction trx) {
|
||||
requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE);
|
||||
requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE);
|
||||
|
||||
getSingleInputState(trx, GameProposalState.class);
|
||||
}
|
||||
|
||||
public static final String UNSUPPORTED_VALUE_OF = "Unsupported GameProposalCommand value: ";
|
||||
|
||||
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";
|
||||
|
||||
static final String CANCEL_INPUT_STATE = "Cancel command should have exactly one GameProposal input state";
|
||||
static final String CANCEL_OUTPUT_STATE = "Cancel command should have no output states";
|
||||
|
||||
static final String ACCEPT_INPUT_STATE = "Accept command should have exactly one GameProposal input state";
|
||||
static final String ACCEPT_OUTPUT_STATE = "Accept command should have exactly one GameBoard output state";
|
||||
static final String ACCEPT_PARTICIPANTS = "Accept command: GameBoard participants should math GameProposal participants";
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import static djmil.cordacheckers.contracts.GameCommand.requireThat;
|
||||
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.getSingleCommand;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.requireThat;
|
||||
|
||||
public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(GameProposalContract.class);
|
||||
@ -17,31 +16,29 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
public void verify(UtxoLedgerTransaction trx) {
|
||||
log.info("GameProposalContract.verify() called");
|
||||
|
||||
requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND);
|
||||
final GameProposalCommand command = getSingleCommand(trx, GameProposalCommand.class);
|
||||
requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND);
|
||||
final GameCommand command = getSingleCommand(trx, GameCommand.class);
|
||||
|
||||
switch (command) {
|
||||
case CREATE:
|
||||
GameProposalCommand.validateCreateTrx(trx);
|
||||
switch (command.action) {
|
||||
case GAME_PROPOSAL_CREATE:
|
||||
GameCommand.validateGameProposalCreate(trx);
|
||||
break;
|
||||
|
||||
case ACCEPT:
|
||||
GameProposalCommand.validateAcceptTrx(trx);
|
||||
case GAME_PROPOSAL_ACCEPT:
|
||||
GameCommand.validateGameProposalAccept(trx);
|
||||
break;
|
||||
|
||||
case REJECT:
|
||||
GameProposalCommand.validateRejectTrx(trx);
|
||||
case GAME_PROPOSAL_REJECT:
|
||||
GameCommand.validateGameProposalReject(trx);
|
||||
break;
|
||||
|
||||
case CANCEL:
|
||||
GameProposalCommand.validateCancelTrx(trx);
|
||||
case GAME_PROPOSAL_CANCEL:
|
||||
GameCommand.validateGameProposalCancel(trx);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new CordaRuntimeException(UNKNOWN_COMMAND);
|
||||
throw new GameCommand.ActionException();
|
||||
}
|
||||
}
|
||||
|
||||
static final String REQUIRE_SINGLE_COMMAND = "Require a single command";
|
||||
static final String UNKNOWN_COMMAND = "Unsupported command";
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import static djmil.cordacheckers.contracts.GameCommand.requireThat;
|
||||
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);
|
||||
@ -18,23 +16,21 @@ public class GameResultContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
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);
|
||||
requireThat(trx.getCommands().size() == 1, GameCommand.REQUIRE_SINGLE_COMMAND);
|
||||
final GameCommand command = getSingleCommand(trx, GameCommand.class);
|
||||
|
||||
switch (command.getType()) {
|
||||
case SURRENDER:
|
||||
GameBoardCommand.validateSurrenderTrx(trx);
|
||||
switch (command.action) {
|
||||
case GAME_BOARD_SURRENDER:
|
||||
GameCommand.validateGameBoardSurrender(trx);
|
||||
break;
|
||||
|
||||
case FINISH:
|
||||
GameBoardCommand.validateFinishTrx(trx);
|
||||
case GAME_RESULT_CREATE:
|
||||
GameCommand.validateGameResultCreate(trx);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new CordaRuntimeException(UNKNOWN_COMMAND);
|
||||
throw new GameCommand.ActionException();
|
||||
}
|
||||
}
|
||||
|
||||
static final String REQUIRE_SINGLE_COMMAND = "Require a single command";
|
||||
static final String UNKNOWN_COMMAND = "Unsupported command";
|
||||
}
|
||||
|
@ -3,53 +3,45 @@ package djmil.cordacheckers.contracts;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.ContractState;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
public class UtxoLedgerTransactionUtil {
|
||||
public static <T extends Command> T getSingleCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
public static <T extends GameCommand> T getSingleCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return single(utxoTrx.getCommands(clazz), clazz);
|
||||
}
|
||||
|
||||
public static <T extends ContractState> StateAndRef<T> getSingleReferenceSar(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return singleSar(utxoTrx.getReferenceStateAndRefs(clazz), clazz);
|
||||
}
|
||||
// public static <T extends ContractState> StateAndRef<T> getSingleReferenceSar(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
// return singleSar(utxoTrx.getReferenceStateAndRefs(clazz), clazz);
|
||||
// }
|
||||
|
||||
public static <T extends ContractState> T getSingleInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
public static <T extends GameState> T getSingleInputState(UtxoLedgerTransaction utxoTrx, Class<T> 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> 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 GameState> T getSingleOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return single(utxoTrx.getOutputStates(clazz), clazz);
|
||||
}
|
||||
|
||||
public static <T extends Command> Optional<T> getOptionalCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return optional(utxoTrx.getCommands(clazz), clazz);
|
||||
}
|
||||
// public static <T extends Command> Optional<T> getOptionalCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
// return optional(utxoTrx.getCommands(clazz), clazz);
|
||||
// }
|
||||
|
||||
public static <T extends ContractState> Optional<T> getOptionalReferenceState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return optional(utxoTrx.getReferenceStates(clazz), clazz);
|
||||
}
|
||||
// public static <T extends ContractState> Optional<T> getOptionalReferenceState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
// return optional(utxoTrx.getReferenceStates(clazz), clazz);
|
||||
// }
|
||||
|
||||
public static <T extends ContractState> Optional<T> getOptionalInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return optional(utxoTrx.getInputStates(clazz), clazz);
|
||||
}
|
||||
// public static <T extends ContractState> Optional<T> getOptionalInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
// return optional(utxoTrx.getInputStates(clazz), clazz);
|
||||
// }
|
||||
|
||||
public static <T extends ContractState> Optional<T> getOptionalOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return optional(utxoTrx.getOutputStates(clazz), clazz);
|
||||
}
|
||||
|
||||
public static void requireThat(boolean asserted, String errorMessage) {
|
||||
if (!asserted) {
|
||||
throw new IllegalStateException("Failed requirement: " + errorMessage);
|
||||
}
|
||||
}
|
||||
// public static <T extends ContractState> Optional<T> getOptionalOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
// return optional(utxoTrx.getOutputStates(clazz), clazz);
|
||||
// }
|
||||
|
||||
private static <T> Optional<T> optional(List<T> list, Class<T> clazz) {
|
||||
return list
|
||||
@ -62,13 +54,6 @@ public class UtxoLedgerTransactionUtil {
|
||||
.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 NO_INSTANCES_OF = "No instances of ";
|
||||
}
|
||||
|
@ -26,11 +26,11 @@ public class GameResultState extends GameState {
|
||||
this.winnerColor = winnerColor;
|
||||
}
|
||||
|
||||
public GameResultState(GameBoardState gameBoardState, Piece.Color victoryColor) {
|
||||
public GameResultState(GameBoardState gameBoardState, MemberX500Name winnerName) {
|
||||
super(gameBoardState.gameUuid, null, gameBoardState.participants);
|
||||
this.winnerName = gameBoardState.getWhitePlayerName();
|
||||
this.opponentName = gameBoardState.getBlackPlayerName();
|
||||
this.winnerColor = victoryColor;
|
||||
this.opponentName = gameBoardState.getCounterpartyName(winnerName);
|
||||
this.winnerName = winnerName;
|
||||
this.winnerColor = gameBoardState.getWhitePlayerName().compareTo(winnerName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK;
|
||||
}
|
||||
|
||||
public MemberX500Name getWinnerName() {
|
||||
@ -57,7 +57,7 @@ public class GameResultState extends GameState {
|
||||
}
|
||||
|
||||
@Override
|
||||
MemberX500Name getWhitePlayerName() {
|
||||
public MemberX500Name getWhitePlayerName() {
|
||||
return winnerColor == Piece.Color.WHITE ? winnerName : opponentName;
|
||||
}
|
||||
|
||||
|
@ -17,10 +17,10 @@ public abstract class GameState implements ContractState {
|
||||
final List<PublicKey> participants;
|
||||
|
||||
@NotNull
|
||||
abstract MemberX500Name getWhitePlayerName();
|
||||
public abstract MemberX500Name getWhitePlayerName();
|
||||
|
||||
@NotNull
|
||||
abstract MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved;
|
||||
public abstract MemberX500Name getCounterpartyName(MemberX500Name myName) throws NotInvolved;
|
||||
|
||||
GameState(UUID gameUuid, String message, List<PublicKey> participants) {
|
||||
this.gameUuid = gameUuid;
|
||||
|
@ -1,198 +0,0 @@
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.FlowResult;
|
||||
import djmil.cordacheckers.contracts.GameBoardCommand;
|
||||
import djmil.cordacheckers.contracts.GameBoardCommand.CommandTypeException;
|
||||
import djmil.cordacheckers.gameresult.GameResultView;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameResultState;
|
||||
import djmil.cordacheckers.states.Piece;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.InitiatingFlow;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
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.types.MemberX500Name;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
|
||||
|
||||
@InitiatingFlow(protocol = "game-board")
|
||||
public class CommandFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CommandFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@CordaInject
|
||||
public FlowMessaging flowMessaging;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
log.info("GameBoardCommandFlow started");
|
||||
try {
|
||||
final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class);
|
||||
|
||||
final StateAndRef<GameBoardState> utxoSarGameBoard = findUtxoGameBoard(args.getGameBoardUuid());
|
||||
|
||||
final SecureHash trxId = doTransaction(args.getCommand(), utxoSarGameBoard);
|
||||
final Object trxResult = getTrxResult(trxId);
|
||||
|
||||
return new FlowResult(trxResult, trxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +
|
||||
" because: " + e.getMessage());
|
||||
return new FlowResult(e).toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
StateAndRef<GameBoardState> findUtxoGameBoard (UUID gameBoardUuid) {
|
||||
/*
|
||||
* Get list of all unconsumed aka 'active' GameProposalStates, then filter by UUID.
|
||||
* Note, this is an inefficient way to perform this operation if there are a large
|
||||
* number of 'active' GameProposals exists in storage.
|
||||
*/
|
||||
return this.utxoLedgerService
|
||||
.findUnconsumedStatesByType(GameBoardState.class)
|
||||
.stream()
|
||||
.filter(sar -> sar.getState().getContractState().getGameUuid().equals(gameBoardUuid))
|
||||
.reduce((a, b) -> {throw new IllegalStateException("Multiple states: " +a +", " +b);})
|
||||
.get();
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
SecureHash doTransaction(
|
||||
GameBoardCommand command,
|
||||
StateAndRef<GameBoardState> utxoSarGameBoard
|
||||
) {
|
||||
final GameBoardState stateGameBoard = utxoSarGameBoard.getState().getContractState();
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
final MemberX500Name opponentName = stateGameBoard.getCounterpartyName(myName);
|
||||
|
||||
UtxoTransactionBuilder trxBuilder = utxoLedgerService.createTransactionBuilder()
|
||||
.setNotary(utxoSarGameBoard.getState().getNotaryName())
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addCommand(command)
|
||||
.addInputState(utxoSarGameBoard.getRef())
|
||||
.addSignatories(stateGameBoard.getParticipants());
|
||||
|
||||
switch (command.getType()) {
|
||||
case SURRENDER:
|
||||
trxBuilder = trxBuilder
|
||||
.addOutputState( new GameResultState(
|
||||
stateGameBoard,
|
||||
stateGameBoard.getCounterpartyColor(myName)) // winner color
|
||||
);
|
||||
break;
|
||||
|
||||
case MOVE:
|
||||
trxBuilder = trxBuilder
|
||||
.addOutputState( new GameBoardState(stateGameBoard, stateGameBoard.getBoard(), stateGameBoard.getMoveColor())); // TODO: advance color
|
||||
break;
|
||||
|
||||
case FINISH:
|
||||
throw new CommandTypeException("GameBoard.CommandFlow can not process externaly issued FINISH commnd");
|
||||
|
||||
default:
|
||||
throw new CommandTypeException("GameBoard.CommandFlow doTransaction(): Unknown command");
|
||||
}
|
||||
|
||||
return commit(
|
||||
trxBuilder.toSignedTransaction(),
|
||||
opponentName);
|
||||
}
|
||||
|
||||
@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:
|
||||
return viewGameResult(utxoTrx);
|
||||
|
||||
case MOVE:
|
||||
// 1. create initial GameBoardView
|
||||
// 2. check for winning conditions
|
||||
// - run subcommnd FINISH that will consume current gbUtxo and will produce GameResult state
|
||||
// - update GameBoardView to have FINISH command
|
||||
// 3. return GameBoardView
|
||||
return viewGameBoard(utxoTrx);
|
||||
|
||||
case FINISH:
|
||||
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();
|
||||
|
||||
return new GameBoardView(utxoGameBoard, myName);
|
||||
}
|
||||
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package djmil.cordacheckers.gameboard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameBoardCommand;
|
||||
|
||||
public class CommandFlowArgs {
|
||||
private UUID gameBoardUuid;
|
||||
private GameBoardCommand command;
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public CommandFlowArgs() {
|
||||
this.gameBoardUuid = null;
|
||||
this.command = null;
|
||||
}
|
||||
|
||||
public GameBoardCommand getCommand() {
|
||||
return this.command;
|
||||
}
|
||||
|
||||
public UUID getGameBoardUuid() {
|
||||
return this.gameBoardUuid;
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
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 FINISH:
|
||||
return getSingleInputState(utxoGameBoard, GameBoardState.class);
|
||||
|
||||
case MOVE:
|
||||
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,68 +0,0 @@
|
||||
package djmil.cordacheckers.gameboard;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameBoardCommand;
|
||||
import djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil;
|
||||
import djmil.cordacheckers.states.GameState.NotInvolved;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.Piece;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
|
||||
// GameBoard from the player's point of view
|
||||
public class GameBoardView {
|
||||
public final String opponentName;
|
||||
public final Piece.Color opponentColor;
|
||||
public final Boolean opponentMove;
|
||||
public final Integer moveNumber;
|
||||
public final Map<Integer, Piece> board;
|
||||
public final GameBoardCommand previousCommand;
|
||||
public final String message;
|
||||
public final UUID gameUuid;
|
||||
|
||||
/*
|
||||
* GameStatus enum:
|
||||
* - YOUR_TURN
|
||||
* - WAIT_FOR_OPPONENT
|
||||
* - VICTORY
|
||||
* - YOU_LOOSE
|
||||
*/
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public GameBoardView() {
|
||||
this.opponentName = null;
|
||||
this.opponentColor = null;
|
||||
this.opponentMove = null;
|
||||
this.moveNumber = null;
|
||||
this.board = null;
|
||||
this.previousCommand = null;
|
||||
this.message = null;
|
||||
this.gameUuid = null;
|
||||
}
|
||||
|
||||
// A view from a perspective of a concrete player, on a ledger transaction that has
|
||||
// produced new GameBoardState
|
||||
public GameBoardView(UtxoLedgerTransaction utxoGameBoard, MemberX500Name myName) throws NotInvolved {
|
||||
// TODO check command type: MOVE vs FINNISH | SURRENDER
|
||||
// SingleOutPut vs SingleInput
|
||||
this.previousCommand = UtxoLedgerTransactionUtil
|
||||
.getOptionalCommand(utxoGameBoard, GameBoardCommand.class)
|
||||
.orElseGet(() -> null); // there is no previous command for GameProposal.Accept case
|
||||
|
||||
final GameBoardState stateGameBoard = UtxoLedgerTransactionUtil
|
||||
.getSingleOutputState(utxoGameBoard, GameBoardState.class);
|
||||
|
||||
this.opponentName = stateGameBoard.getCounterpartyName(myName).getCommonName();
|
||||
this.opponentColor = stateGameBoard.getCounterpartyColor(myName);
|
||||
|
||||
this.opponentMove = this.opponentColor == stateGameBoard.getMoveColor();
|
||||
this.moveNumber = stateGameBoard.getMoveNumber();
|
||||
this.board = stateGameBoard.getBoard();
|
||||
this.message = stateGameBoard.getMessage();
|
||||
this.gameUuid = stateGameBoard.getGameUuid();
|
||||
}
|
||||
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package djmil.cordacheckers.gameboard;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.FlowResult;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameState.NotInvolved;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
public class ListFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(ListFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
|
||||
try {
|
||||
final var utxoGameBoardList = utxoLedgerService
|
||||
.findUnconsumedStatesByType(GameBoardState.class);
|
||||
// NOTE: lambda operations (aka .map) can not be used with UtxoLedgerService instances
|
||||
|
||||
final List<GameBoardView> res = prepareGameBoardViewList(utxoGameBoardList);
|
||||
|
||||
return new FlowResult(res)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("GameBoard.ListFlow failed to process utxo request body " + requestBody + " because: " + e.getMessage());
|
||||
return new FlowResult(e).toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private List<GameBoardView> prepareGameBoardViewList(List<StateAndRef<GameBoardState>> utxoGamaBoardList) {
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
List<GameBoardView> res = new LinkedList<GameBoardView>();
|
||||
|
||||
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
|
||||
.findLedgerTransaction(trxId);
|
||||
|
||||
return new GameBoardView(utxoGameBoard, myName);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package djmil.cordacheckers.gameboard;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameCommand;
|
||||
import djmil.cordacheckers.gamestate.CommitSubFlow;
|
||||
import djmil.cordacheckers.gamestate.FlowResponce;
|
||||
import djmil.cordacheckers.gamestate.GetFlow;
|
||||
import djmil.cordacheckers.gamestate.View;
|
||||
import djmil.cordacheckers.gamestate.ViewBuilder;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameResultState;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.FlowEngine;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.common.NotaryLookup;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
|
||||
public class SurrenderFlow implements ClientStartableFlow{
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(SurrenderFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@CordaInject
|
||||
public NotaryLookup notaryLookup;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
SecureHash gameStateUtxoTrxId = null;
|
||||
|
||||
try {
|
||||
final GameCommand command = new GameCommand(GameCommand.Action.GAME_BOARD_SURRENDER);
|
||||
|
||||
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody());
|
||||
|
||||
final StateAndRef<GameState> inputStateSar = this.flowEngine
|
||||
.subFlow(new GetFlow(gameStateUuid));
|
||||
|
||||
final GameResultState outputState = prepareGameResultState(inputStateSar);
|
||||
|
||||
final UtxoSignedTransaction transactionCandidate = utxoLedgerService.createTransactionBuilder()
|
||||
.addCommand(command)
|
||||
.addInputState(inputStateSar.getRef())
|
||||
.addOutputState(outputState)
|
||||
.addSignatories(outputState.getParticipants())
|
||||
.setNotary(inputStateSar.getState().getNotaryName())
|
||||
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.toSignedTransaction();
|
||||
|
||||
gameStateUtxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitSubFlow(transactionCandidate, command.getObserver(outputState)));
|
||||
|
||||
final View gameStateView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
|
||||
|
||||
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
|
||||
e.printStackTrace(System.out);
|
||||
return new FlowResponce(e, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
GameResultState prepareGameResultState(StateAndRef<GameState> gameStateSar) {
|
||||
final GameState gameState = gameStateSar.getState().getContractState();
|
||||
final GameBoardState gameBoard = (GameBoardState) gameState;
|
||||
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
final MemberX500Name winnerName = gameBoard.getCounterpartyName(myName);
|
||||
|
||||
return new GameResultState(gameBoard, winnerName);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameCommand;
|
||||
import djmil.cordacheckers.gamestate.CommitSubFlow;
|
||||
import djmil.cordacheckers.gamestate.FlowResponce;
|
||||
import djmil.cordacheckers.gamestate.GetFlow;
|
||||
import djmil.cordacheckers.gamestate.View;
|
||||
import djmil.cordacheckers.gamestate.ViewBuilder;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.FlowEngine;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
|
||||
public class AcceptFlow implements ClientStartableFlow{
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(AcceptFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
SecureHash gameStateUtxoTrxId = null;
|
||||
|
||||
try {
|
||||
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_ACCEPT);
|
||||
|
||||
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody());
|
||||
|
||||
final StateAndRef<GameState> inputState = this.flowEngine
|
||||
.subFlow(new GetFlow(gameStateUuid));
|
||||
|
||||
final GameBoardState outputState = new GameBoardState((GameProposalState)inputState.getState().getContractState());
|
||||
|
||||
final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder()
|
||||
.addCommand(command)
|
||||
.addInputState(inputState.getRef())
|
||||
.addOutputState(outputState)
|
||||
.addSignatories(inputState.getState().getContractState().getParticipants())
|
||||
.setNotary(inputState.getState().getNotaryName())
|
||||
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.toSignedTransaction();
|
||||
|
||||
gameStateUtxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState)));
|
||||
|
||||
final View gameStateView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
|
||||
|
||||
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
|
||||
e.printStackTrace(System.out);
|
||||
return new FlowResponce(e, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameCommand;
|
||||
import djmil.cordacheckers.gamestate.CommitSubFlow;
|
||||
import djmil.cordacheckers.gamestate.FlowResponce;
|
||||
import djmil.cordacheckers.gamestate.GetFlow;
|
||||
import djmil.cordacheckers.gamestate.View;
|
||||
import djmil.cordacheckers.gamestate.ViewBuilder;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.FlowEngine;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
|
||||
public class CancelFlow implements ClientStartableFlow{
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CancelFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
SecureHash gameStateUtxoTrxId = null;
|
||||
|
||||
try {
|
||||
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_CANCEL);
|
||||
|
||||
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody());
|
||||
|
||||
final StateAndRef<GameState> inputState = this.flowEngine
|
||||
.subFlow(new GetFlow(gameStateUuid));
|
||||
|
||||
final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder()
|
||||
.addCommand(command)
|
||||
.addInputState(inputState.getRef())
|
||||
.addSignatories(inputState.getState().getContractState().getParticipants())
|
||||
.setNotary(inputState.getState().getNotaryName())
|
||||
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.toSignedTransaction();
|
||||
|
||||
gameStateUtxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState)));
|
||||
|
||||
final View gameStateView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
|
||||
|
||||
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
|
||||
return new FlowResponce(e, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.FlowResult;
|
||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
||||
import djmil.cordacheckers.gameboard.GameBoardView;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import djmil.cordacheckers.states.GameState.NotInvolved;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.FlowEngine;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
public class CommandFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CommandFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
try {
|
||||
final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class);
|
||||
final GameProposalCommand command = args.getCommand();
|
||||
|
||||
final StateAndRef<GameProposalState> utxoSarGameProposal = findUtxoGameProposal(args.getGameProposalUuid());
|
||||
|
||||
final UtxoSignedTransaction utxoDraft = prepareSignedTransaction(command, utxoSarGameProposal);
|
||||
|
||||
final SecureHash utxoTrxId = this.flowEngine
|
||||
.subFlow( new CommitSubFlow(utxoDraft, command.getRespondent(utxoSarGameProposal)) );
|
||||
|
||||
if (command == GameProposalCommand.ACCEPT) {
|
||||
final GameBoardView viewGameBoard = prepareGameBoardView(utxoTrxId);
|
||||
|
||||
return new FlowResult(viewGameBoard, utxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
|
||||
return new FlowResult(command+"ED", utxoTrxId) // REJECT+ED
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +
|
||||
" because: " + e.getMessage());
|
||||
return new FlowResult(e).toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private StateAndRef<GameProposalState> findUtxoGameProposal (UUID uuidGameProposal) {
|
||||
/*
|
||||
* Get list of all unconsumed aka 'active' GameProposalStates, then filter by UUID.
|
||||
* Note, this is an inefficient way to perform this operation if there are a large
|
||||
* number of 'active' GameProposals exists in storage.
|
||||
*/
|
||||
return this.utxoLedgerService
|
||||
.findUnconsumedStatesByType(GameProposalState.class)
|
||||
.stream()
|
||||
.filter(sar -> sar.getState().getContractState().getGameUuid().equals(uuidGameProposal))
|
||||
.reduce((a, b) -> {throw new IllegalStateException("Multiple states: " +a +", " +b);})
|
||||
.get();
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private UtxoSignedTransaction prepareSignedTransaction(
|
||||
GameProposalCommand command,
|
||||
StateAndRef<GameProposalState> sarGameProposal
|
||||
) {
|
||||
UtxoTransactionBuilder trxBuilder = utxoLedgerService.createTransactionBuilder()
|
||||
.setNotary(sarGameProposal.getState().getNotaryName())
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addInputState(sarGameProposal.getRef())
|
||||
.addCommand(command)
|
||||
.addSignatories(sarGameProposal.getState().getContractState().getParticipants());
|
||||
|
||||
if (command == GameProposalCommand.ACCEPT) {
|
||||
trxBuilder = trxBuilder
|
||||
.addOutputState(new GameBoardState(sarGameProposal.getState().getContractState()));
|
||||
}
|
||||
|
||||
return trxBuilder.toSignedTransaction();
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private GameBoardView prepareGameBoardView(SecureHash utxoTrxId) throws NotInvolved {
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
|
||||
final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService
|
||||
.findLedgerTransaction(utxoTrxId);
|
||||
|
||||
return new GameBoardView(utxoGameBoard, myName);
|
||||
}
|
||||
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
||||
|
||||
public class CommandFlowArgs {
|
||||
private UUID gameProposalUuid;
|
||||
private String command;
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public CommandFlowArgs() {
|
||||
this.gameProposalUuid = null;
|
||||
this.command = null;
|
||||
}
|
||||
|
||||
public GameProposalCommand getCommand() {
|
||||
return GameProposalCommand.valueOf(this.command);
|
||||
}
|
||||
|
||||
public UUID getGameProposalUuid() {
|
||||
return this.gameProposalUuid;
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.InitiatedBy;
|
||||
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;
|
||||
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||
|
||||
@InitiatedBy(protocol = "game-proposal")
|
||||
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) {
|
||||
log.info("GameProposal: Commit responder flow");
|
||||
try {
|
||||
UtxoTransactionValidator txValidator = ledgerTransaction -> {
|
||||
final GameProposalCommand command = ledgerTransaction.getCommands(GameProposalCommand.class).get(0);
|
||||
|
||||
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,
|
||||
GameProposalCommand command
|
||||
) {
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
final MemberX500Name otherName = session.getCounterparty();
|
||||
|
||||
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 GameProposalCommand command = trx.getCommands(GameProposalCommand.class).get(0);
|
||||
|
||||
switch (command) {
|
||||
case CREATE:
|
||||
return (GameProposalState)trx.getOutputContractStates().get(0);
|
||||
|
||||
case ACCEPT:
|
||||
case REJECT:
|
||||
case CANCEL:
|
||||
return (GameProposalState)trx.getInputContractStates().get(0);
|
||||
|
||||
default:
|
||||
throw new RuntimeException(GameProposalCommand.UNSUPPORTED_VALUE_OF +command);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,20 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.FlowResult;
|
||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
||||
import djmil.cordacheckers.contracts.GameCommand;
|
||||
import djmil.cordacheckers.gamestate.CommitSubFlow;
|
||||
import djmil.cordacheckers.gamestate.FlowResponce;
|
||||
import djmil.cordacheckers.gamestate.View;
|
||||
import djmil.cordacheckers.gamestate.ViewBuilder;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import djmil.cordacheckers.states.Piece;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
@ -20,14 +30,6 @@ import net.corda.v5.ledger.common.NotaryLookup;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.membership.MemberInfo;
|
||||
import net.corda.v5.membership.NotaryInfo;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
public class CreateFlow implements ClientStartableFlow{
|
||||
|
||||
@ -43,7 +45,7 @@ public class CreateFlow implements ClientStartableFlow{
|
||||
public NotaryLookup notaryLookup;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService ledgerService;
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
@ -51,24 +53,34 @@ public class CreateFlow implements ClientStartableFlow{
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
SecureHash gameStateUtxoTrxId = null;
|
||||
|
||||
try {
|
||||
log.info("flow: Create Game Proposal");
|
||||
final GameProposalCommand command = GameProposalCommand.CREATE;
|
||||
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_CREATE);
|
||||
|
||||
final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody);
|
||||
|
||||
final UtxoSignedTransaction trx = prepareSignedTransaction(newGameProposal);
|
||||
final UtxoSignedTransaction utxoCandidate = utxoLedgerService.createTransactionBuilder()
|
||||
.addCommand(command)
|
||||
.addOutputState(newGameProposal)
|
||||
.addSignatories(newGameProposal.getParticipants())
|
||||
.setNotary(notaryLookup.getNotaryServices().iterator().next().getName())
|
||||
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.toSignedTransaction();
|
||||
|
||||
final SecureHash trxId = this.flowEngine
|
||||
.subFlow(new CommitSubFlow(trx, command.getRespondent(newGameProposal)));
|
||||
gameStateUtxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitSubFlow(utxoCandidate, command.getObserver(newGameProposal)));
|
||||
|
||||
return new FlowResult(newGameProposal.getGameUuid(), trxId)
|
||||
final View gameStateView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
|
||||
|
||||
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody +
|
||||
" because: " + e.getMessage());
|
||||
return new FlowResult(e).toJsonEncodedString(jsonMarshallingService);
|
||||
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
|
||||
return new FlowResponce(e, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,16 +111,4 @@ public class CreateFlow implements ClientStartableFlow{
|
||||
);
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private UtxoSignedTransaction prepareSignedTransaction(GameProposalState outputGameProposalState) {
|
||||
final NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next();
|
||||
|
||||
return ledgerService.createTransactionBuilder()
|
||||
.setNotary(notary.getName())
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addOutputState(outputGameProposalState)
|
||||
.addCommand(GameProposalCommand.CREATE)
|
||||
.addSignatories(outputGameProposalState.getParticipants())
|
||||
.toSignedTransaction();
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.FlowResult;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
|
||||
public class ListFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(ListFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
try {
|
||||
log.info("ListFlow.call() called");
|
||||
|
||||
// Queries the VNode's vault for unconsumed states and converts the resulting
|
||||
// List<StateAndRef<GameProposalState>> to a _serializable_ List<ListItem> DTO
|
||||
List<ListItem> unconsumedGameProposaList = utxoLedgerService
|
||||
.findUnconsumedStatesByType(GameProposalState.class)
|
||||
.stream()
|
||||
.map( stateAndRef -> new ListItem(stateAndRef.getState().getContractState()) )
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new FlowResult(unconsumedGameProposaList).toJsonEncodedString(jsonMarshallingService);
|
||||
} catch (Exception e) {
|
||||
log.warn("ListFlow failed to process utxo request body " + requestBody + " because: " + e.getMessage());
|
||||
return new FlowResult(e).toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import djmil.cordacheckers.states.Piece;
|
||||
|
||||
// Class to hold results of the List flow.
|
||||
// JsonMarshallingService can only serialize simple classes that the underlying Jackson serializer recognises,
|
||||
// hence creating a DTO style object which consists only of Strings and a UUIDs. It is possible to create custom
|
||||
// serializers for the JsonMarshallingService in the future.
|
||||
|
||||
public class ListItem {
|
||||
public final String issuer;
|
||||
public final String acquier;
|
||||
public final Piece.Color acquierColor;
|
||||
public final String message;
|
||||
public final UUID id;
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public ListItem() {
|
||||
this.issuer = null;
|
||||
this.acquier = null;
|
||||
this.acquierColor = null;
|
||||
this.message = null;
|
||||
this.id = null;
|
||||
}
|
||||
|
||||
public ListItem(GameProposalState state) {
|
||||
this.issuer = state.getIssuer().getCommonName();
|
||||
this.acquier = state.getAcquier().getCommonName();
|
||||
this.acquierColor = state.getAcquierColor();
|
||||
this.message = state.getMessage();
|
||||
this.id = state.getGameUuid();
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameCommand;
|
||||
import djmil.cordacheckers.gamestate.CommitSubFlow;
|
||||
import djmil.cordacheckers.gamestate.FlowResponce;
|
||||
import djmil.cordacheckers.gamestate.GetFlow;
|
||||
import djmil.cordacheckers.gamestate.View;
|
||||
import djmil.cordacheckers.gamestate.ViewBuilder;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.FlowEngine;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.common.NotaryLookup;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
|
||||
public class RejectFlow implements ClientStartableFlow{
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(RejectFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@CordaInject
|
||||
public NotaryLookup notaryLookup;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
SecureHash gameStateUtxoTrxId = null;
|
||||
|
||||
try {
|
||||
final GameCommand command = new GameCommand(GameCommand.Action.GAME_PROPOSAL_REJECT);
|
||||
|
||||
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody());
|
||||
|
||||
final StateAndRef<GameState> inputState = this.flowEngine
|
||||
.subFlow(new GetFlow(gameStateUuid));
|
||||
|
||||
final UtxoSignedTransaction gameStateRejectTrx = utxoLedgerService.createTransactionBuilder()
|
||||
.addCommand(command)
|
||||
.addInputState(inputState.getRef())
|
||||
.addSignatories(inputState.getState().getContractState().getParticipants())
|
||||
.setNotary(inputState.getState().getNotaryName())
|
||||
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.toSignedTransaction();
|
||||
|
||||
gameStateUtxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitSubFlow(gameStateRejectTrx, command.getObserver(inputState)));
|
||||
|
||||
final View gameStateView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
|
||||
|
||||
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
|
||||
return new FlowResponce(e, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
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.getWinnerName().getCommonName();
|
||||
this.blackPlayerName = gameResultState.getOpponentName().getCommonName();
|
||||
this.victoryColor = gameResultState.getWinnerColor();
|
||||
this.id = gameResultState.getGameUuid();
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
package djmil.cordacheckers.gamestate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@ -17,16 +17,16 @@ import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
|
||||
@InitiatingFlow(protocol = "game-proposal")
|
||||
@InitiatingFlow(protocol = "gamestate-commit")
|
||||
public class CommitSubFlow implements SubFlow<SecureHash> {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CommitSubFlow.class);
|
||||
private final UtxoSignedTransaction signedTransaction;
|
||||
private final MemberX500Name respondenName;
|
||||
private final UtxoSignedTransaction utxTrxCandidate;
|
||||
private final MemberX500Name counterpartyName;
|
||||
|
||||
public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name respondenName) {
|
||||
this.signedTransaction = signedTransaction;
|
||||
this.respondenName = respondenName;
|
||||
public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name counterpartyName) {
|
||||
this.utxTrxCandidate = signedTransaction;
|
||||
this.counterpartyName = counterpartyName;
|
||||
}
|
||||
|
||||
@CordaInject
|
||||
@ -38,9 +38,9 @@ public class CommitSubFlow implements SubFlow<SecureHash> {
|
||||
@Override
|
||||
@Suspendable
|
||||
public SecureHash call() {
|
||||
log.info("GamePropsal commit started");
|
||||
log.info("GameState commit started");
|
||||
|
||||
final FlowSession session = flowMessaging.initiateFlow(this.respondenName);
|
||||
final FlowSession session = flowMessaging.initiateFlow(this.counterpartyName);
|
||||
|
||||
/*
|
||||
* Calls the Corda provided finalise() function which gather signatures from the counterparty,
|
||||
@ -50,11 +50,11 @@ public class CommitSubFlow implements SubFlow<SecureHash> {
|
||||
final List<FlowSession> sessionsList = Arrays.asList(session);
|
||||
|
||||
final SecureHash trxId = ledgerService
|
||||
.finalize(this.signedTransaction, sessionsList)
|
||||
.finalize(this.utxTrxCandidate, sessionsList)
|
||||
.getTransaction()
|
||||
.getId();
|
||||
|
||||
log.info("GamePropsal commit " +trxId);
|
||||
log.info("GameState commit " +trxId);
|
||||
return trxId;
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package djmil.cordacheckers.gamestate;
|
||||
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameCommand;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import djmil.cordacheckers.states.GameResultState;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
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.UtxoTransactionValidator;
|
||||
|
||||
@InitiatedBy(protocol = "gamestate-commit")
|
||||
public class CommitSubFlowResponder implements ResponderFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CommitSubFlowResponder.class);
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public void call(FlowSession session) {
|
||||
UtxoTransactionValidator txValidator = trxToValidate -> {
|
||||
try {
|
||||
final GameCommand gameCommand = getSingleCommand(trxToValidate, GameCommand.class);
|
||||
final GameState gameState = getGameStateFromTransaction(trxToValidate, gameCommand);
|
||||
|
||||
checkParticipants(session, gameCommand, gameState);
|
||||
|
||||
/*
|
||||
* Other checks / actions ?
|
||||
*/
|
||||
|
||||
log.info("Approval for " + trxToValidate.getId());
|
||||
} catch (Exception e) {
|
||||
log.warn("Validation has failed. " +e.getMessage());
|
||||
// We have to re-trow an exception to indicate validation failure
|
||||
throw new CordaRuntimeException(e.getClass().getCanonicalName(), e.getMessage(), e.getCause());
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
this.utxoLedgerService
|
||||
.receiveFinality(session, txValidator);
|
||||
} catch (Exception e) {
|
||||
// Log Corda's specific message about finality validation failure
|
||||
log.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param gameStateTransaction an utxo ledger transaction, that involves GameState
|
||||
* @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)
|
||||
*/
|
||||
@Suspendable
|
||||
GameState getGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) {
|
||||
switch (command.action) {
|
||||
case GAME_PROPOSAL_CREATE:
|
||||
return getSingleOutputState(gameStateTransaction, GameProposalState.class);
|
||||
|
||||
case GAME_PROPOSAL_ACCEPT:
|
||||
case GAME_PROPOSAL_REJECT:
|
||||
case GAME_PROPOSAL_CANCEL:
|
||||
return getSingleInputState(gameStateTransaction, GameProposalState.class);
|
||||
|
||||
case GAME_BOARD_MOVE:
|
||||
return getSingleOutputState(gameStateTransaction, GameBoardState.class);
|
||||
|
||||
case GAME_BOARD_SURRENDER:
|
||||
case GAME_RESULT_CREATE:
|
||||
return getSingleOutputState(gameStateTransaction, GameResultState.class);
|
||||
}
|
||||
|
||||
throw new GameCommand.ActionException();
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
void checkParticipants(FlowSession session, GameCommand gameCommand, GameState outputGameState) 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);
|
||||
|
||||
if (outputStateConterparty.compareTo(sessionConterparty) != 0)
|
||||
throw new ParticipantException("Counterparty", sessionConterparty, outputStateConterparty);
|
||||
|
||||
if (actor.compareTo(sessionConterparty) != 0)
|
||||
throw new ParticipantException("Actor", sessionConterparty, actor);
|
||||
|
||||
if (observer.compareTo(myName) != 0)
|
||||
throw new ParticipantException("Observer", myName, observer);
|
||||
}
|
||||
|
||||
public static class ParticipantException extends Exception {
|
||||
public ParticipantException(String role, MemberX500Name expected, MemberX500Name actual) {
|
||||
super("Bad participants. " +role +" role: expected '" +expected +"', actual '" +actual +"'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,28 +1,22 @@
|
||||
package djmil.cordacheckers;
|
||||
package djmil.cordacheckers.gamestate;
|
||||
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
|
||||
public class FlowResult {
|
||||
public final Object successStatus;
|
||||
public class FlowResponce {
|
||||
public final View successStatus;
|
||||
public final SecureHash transactionId;
|
||||
public final String failureStatus;
|
||||
|
||||
public FlowResult(Object success) {
|
||||
this.successStatus = success;
|
||||
this.transactionId = null;
|
||||
this.failureStatus = null;
|
||||
}
|
||||
|
||||
public FlowResult(Object success, SecureHash transactionId) {
|
||||
public FlowResponce(View success, SecureHash transactionId) {
|
||||
this.successStatus = success;
|
||||
this.transactionId = transactionId;
|
||||
this.failureStatus = null;
|
||||
}
|
||||
|
||||
public FlowResult(Exception exception) {
|
||||
public FlowResponce(Exception exception, SecureHash transactionId) {
|
||||
this.successStatus = null;
|
||||
this.transactionId = null;
|
||||
this.transactionId = transactionId;
|
||||
this.failureStatus = exception.getMessage();
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
package djmil.cordacheckers.gamestate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.FlowEngine;
|
||||
import net.corda.v5.application.flows.SubFlow;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
|
||||
public class GetFlow implements ClientStartableFlow, SubFlow<StateAndRef<GameState>> {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(GetFlow.class);
|
||||
private UUID subflowGameStateUuid = null;
|
||||
|
||||
public GetFlow() {
|
||||
this.subflowGameStateUuid = null;
|
||||
}
|
||||
|
||||
public GetFlow(UUID gameStateUuid) {
|
||||
this.subflowGameStateUuid = gameStateUuid;
|
||||
}
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public StateAndRef<GameState> call() { // <<-- SubFlow<T> entry
|
||||
return findGameStateUtxoSar(this.subflowGameStateUuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public String call(ClientRequestBody requestBody) { // <<-- ClientStartableFlow entry
|
||||
SecureHash gameStateUtxoTrxId = null;
|
||||
|
||||
try {
|
||||
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody());
|
||||
|
||||
gameStateUtxoTrxId = findGameStateUtxoSar(gameStateUuid)
|
||||
.getRef().getTransactionId();
|
||||
|
||||
final View gameStateView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
|
||||
|
||||
return new FlowResponce(gameStateView, gameStateUtxoTrxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
|
||||
return new FlowResponce(e, gameStateUtxoTrxId).toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
StateAndRef<GameState> findGameStateUtxoSar(UUID gameStateUuid) {
|
||||
/*
|
||||
* Get list of all unconsumed aka 'active' GameStates, then filter by UUID.
|
||||
* Note, this is an inefficient way to perform this operation if there are a large
|
||||
* number of 'active' GameProposals exists in storage.
|
||||
*/
|
||||
return this.utxoLedgerService
|
||||
.findUnconsumedStatesByType(GameState.class)
|
||||
.stream()
|
||||
.filter(sar -> sar.getState().getContractState().getGameUuid().equals(gameStateUuid))
|
||||
.reduce((a, b) -> {throw new IllegalStateException("Multiple GameStates found: " +a +", " +b);})
|
||||
.get();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package djmil.cordacheckers.gamestate;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.FlowEngine;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
|
||||
public class ListFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(ListFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
try {
|
||||
List<SecureHash> gameStateUtxoTrxIdList = getGameStateUtxoTrxIdList();
|
||||
|
||||
// TODO: try in one single list
|
||||
List<View> gameStateViewList = new LinkedList<View>();
|
||||
|
||||
for (SecureHash gameStateUtxoTrxId: gameStateUtxoTrxIdList) {
|
||||
final View gameStateView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(gameStateUtxoTrxId));
|
||||
|
||||
gameStateViewList.add(gameStateView);
|
||||
}
|
||||
|
||||
return new ListFlowResponce(gameStateViewList)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
} catch (Exception e) {
|
||||
log.warn("Exception during processing " + requestBody + " request: " + e.getMessage());
|
||||
return new ListFlowResponce(e)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
List<SecureHash> getGameStateUtxoTrxIdList() {
|
||||
return utxoLedgerService
|
||||
.findUnconsumedStatesByType(GameState.class)
|
||||
.stream()
|
||||
.map( stateAndRef -> stateAndRef.getRef().getTransactionId() )
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package djmil.cordacheckers.gamestate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
|
||||
public class ListFlowResponce {
|
||||
public final List<View> successStatus;
|
||||
public final String failureStatus;
|
||||
|
||||
public ListFlowResponce(List<View> success) {
|
||||
this.successStatus = success;
|
||||
this.failureStatus = null;
|
||||
}
|
||||
|
||||
public ListFlowResponce(Exception exception) {
|
||||
this.successStatus = null;
|
||||
this.failureStatus = exception.getMessage();
|
||||
}
|
||||
|
||||
public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) {
|
||||
return jsonMarshallingService.format(this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package djmil.cordacheckers.gamestate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
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 {
|
||||
GAME_PROPOSAL_WAIT_FOR_OPPONENT,
|
||||
GAME_PROPOSAL_WAIT_FOR_YOU,
|
||||
GAME_PROPOSAL_REJECTED,
|
||||
GAME_PROPOSAL_CANCELED,
|
||||
|
||||
GAME_BOARD_WAIT_FOR_OPPONENT,
|
||||
GAME_BOARD_WAIT_FOR_YOU,
|
||||
|
||||
GAME_RESULT_YOU_WON,
|
||||
GAME_RESULT_YOU_LOOSE;
|
||||
}
|
||||
public final Status status;
|
||||
|
||||
public final String opponentName;
|
||||
public final Piece.Color opponentColor;
|
||||
|
||||
public final Map<Integer, Piece> board;
|
||||
public final Integer moveNumber;
|
||||
public final List<Integer> previousMove;
|
||||
|
||||
public final String message;
|
||||
public final UUID uuid;
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public View() {
|
||||
this.status = null;
|
||||
this.opponentName = null;
|
||||
this.opponentColor = null;
|
||||
this.board = null;
|
||||
this.moveNumber = null;
|
||||
this.previousMove = null;
|
||||
this.message = null;
|
||||
this.uuid = null;
|
||||
}
|
||||
|
||||
public View(View.Status status, GameProposalState gameProposal, MemberX500Name myName) {
|
||||
this.status = status;
|
||||
this.opponentName = gameProposal.getCounterpartyName(myName).getCommonName();
|
||||
this.opponentColor = gameProposal.getCounterpartyColor(myName);
|
||||
this.board = null;
|
||||
this.moveNumber = null;
|
||||
this.previousMove = null;
|
||||
this.message = gameProposal.getMessage();
|
||||
this.uuid = gameProposal.getGameUuid();
|
||||
}
|
||||
|
||||
public View(View.Status status, GameBoardState gameBoard, MemberX500Name myName) {
|
||||
this.status = status;
|
||||
this.opponentName = gameBoard.getCounterpartyName(myName).getCommonName();
|
||||
this.opponentColor = gameBoard.getCounterpartyColor(myName);
|
||||
this.board = gameBoard.getBoard();
|
||||
this.moveNumber = gameBoard.getMoveNumber();
|
||||
this.previousMove = null;
|
||||
this.message = gameBoard.getMessage();
|
||||
this.uuid = gameBoard.getGameUuid();
|
||||
}
|
||||
|
||||
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.board = gameBoard.getBoard();
|
||||
this.moveNumber = gameBoard.getMoveNumber();
|
||||
this.previousMove = previousMove;
|
||||
this.message = gameBoard.getMessage();
|
||||
this.uuid = gameBoard.getGameUuid();
|
||||
}
|
||||
|
||||
public View(View.Status status, GameResultState gameResult, MemberX500Name myName) {
|
||||
this.status = status;
|
||||
this.opponentName = gameResult.getCounterpartyName(myName).getCommonName();
|
||||
this.opponentColor = gameResult.getCounterpartyColor(myName);
|
||||
this.board = null;
|
||||
this.moveNumber = null;
|
||||
this.previousMove = null;
|
||||
this.message = gameResult.getMessage();
|
||||
this.uuid = gameResult.getGameUuid();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package djmil.cordacheckers.gamestate;
|
||||
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameCommand;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import djmil.cordacheckers.states.GameResultState;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.SubFlow;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
/*
|
||||
* A view on a ledger transaction that has GameState involved in it, from perspective of a concrete player
|
||||
*/
|
||||
public class ViewBuilder implements SubFlow<View> {
|
||||
private final SecureHash gameStateUtxoTrxId;
|
||||
|
||||
public ViewBuilder (SecureHash gameStateUtxoTrxId) {
|
||||
this.gameStateUtxoTrxId = gameStateUtxoTrxId;
|
||||
}
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public View call() {
|
||||
final UtxoLedgerTransaction gameStateUtxo = this.utxoLedgerService
|
||||
.findLedgerTransaction(gameStateUtxoTrxId);
|
||||
|
||||
return buildGameStateView(gameStateUtxo);
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
View buildGameStateView(UtxoLedgerTransaction gameStateUtxo) {
|
||||
MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
|
||||
final GameCommand command = getSingleCommand(gameStateUtxo, GameCommand.class);
|
||||
final GameState state = getGameStateFromTransaction(gameStateUtxo, command);
|
||||
final View.Status viewStatus = action2status(command, state, myName);
|
||||
|
||||
switch (command.action) {
|
||||
case GAME_PROPOSAL_CREATE:
|
||||
case GAME_PROPOSAL_CANCEL:
|
||||
case GAME_PROPOSAL_REJECT:
|
||||
if (state instanceof GameProposalState)
|
||||
return new View(viewStatus, (GameProposalState)state, myName);
|
||||
break;
|
||||
|
||||
case GAME_PROPOSAL_ACCEPT:
|
||||
case GAME_BOARD_MOVE:
|
||||
if (state instanceof GameBoardState)
|
||||
return new View(viewStatus, (GameBoardState)state, command.move, myName);
|
||||
break;
|
||||
|
||||
case GAME_BOARD_SURRENDER:
|
||||
case GAME_RESULT_CREATE:
|
||||
if (state instanceof GameResultState)
|
||||
return new View(viewStatus, (GameResultState)state, myName);
|
||||
break;
|
||||
}
|
||||
|
||||
throw new GameCommand.ActionException();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param gameStateTransaction an utxo ledger transaction, that involves GameState
|
||||
* @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)
|
||||
*/
|
||||
@Suspendable
|
||||
GameState getGameStateFromTransaction(UtxoLedgerTransaction gameStateTransaction, GameCommand command) {
|
||||
switch (command.action) {
|
||||
case GAME_PROPOSAL_CREATE:
|
||||
return getSingleOutputState(gameStateTransaction, GameProposalState.class);
|
||||
|
||||
case GAME_PROPOSAL_REJECT:
|
||||
case GAME_PROPOSAL_CANCEL:
|
||||
return getSingleInputState(gameStateTransaction, GameProposalState.class);
|
||||
|
||||
case GAME_PROPOSAL_ACCEPT:
|
||||
case GAME_BOARD_MOVE:
|
||||
return getSingleOutputState(gameStateTransaction, GameBoardState.class);
|
||||
|
||||
case GAME_BOARD_SURRENDER:
|
||||
case GAME_RESULT_CREATE:
|
||||
return getSingleOutputState(gameStateTransaction, GameResultState.class);
|
||||
}
|
||||
|
||||
throw new GameCommand.ActionException();
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
View.Status action2status(GameCommand command, GameState state, MemberX500Name myName) {
|
||||
final boolean myAction = command.getActor(state).compareTo(myName) == 0;
|
||||
|
||||
switch (command.action) {
|
||||
case GAME_PROPOSAL_CREATE:
|
||||
if (myAction)
|
||||
return View.Status.GAME_PROPOSAL_WAIT_FOR_OPPONENT;
|
||||
else
|
||||
return View.Status.GAME_PROPOSAL_WAIT_FOR_YOU;
|
||||
|
||||
case GAME_PROPOSAL_REJECT:
|
||||
return View.Status.GAME_PROPOSAL_REJECTED;
|
||||
|
||||
case GAME_PROPOSAL_CANCEL:
|
||||
return View.Status.GAME_PROPOSAL_CANCELED;
|
||||
|
||||
case GAME_PROPOSAL_ACCEPT:
|
||||
case GAME_BOARD_MOVE:
|
||||
if (myAction)
|
||||
return View.Status.GAME_BOARD_WAIT_FOR_YOU;
|
||||
else
|
||||
return View.Status.GAME_BOARD_WAIT_FOR_OPPONENT;
|
||||
|
||||
case GAME_BOARD_SURRENDER:
|
||||
if (myAction)
|
||||
return View.Status.GAME_RESULT_YOU_LOOSE;
|
||||
else
|
||||
return View.Status.GAME_RESULT_YOU_WON;
|
||||
|
||||
case GAME_RESULT_CREATE:
|
||||
if (myAction)
|
||||
return View.Status.GAME_RESULT_YOU_WON;
|
||||
else
|
||||
return View.Status.GAME_RESULT_YOU_LOOSE;
|
||||
}
|
||||
|
||||
throw new GameCommand.ActionException();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user