diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java index 1d7254a..e199cdd 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java @@ -19,7 +19,8 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; -import djmil.cordacheckers.cordaclient.dao.Color; +import djmil.cordacheckers.cordaclient.dao.Piece; +import djmil.cordacheckers.cordaclient.dao.GameBoard; import djmil.cordacheckers.cordaclient.dao.GameProposal; import djmil.cordacheckers.cordaclient.dao.VirtualNode; import djmil.cordacheckers.cordaclient.dao.VirtualNodeList; @@ -28,7 +29,10 @@ import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateRes; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalListRes; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq.Action; import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardListRes; +import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionAcceptRes; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionRes; @@ -68,7 +72,7 @@ public class CordaClient { public List gameProposalList(HoldingIdentity holdingIdentity) { final RequestBody requestBody = new RequestBody( - "list-" + UUID.randomUUID(), + "gp.list-" + UUID.randomUUID(), "djmil.cordacheckers.gameproposal.ListFlow", new Empty() ); @@ -88,67 +92,111 @@ public class CordaClient { } public UUID gameProposalCreate( - HoldingIdentity sender, - HoldingIdentity receiver, - Color receiverColor, + HoldingIdentity issuer, + HoldingIdentity acquier, + Piece.Color acquierColor, String message ) throws JsonMappingException, JsonProcessingException { - final GameProposalCreateReq createGameProposal = new GameProposalCreateReq( - receiver.x500Name(), - receiverColor, - message - ); - final RequestBody requestBody = new RequestBody( - "create-" + UUID.randomUUID(), + "gp.create-" + UUID.randomUUID(), "djmil.cordacheckers.gameproposal.CreateFlow", - createGameProposal + new GameProposalCreateReq( + acquier.x500Name(), + acquierColor, + message + ) ); final GameProposalCreateRes createResult = cordaFlowExecute( - sender, + issuer, requestBody, GameProposalCreateRes.class ); if (createResult.failureStatus() != null) { - System.out.println("GameProposalCreateFlow failed: " + createResult.failureStatus()); + System.out.println("GameProposal.CreateFlow failed: " + createResult.failureStatus()); throw new RuntimeException("GameProsal: CreateFlow execution has failed"); } return createResult.successStatus(); } - public String gameProposalAction( - HoldingIdentity self, - UUID gameProposalUuid, - GameProposalActionReq.Action action + public String gameProposalReject( + HoldingIdentity myHoldingIdentity, + UUID gameProposalUuid ) { - final GameProposalActionReq rejectGameProposal = new GameProposalActionReq( - gameProposalUuid.toString(), - action - ); - final RequestBody requestBody = new RequestBody( - "reject-" + UUID.randomUUID(), + "gp.reject-" +UUID.randomUUID(), "djmil.cordacheckers.gameproposal.ActionFlow", - rejectGameProposal + new GameProposalActionReq( + gameProposalUuid.toString(), + Action.REJECT + ) ); final GameProposalActionRes actionResult = cordaFlowExecute( - self, + myHoldingIdentity, requestBody, GameProposalActionRes.class ); if (actionResult.failureStatus() != null) { - System.out.println("GameProposalActionFlow failed: " + actionResult.failureStatus()); + System.out.println("GameProposal.ActionFlow failed: " + actionResult.failureStatus()); throw new RuntimeException("GameProsal: ActionFlow execution has failed"); } return actionResult.successStatus(); } + public UUID gameProposalAccept( + HoldingIdentity myHoldingIdentity, + UUID gameProposalUuid + ) { + final RequestBody requestBody = new RequestBody( + "gp.accept-" +UUID.randomUUID(), + "djmil.cordacheckers.gameproposal.ActionFlow", + new GameProposalActionReq( + gameProposalUuid.toString(), + Action.ACCEPT + ) + ); + + final GameProposalActionAcceptRes actionResult = cordaFlowExecute( + myHoldingIdentity, + requestBody, + GameProposalActionAcceptRes.class + ); + + if (actionResult.failureStatus() != null) { + System.out.println("GameProposal.ActionFlow failed: " + actionResult.failureStatus()); + throw new RuntimeException("GameProsal: ActionFlow execution has failed"); + } + + return actionResult.successStatus(); + } + + public List 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(); + } + private T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class flowResultType) { try { diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/Color.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/Color.java deleted file mode 100644 index 9211dc9..0000000 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/Color.java +++ /dev/null @@ -1,6 +0,0 @@ -package djmil.cordacheckers.cordaclient.dao; - -public enum Color { - WHITE, - BLACK -} diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/CordaState.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/CordaState.java new file mode 100644 index 0000000..48460fc --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/CordaState.java @@ -0,0 +1,7 @@ +package djmil.cordacheckers.cordaclient.dao; + +import java.util.UUID; + +public interface CordaState { + public UUID id(); +} diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameBoard.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameBoard.java new file mode 100644 index 0000000..5b76bc9 --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameBoard.java @@ -0,0 +1,17 @@ +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, + Map board, + String message, + UUID id) implements CordaState { + +} diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameProposal.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameProposal.java index c742f19..0f9b8fd 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameProposal.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/GameProposal.java @@ -8,8 +8,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; public record GameProposal( String issuer, String acquier, - Color acquierColor, + Piece.Color acquierColor, String message, - UUID id) { + UUID id) implements CordaState { } diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/Piece.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/Piece.java new file mode 100644 index 0000000..066dc93 --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/Piece.java @@ -0,0 +1,41 @@ +package djmil.cordacheckers.cordaclient.dao; + +public class Piece { + + public enum Type { + MAN, + KING, + } + + public enum Color { + WHITE, + BLACK, + } + + Color color; + Type type; + + public Piece() { + this.color = null; + this.type = null; + } + + public Piece(Color color, Type type) { + this.color = color; + this.type = type; + } + + public Color getColor() { + return color; + } + + public Type getType() { + return type; + } + + @Override + public String toString() { + return color +"." +type; + } + +} diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameBoardListRes.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameBoardListRes.java new file mode 100644 index 0000000..abf270d --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameBoardListRes.java @@ -0,0 +1,12 @@ +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 successStatus, String failureStatus) { + +} diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionAcceptRes.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionAcceptRes.java new file mode 100644 index 0000000..13a608b --- /dev/null +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalActionAcceptRes.java @@ -0,0 +1,7 @@ +package djmil.cordacheckers.cordaclient.dao.flow.arguments; + +import java.util.UUID; + +public record GameProposalActionAcceptRes(UUID successStatus, String transactionId, String failureStatus) { + +} diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateReq.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateReq.java index d9c7da4..c41d58b 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateReq.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/dao/flow/arguments/GameProposalCreateReq.java @@ -1,7 +1,7 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; -import djmil.cordacheckers.cordaclient.dao.Color; +import djmil.cordacheckers.cordaclient.dao.Piece; -public record GameProposalCreateReq(String opponentName, Color opponentColor, String message) { +public record GameProposalCreateReq(String opponentName, Piece.Color opponentColor, String message) { } diff --git a/backend/src/main/java/djmil/cordacheckers/gameproposal/GameProposalController.java b/backend/src/main/java/djmil/cordacheckers/gameproposal/GameProposalController.java index cea4c2f..12aff5e 100644 --- a/backend/src/main/java/djmil/cordacheckers/gameproposal/GameProposalController.java +++ b/backend/src/main/java/djmil/cordacheckers/gameproposal/GameProposalController.java @@ -17,7 +17,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; import djmil.cordacheckers.cordaclient.CordaClient; import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq; -import djmil.cordacheckers.cordaclient.dao.Color; +import djmil.cordacheckers.cordaclient.dao.Piece; import djmil.cordacheckers.cordaclient.dao.GameProposal; import djmil.cordacheckers.user.HoldingIdentityResolver; import djmil.cordacheckers.user.User; @@ -61,7 +61,7 @@ public class GameProposalController { final HoldingIdentity gpSender = sender.getHoldingIdentity(); // TODO: throw execption with custom type final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName()); - final Color gpReceiverColor = gpRequest.opponentColor(); + final Piece.Color gpReceiverColor = gpRequest.opponentColor(); // TODO handle expectionns here UUID newGameProposalUuid = cordaClient.gameProposalCreate( diff --git a/backend/src/test/java/djmil/cordacheckers/cordaclient/CordaClientTest.java b/backend/src/test/java/djmil/cordacheckers/cordaclient/CordaClientTest.java index d5fe9d8..a1e8ddc 100644 --- a/backend/src/test/java/djmil/cordacheckers/cordaclient/CordaClientTest.java +++ b/backend/src/test/java/djmil/cordacheckers/cordaclient/CordaClientTest.java @@ -1,19 +1,5 @@ package djmil.cordacheckers.cordaclient; -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.Color; -import djmil.cordacheckers.cordaclient.dao.GameProposal; -import djmil.cordacheckers.cordaclient.dao.VirtualNode; -import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq.Action; -import djmil.cordacheckers.user.HoldingIdentityResolver; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -22,6 +8,20 @@ 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.CordaState; +import djmil.cordacheckers.cordaclient.dao.GameBoard; +import djmil.cordacheckers.cordaclient.dao.GameProposal; +import djmil.cordacheckers.cordaclient.dao.Piece; +import djmil.cordacheckers.cordaclient.dao.VirtualNode; +import djmil.cordacheckers.user.HoldingIdentityResolver; + @SpringBootTest public class CordaClientTest { @Autowired @@ -49,7 +49,7 @@ public class CordaClientTest { void testGemeProposalCreate() throws JsonMappingException, JsonProcessingException { final String gpIssuer = "alice"; final String gpAcquier = "bob"; - final Color gpAcquierColor = Color.WHITE; + final Piece.Color gpAcquierColor = Piece.Color.WHITE; final String gpMessage = "GameProposal create test"; final UUID createdGpUuid = cordaClient.gameProposalCreate( @@ -78,58 +78,118 @@ public class CordaClientTest { @Test void testGemeProposalReject() throws JsonMappingException, JsonProcessingException { - final String gpSender = "alice"; - final String gpReceiver = "bob"; - final Color gpReceiverColor = Color.WHITE; + 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(gpSender), - holdingIdentityResolver.getByUsername(gpReceiver), + holdingIdentityResolver.getByUsername(gpIssuer), + holdingIdentityResolver.getByUsername(gpAcquier), gpReceiverColor, gpMessage ); System.out.println("Create GP UUID "+ gpUuid); - assertThatThrownBy(() -> { - cordaClient.gameProposalAction( - holdingIdentityResolver.getByUsername(gpSender), - gpUuid, - Action.REJECT); + assertThatThrownBy(() -> { // Issuer can not reject + cordaClient.gameProposalReject( + holdingIdentityResolver.getByUsername(gpIssuer), + gpUuid); }); - final String rejectRes = cordaClient.gameProposalAction( - holdingIdentityResolver.getByUsername(gpReceiver), - gpUuid, - Action.REJECT + final String rejectRes = cordaClient.gameProposalReject( + holdingIdentityResolver.getByUsername(gpAcquier), + gpUuid ); assertThat(rejectRes).isEqualToIgnoringCase("REJECTED"); List gpListSender = cordaClient.gameProposalList( - holdingIdentityResolver.getByUsername(gpSender)); + holdingIdentityResolver.getByUsername(gpIssuer)); assertThat(findByUuid(gpListSender, gpUuid)).isNull(); List gpListReceiver = cordaClient.gameProposalList( - holdingIdentityResolver.getByUsername(gpReceiver)); + holdingIdentityResolver.getByUsername(gpAcquier)); assertThat(findByUuid(gpListReceiver, gpUuid)).isNull(); // GameProposal can not be rejected twice assertThatThrownBy(() -> { - cordaClient.gameProposalAction( - holdingIdentityResolver.getByUsername(gpSender), - gpUuid, - Action.REJECT); + cordaClient.gameProposalReject( + holdingIdentityResolver.getByUsername(gpIssuer), + gpUuid); }); } - private GameProposal findByUuid(List gpList, UUID uuid) { - for (GameProposal gameProposal : gpList) { - if (gameProposal.id().compareTo(uuid) == 0) - return gameProposal; + @Test + void testGemeProposalAccept() 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 UUID newGameBoardId = cordaClient.gameProposalAccept( + holdingIdentityResolver.getByUsername(gpAcquier), + gpUuid + ); + + System.out.println("New GameBoard UUID "+newGameBoardId); + + List gbListIssuer = cordaClient.gameBoardList( + holdingIdentityResolver.getByUsername(gpIssuer)); + + GameBoard gbAlice = findByUuid(gbListIssuer, newGameBoardId); + assertThat(gbAlice).isNotNull(); + assertThat(gbAlice.opponentName()).isEqualToIgnoringCase(gpAcquier); + assertThat(gbAlice.opponentColor()).isEqualByComparingTo(gpAcquierColor); + assertThat(gbAlice.opponentMove()).isEqualTo(true); + + + List gbListAcquier = cordaClient.gameBoardList( + holdingIdentityResolver.getByUsername(gpAcquier)); + + GameBoard bgBob = findByUuid(gbListAcquier, newGameBoardId); + 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 testGemeProposalList() throws JsonMappingException, JsonProcessingException { + List gbList = cordaClient.gameBoardList( + holdingIdentityResolver.getByUsername("bob")); + + System.out.println("GB list: " +gbList); + } + + private T findByUuid(List statesList, UUID uuid) { + for (T state : statesList) { + if (state.id().compareTo(uuid) == 0) + return state; }; return null; } diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java new file mode 100644 index 0000000..c3347a2 --- /dev/null +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameBoardContract.java @@ -0,0 +1,69 @@ +package djmil.cordacheckers.contracts; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import djmil.cordacheckers.states.GameProposalState; +import net.corda.v5.base.annotations.Suspendable; +import net.corda.v5.base.exceptions.CordaRuntimeException; +import net.corda.v5.ledger.utxo.Command; +import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; + +import static djmil.cordacheckers.contracts.GameProposalContract.Action; + +public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract { + + private final static Logger log = LoggerFactory.getLogger(GameBoardContract.class); + + public static class Move implements Command { } + public static class Surrender implements Command { } + public static class ClaimVictory implements Command { } + public static class ProposeDraw implements Command { } + + @Override + public void verify(UtxoLedgerTransaction trx) { + log.info("GameBoardContract.verify()"); + + requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); + Command command = trx.getCommands().get(0); + } + + @Suspendable + public static GameProposalState getReferanceGameProposalState(UtxoLedgerTransaction trx) { + final List commandList = trx.getCommands(); + requireThat(commandList.size() == 1, REQUIRE_SINGLE_COMMAND); + + final Command command = commandList.get(0); + + if (command instanceof Action && (Action)command == Action.ACCEPT) { + return trx.getInputStates(GameProposalState.class) + .stream() + .reduce( (a, b) -> {throw new IllegalStateException(SINGLE_STATE_EXPECTED);} ) + .get(); + + } else + if (command instanceof GameBoardContract.Move) + { + List refStates = trx.getReferenceStates(GameProposalState.class); + if (refStates.size() == 1) { + return (GameProposalState) refStates.get(0); + } + } + + throw new IllegalStateException(NO_REFERANCE_GAMEPROPOSAL_STATE_FOR_TRXID +trx.getId()); + } + + 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 UNKNOWN_COMMAND = "Unsupported 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 "; +} diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java index 9d70f7d..11c418d 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/contracts/GameProposalContract.java @@ -3,7 +3,7 @@ package djmil.cordacheckers.contracts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -//import djmil.cordacheckers.states.GameProposalResolutionState; +import djmil.cordacheckers.states.GameBoardState; import djmil.cordacheckers.states.GameProposalState; import net.corda.v5.base.exceptions.CordaRuntimeException; import net.corda.v5.base.types.MemberX500Name; @@ -47,11 +47,11 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { } } - public MemberX500Name getSender(StateAndRef utxoGameProposal) { + public MemberX500Name getInitiator(StateAndRef utxoGameProposal) { return getInitiator(utxoGameProposal.getState().getContractState()); } - public MemberX500Name getReceiver(StateAndRef utxoGameProposal) { + public MemberX500Name getRespondent(StateAndRef utxoGameProposal) { return getRespondent(utxoGameProposal.getState().getContractState()); } @@ -64,7 +64,7 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); - switch ((Action)(trx.getCommands().get(0))) { + switch ((trx.getCommands(Action.class).get(0))) { case CREATE: { requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE); @@ -72,12 +72,21 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0); requireThat(outputState != null, CREATE_OUTPUT_STATE); - requireThat(outputState.getRecipientColor() != null, NON_NULL_RECIPIENT_COLOR); + requireThat(outputState.getAcquierColor() != null, NON_NULL_RECIPIENT_COLOR); break; } case ACCEPT: - throw new CordaRuntimeException("Unimplemented!"); - //break; + requireThat(trx.getInputContractStates().size() == 1, ACCEPT_INPUT_STATE); + requireThat(trx.getOutputContractStates().size() == 1, ACCEPT_OUTPUT_STATE); + + GameProposalState inGameProposal = trx.getInputStates(GameProposalState.class).get(0); + requireThat(inGameProposal != null, ACCEPT_INPUT_STATE); + + GameBoardState outGameBoard = trx.getOutputStates(GameBoardState.class).get(0); + requireThat(outGameBoard != null, ACCEPT_INPUT_STATE); + + requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), ACCEPT_PARTICIPANTS); + break; case REJECT: requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE); @@ -114,4 +123,8 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { 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"; } diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java new file mode 100644 index 0000000..856446e --- /dev/null +++ b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameBoardState.java @@ -0,0 +1,93 @@ +package djmil.cordacheckers.states; + +import java.security.PublicKey; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import djmil.cordacheckers.contracts.GameBoardContract; +import djmil.cordacheckers.states.Piece.Color; +import net.corda.v5.base.annotations.ConstructorForDeserialization; +import net.corda.v5.ledger.utxo.BelongsToContract; +import net.corda.v5.ledger.utxo.ContractState; +import net.corda.v5.ledger.utxo.StateAndRef; + +@BelongsToContract(GameBoardContract.class) +public class GameBoardState implements ContractState { + + Map board; + Piece.Color moveColor; + String message; + UUID id; + List participants; + + public GameBoardState( + StateAndRef utxoGameBoard + ) { + this.board = new LinkedHashMap(initialBoard); + this.moveColor = Piece.Color.WHITE; + this.message = null; + this.id = UUID.randomUUID(); + this.participants = utxoGameBoard.getState().getContractState().getParticipants(); + } + + @ConstructorForDeserialization + public GameBoardState(Map board, Color moveColor, String message, UUID id, + List participants) { + this.board = board; + this.moveColor = moveColor; + this.message = message; + this.id = id; + this.participants = participants; + } + + public Map getBoard() { + return board; + } + + public Piece.Color getMoveColor() { + return moveColor; + } + + public String getMessage() { + return message; + } + + public UUID getId() { + return id; + } + + public List getParticipants() { + return participants; + } + + public final static Map initialBoard = Map.ofEntries( + // Inspired by Checkers notation rules: https://www.bobnewell.net/nucleus/checkers.php + Map.entry( 1, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry( 2, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry( 3, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry( 4, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry( 5, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry( 6, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry( 7, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry( 8, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry( 9, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry(10, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry(11, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + Map.entry(12, new Piece(Piece.Color.BLACK, Piece.Type.MAN)), + + Map.entry(21, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(22, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(23, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(24, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(25, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(26, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(27, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(28, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(29, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(30, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(31, new Piece(Piece.Color.WHITE, Piece.Type.MAN)), + Map.entry(32, new Piece(Piece.Color.WHITE, Piece.Type.MAN)) + ); +} diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameProposalState.java b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameProposalState.java index 764976b..9ccf4d6 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/states/GameProposalState.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/states/GameProposalState.java @@ -15,24 +15,23 @@ public class GameProposalState implements ContractState { MemberX500Name issuer; MemberX500Name acquier; - Piece.Color recipientColor; + Piece.Color acquierColor; String message; UUID id; List participants; - // Allows serialisation and to use a specified UUID @ConstructorForDeserialization public GameProposalState( MemberX500Name issuer, MemberX500Name acquier, - Piece.Color recipientColor, + Piece.Color acquierColor, String message, UUID id, List participants ) { this.issuer = issuer; this.acquier = acquier; - this.recipientColor = recipientColor; + this.acquierColor = acquierColor; this.message = message; this.id = id; this.participants = participants; @@ -46,8 +45,8 @@ public class GameProposalState implements ContractState { return acquier; } - public Piece.Color getRecipientColor() { - return recipientColor; + public Piece.Color getAcquierColor() { + return acquierColor; } public String getMessage() { @@ -61,4 +60,29 @@ public class GameProposalState implements ContractState { public List getParticipants() { return participants; } + + public MemberX500Name getWhitePlayerName() { + return acquierColor == Piece.Color.WHITE ? getAcquier() : getIssuer(); + } + + public MemberX500Name getBlackPlayerName() { + return acquierColor == Piece.Color.BLACK ? getAcquier() : getIssuer(); + } + + public MemberX500Name getOpponentName(MemberX500Name myName) { + if (issuer.compareTo(myName) == 0) + return acquier; + + if (acquier.compareTo(myName) == 0) + return issuer; + + throw new RuntimeException(myName +" seems to be not involved in " + id +" game"); + } + + public Piece.Color getOpponentColor(MemberX500Name myName) { + final MemberX500Name opponentName = getOpponentName(myName); + + return getWhitePlayerName().compareTo(opponentName) == 0 ? Piece.Color.WHITE : Piece.Color.BLACK; + } + } diff --git a/corda/contracts/src/main/java/djmil/cordacheckers/states/Piece.java b/corda/contracts/src/main/java/djmil/cordacheckers/states/Piece.java index 634ae06..e549590 100644 --- a/corda/contracts/src/main/java/djmil/cordacheckers/states/Piece.java +++ b/corda/contracts/src/main/java/djmil/cordacheckers/states/Piece.java @@ -1,5 +1,6 @@ package djmil.cordacheckers.states; +import net.corda.v5.base.annotations.ConstructorForDeserialization; import net.corda.v5.base.annotations.CordaSerializable; @CordaSerializable @@ -20,5 +21,18 @@ public class Piece { Color color; Type type; - + @ConstructorForDeserialization + public Piece(Color color, Type type) { + this.color = color; + this.type = type; + } + + public Color getColor() { + return color; + } + + public Type getType() { + return type; + } + } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/ListFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/ListFlow.java new file mode 100644 index 0000000..cedd1e8 --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/ListFlow.java @@ -0,0 +1,80 @@ +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.contracts.GameBoardContract; +import djmil.cordacheckers.states.GameBoardState; +import djmil.cordacheckers.states.GameProposalState; +import net.corda.v5.application.flows.ClientRequestBody; +import net.corda.v5.application.flows.ClientStartableFlow; +import net.corda.v5.application.flows.CordaInject; +import net.corda.v5.application.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 unconsumedGameBoardList = utxoLedgerService + .findUnconsumedStatesByType(GameBoardState.class); + + List res = new LinkedList(); + for (StateAndRef gbState :unconsumedGameBoardList) { + // NOTE: lambda operations (aka .map) can not be used with UtxoLedgerService instances + res.add(prepareListItem(gbState)); + } + + 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 ListItem prepareListItem(StateAndRef stateAndRef) { + final MemberX500Name myName = memberLookup.myInfo().getName(); + + final SecureHash trxId = stateAndRef.getRef().getTransactionId(); + + final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService + .findLedgerTransaction(trxId); + + final GameProposalState referanceGameProposal = GameBoardContract + .getReferanceGameProposalState(utxoGameBoard); + + return new ListItem( + stateAndRef.getState().getContractState(), + referanceGameProposal.getOpponentName(myName), + referanceGameProposal.getOpponentColor(myName) + ); + } + +} diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/ListItem.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/ListItem.java new file mode 100644 index 0000000..70ecadb --- /dev/null +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameboard/ListItem.java @@ -0,0 +1,41 @@ +package djmil.cordacheckers.gameboard; + +import java.util.UUID; + +import djmil.cordacheckers.states.GameBoardState; +import djmil.cordacheckers.states.Piece; +import net.corda.v5.base.types.MemberX500Name; + +// Class to hold results of the List flow. +// JsonMarshallingService can only serialize simple classes that the underlying Jackson serializer recognises, +// hence creating a DTO style object which consists only of Strings and a UUIDs. It is possible to create custom +// serializers for the JsonMarshallingService in the future. + +public class ListItem { + public final String opponentName; + public final Piece.Color opponentColor; + public final Boolean opponentMove; + public final Object board; + public final String message; + public final UUID id; + + // Serialisation service requires a default constructor + public ListItem() { + this.opponentName = null; + this.opponentColor = null; + this.opponentMove = null; + this.board = null; + this.message = null; + this.id = null; + } + + public ListItem(GameBoardState gameBoard, MemberX500Name opponentName, Piece.Color opponentColor) { + this.opponentName = opponentName.getCommonName(); + this.opponentColor = opponentColor; + this.opponentMove = opponentColor == gameBoard.getMoveColor(); + this.board = gameBoard.getBoard(); + this.message = gameBoard.getMessage(); + this.id = gameBoard.getId(); + } + +} diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlow.java index 05116fc..a2bbd8d 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ActionFlow.java @@ -1,13 +1,12 @@ package djmil.cordacheckers.gameproposal; -import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import djmil.cordacheckers.FlowResult; +import djmil.cordacheckers.states.GameBoardState; import djmil.cordacheckers.states.GameProposalState; import net.corda.v5.application.flows.ClientRequestBody; import net.corda.v5.application.flows.ClientStartableFlow; @@ -15,7 +14,6 @@ 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.base.exceptions.CordaRuntimeException; import net.corda.v5.crypto.SecureHash; import net.corda.v5.ledger.utxo.StateAndRef; import net.corda.v5.ledger.utxo.UtxoLedgerService; @@ -47,12 +45,19 @@ public class ActionFlow implements ClientStartableFlow { final ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class); final Action action = args.getAction(); - final StateAndRef inputState = findUnconsumedGameProposalState(args.getGameProposalUuid()); + final StateAndRef utxoGameProposal = findUnconsumedGameProposalState(args.getGameProposalUuid()); - final UtxoSignedTransaction trx = prepareSignedTransaction(action, inputState); + final UtxoSignedTransaction trx = prepareSignedTransaction(action, utxoGameProposal); final SecureHash trxId = this.flowEngine - .subFlow( new CommitSubFlow(trx, action.getReceiver(inputState)) ); + .subFlow( new CommitSubFlow(trx, action.getRespondent(utxoGameProposal)) ); + + if (action == Action.ACCEPT) { + GameBoardState newGb = (GameBoardState)trx.getOutputStateAndRefs().get(0).getState().getContractState(); + + return new FlowResult(newGb.getId(), trxId) + .toJsonEncodedString(jsonMarshallingService); + } return new FlowResult(action+"ED", trxId) // REJECT+ED .toJsonEncodedString(jsonMarshallingService); @@ -71,36 +76,31 @@ public class ActionFlow implements ClientStartableFlow { * Note, this is an inefficient way to perform this operation if there are a large * number of 'active' GameProposals exists in storage. */ - List> stateAndRefs = this.ledgerService + return this.ledgerService .findUnconsumedStatesByType(GameProposalState.class) .stream() .filter(sar -> sar.getState().getContractState().getId().equals(gameProposalUuid)) - .collect(Collectors.toList()); - - if (stateAndRefs.size() != 1) { - throw new CordaRuntimeException("Expected only one GameProposal state with id " + gameProposalUuid + - ", but found " + stateAndRefs.size()); - } - - return stateAndRefs.get(0); + .reduce((a, b) -> {throw new IllegalStateException("Multiple states: " +a +", " +b);}) + .get(); } @Suspendable private UtxoSignedTransaction prepareSignedTransaction( Action action, - StateAndRef inputState + StateAndRef utxoGameProposal ) { UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder() - .setNotary(inputState.getState().getNotaryName()) + .setNotary(utxoGameProposal.getState().getNotaryName()) .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) - .addInputState(inputState.getRef()) + .addInputState(utxoGameProposal.getRef()) .addCommand(action) - .addSignatories(inputState.getState().getContractState().getParticipants()); + .addSignatories(utxoGameProposal.getState().getContractState().getParticipants()); if (action == Action.ACCEPT) { - // TODO - // .addOutputState(outputState) - throw new RuntimeException(action +" unimplemented"); + trxBuilder = trxBuilder + .addOutputState(new GameBoardState(utxoGameProposal)); + //A state cannot be both an input and a reference input in the same transaction + //.addReferenceState(utxoGameProposal.getRef()); } return trxBuilder.toSignedTransaction(); diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java index ee41ec6..e1a57e8 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CommitResponderFlow.java @@ -21,6 +21,7 @@ import static djmil.cordacheckers.contracts.GameProposalContract.Action; @InitiatedBy(protocol = "game-proposal") public class CommitResponderFlow implements ResponderFlow { + private final static Logger log = LoggerFactory.getLogger(CommitResponderFlow.class); @CordaInject @@ -38,7 +39,7 @@ public class CommitResponderFlow implements ResponderFlow { final GameProposalState gameProposal = getGameProposal(ledgerTransaction); - checkSessionParticipants(session, gameProposal, action); + checkParticipants(session, gameProposal, action); /* * Other checks / actions ? @@ -59,7 +60,7 @@ public class CommitResponderFlow implements ResponderFlow { } @Suspendable - void checkSessionParticipants( + void checkParticipants( FlowSession session, GameProposalState gameProposal, Action action @@ -90,6 +91,6 @@ public class CommitResponderFlow implements ResponderFlow { default: throw new RuntimeException(Action.UNSUPPORTED_ACTION_VALUE_OF +action); } - } + } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java index 9b3236f..076a5d8 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/CreateFlow.java @@ -10,7 +10,6 @@ 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.InitiatingFlow; import net.corda.v5.application.marshalling.JsonMarshallingService; import net.corda.v5.application.membership.MemberLookup; import net.corda.v5.base.annotations.Suspendable; diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ListFlow.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ListFlow.java index 6dfb7cc..1d5f85f 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ListFlow.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ListFlow.java @@ -29,7 +29,7 @@ public class ListFlow implements ClientStartableFlow { @Override public String call(ClientRequestBody requestBody) { try { - log.info("ListChatsFlow.call() called"); + log.info("ListFlow.call() called"); // Queries the VNode's vault for unconsumed states and converts the resulting // List> to a _serializable_ List DTO @@ -41,7 +41,7 @@ public class ListFlow implements ClientStartableFlow { return new FlowResult(unconsumedGameProposaList).toJsonEncodedString(jsonMarshallingService); } catch (Exception e) { - log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody + " because: " + e.getMessage()); + log.warn("ListFlow failed to process utxo request body " + requestBody + " because: " + e.getMessage()); return new FlowResult(e).toJsonEncodedString(jsonMarshallingService); } } diff --git a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ListItem.java b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ListItem.java index 9b9482d..f863500 100644 --- a/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ListItem.java +++ b/corda/workflows/src/main/java/djmil/cordacheckers/gameproposal/ListItem.java @@ -3,16 +3,17 @@ 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. -// The GameProposal(s) cannot be returned directly as the 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 UUID. It is possible to create custom serializers for the JsonMarshallingService in the future. +// 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 String acquierColor; + public final Piece.Color acquierColor; public final String message; public final UUID id; @@ -28,7 +29,7 @@ public class ListItem { public ListItem(GameProposalState state) { this.issuer = state.getIssuer().getCommonName(); this.acquier = state.getAcquier().getCommonName(); - this.acquierColor = state.getRecipientColor().name(); + this.acquierColor = state.getAcquierColor(); this.message = state.getMessage(); this.id = state.getId(); }