Compare commits
4 Commits
7a2a366dd5
...
218482034d
Author | SHA1 | Date | |
---|---|---|---|
218482034d | |||
4f5a636909 | |||
abc31d4c03 | |||
beadaba27e |
@ -22,7 +22,9 @@ import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
|||||||
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
|
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.RequestBody;
|
import djmil.cordacheckers.cordaclient.dao.flow.RequestBody;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody;
|
import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody;
|
||||||
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.CreateGameProposal;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
|
||||||
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalAction;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class CordaClient {
|
public class CordaClient {
|
||||||
@ -63,23 +65,63 @@ public class CordaClient {
|
|||||||
"list-" + UUID.randomUUID(),
|
"list-" + UUID.randomUUID(),
|
||||||
"djmil.cordacheckers.gameproposal.ListFlow",
|
"djmil.cordacheckers.gameproposal.ListFlow",
|
||||||
new Empty()
|
new Empty()
|
||||||
);
|
);
|
||||||
|
|
||||||
final String gameProposalsJsonString = cordaFlowExecute(
|
final String gameProposalsJsonString = cordaFlowExecute(
|
||||||
holdingIdentity,
|
holdingIdentity,
|
||||||
requestBody
|
requestBody
|
||||||
);
|
);
|
||||||
|
|
||||||
return gameProposalsJsonString;
|
return gameProposalsJsonString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String sendGameProposal(
|
public String createGameProposal(
|
||||||
HoldingIdentity sender,
|
HoldingIdentity sender,
|
||||||
HoldingIdentity receiver,
|
HoldingIdentity receiver,
|
||||||
Color receiverColor,
|
Color receiverColor,
|
||||||
String message
|
String message
|
||||||
) {
|
) {
|
||||||
return "";
|
final CreateGameProposal createGameProposal = new CreateGameProposal(
|
||||||
|
receiver.x500Name(),
|
||||||
|
receiverColor,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
|
||||||
|
final RequestBody requestBody = new RequestBody(
|
||||||
|
"create-" + UUID.randomUUID(),
|
||||||
|
"djmil.cordacheckers.gameproposal.CreateFlow",
|
||||||
|
createGameProposal
|
||||||
|
);
|
||||||
|
|
||||||
|
final String createdGameProposalUuid = cordaFlowExecute(
|
||||||
|
sender,
|
||||||
|
requestBody
|
||||||
|
);
|
||||||
|
|
||||||
|
return createdGameProposalUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String rejectGameProposal(
|
||||||
|
HoldingIdentity self,
|
||||||
|
String gameProposalUuid
|
||||||
|
) {
|
||||||
|
final GameProposalAction rejectGameProposal = new GameProposalAction(
|
||||||
|
gameProposalUuid,
|
||||||
|
GameProposalAction.Action.REJECT
|
||||||
|
);
|
||||||
|
|
||||||
|
final RequestBody requestBody = new RequestBody(
|
||||||
|
"reject-" + UUID.randomUUID(),
|
||||||
|
"djmil.cordacheckers.gameproposal.ActionFlow",
|
||||||
|
rejectGameProposal
|
||||||
|
);
|
||||||
|
|
||||||
|
final String createdGameProposalUuid = cordaFlowExecute(
|
||||||
|
self,
|
||||||
|
requestBody
|
||||||
|
);
|
||||||
|
|
||||||
|
return createdGameProposalUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody) {
|
private String cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||||
|
|
||||||
public record CreateGameProposal(String opponentName, String opponentColor, String additionalMessage) {
|
import djmil.cordacheckers.cordaclient.dao.Color;
|
||||||
|
|
||||||
|
public record CreateGameProposal(String opponentName, Color opponentColor, String message) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||||
|
|
||||||
|
public record GameProposalAction(String gameProposalUuid, Action action) {
|
||||||
|
public enum Action {
|
||||||
|
ACCEPT,
|
||||||
|
REJECT,
|
||||||
|
CANCEL
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
package djmil.cordacheckers.gameproposal;
|
|
||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.Color;
|
|
||||||
|
|
||||||
public record GameProposal(
|
|
||||||
String sender,
|
|
||||||
String recipient,
|
|
||||||
Color recipientColor,
|
|
||||||
String message)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package djmil.cordacheckers.gameproposal;
|
package djmil.cordacheckers.gameproposal;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -12,7 +11,9 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.CordaClient;
|
import djmil.cordacheckers.cordaclient.CordaClient;
|
||||||
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
||||||
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.CreateGameProposal;
|
||||||
import djmil.cordacheckers.cordaclient.dao.Color;
|
import djmil.cordacheckers.cordaclient.dao.Color;
|
||||||
|
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
||||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||||
import djmil.cordacheckers.user.User;
|
import djmil.cordacheckers.user.User;
|
||||||
|
|
||||||
@ -49,17 +50,15 @@ public class GameProposalController {
|
|||||||
@PostMapping()
|
@PostMapping()
|
||||||
public ResponseEntity<Void> createGameProposal(
|
public ResponseEntity<Void> createGameProposal(
|
||||||
@AuthenticationPrincipal User sender,
|
@AuthenticationPrincipal User sender,
|
||||||
@RequestBody GameProposal gpRequest,
|
@RequestBody CreateGameProposal gpRequest,
|
||||||
UriComponentsBuilder ucb
|
UriComponentsBuilder ucb
|
||||||
) {
|
) {
|
||||||
|
|
||||||
//sender.get
|
|
||||||
final HoldingIdentity gpSender = sender.getHoldingIdentity();
|
final HoldingIdentity gpSender = sender.getHoldingIdentity();
|
||||||
// TODO: throw execption with custom type
|
// TODO: throw execption with custom type
|
||||||
final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.recipient());
|
final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName());
|
||||||
final Color gpReceiverColor = gpRequest.recipientColor();
|
final Color gpReceiverColor = gpRequest.opponentColor();
|
||||||
|
|
||||||
String newGameProposalUuid = cordaClient.sendGameProposal(
|
String newGameProposalUuid = cordaClient.createGameProposal(
|
||||||
gpSender,
|
gpSender,
|
||||||
gpReceiver,
|
gpReceiver,
|
||||||
gpReceiverColor,
|
gpReceiverColor,
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package djmil.cordacheckers;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class GameProposalControllerTests {
|
||||||
|
@Test
|
||||||
|
void testFindAllUnconsumed() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,9 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.cordaclient.dao.Color;
|
||||||
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
||||||
|
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ -31,11 +33,67 @@ public class CordaClientTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testListGameProposals() throws JsonProcessingException {
|
void testGameProposalList() throws JsonProcessingException {
|
||||||
String resp = cordaClient.listGameProposals(
|
String resp = cordaClient.listGameProposals(
|
||||||
holdingIdentityResolver.getByCommonName("alice"));
|
holdingIdentityResolver.getByUsername("alice"));
|
||||||
|
|
||||||
System.out.println("testListGameProposals "+ resp);
|
System.out.println("testListGameProposals "+ resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGemeProposalCreate() {
|
||||||
|
final String gpSender = "alice";
|
||||||
|
final String gpReceiver = "bob";
|
||||||
|
final Color gpReceiverColor = Color.WHITE;
|
||||||
|
final String gpMessage = "GameProposal create test";
|
||||||
|
|
||||||
|
final String gpUuid = cordaClient.createGameProposal(
|
||||||
|
holdingIdentityResolver.getByUsername(gpSender),
|
||||||
|
holdingIdentityResolver.getByUsername(gpReceiver),
|
||||||
|
gpReceiverColor,
|
||||||
|
gpMessage
|
||||||
|
);
|
||||||
|
|
||||||
|
String listResSender = cordaClient.listGameProposals(
|
||||||
|
holdingIdentityResolver.getByUsername(gpSender));
|
||||||
|
|
||||||
|
String listResReceiver = cordaClient.listGameProposals(
|
||||||
|
holdingIdentityResolver.getByUsername(gpReceiver));
|
||||||
|
|
||||||
|
assertThat(listResSender).contains(gpUuid);
|
||||||
|
assertThat(listResReceiver).contains(gpUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGemeProposalReject() {
|
||||||
|
final String gpSender = "alice";
|
||||||
|
final String gpReceiver = "bob";
|
||||||
|
final Color gpReceiverColor = Color.WHITE;
|
||||||
|
final String gpMessage = "GameProposal create test";
|
||||||
|
|
||||||
|
final String gpUuid = cordaClient.createGameProposal(
|
||||||
|
holdingIdentityResolver.getByUsername(gpSender),
|
||||||
|
holdingIdentityResolver.getByUsername(gpReceiver),
|
||||||
|
gpReceiverColor,
|
||||||
|
gpMessage
|
||||||
|
);
|
||||||
|
|
||||||
|
System.out.println("Create GP UUID "+ gpUuid);
|
||||||
|
|
||||||
|
String listResSender = cordaClient.listGameProposals(
|
||||||
|
holdingIdentityResolver.getByUsername(gpSender));
|
||||||
|
|
||||||
|
String listResReceiver = cordaClient.listGameProposals(
|
||||||
|
holdingIdentityResolver.getByUsername(gpReceiver));
|
||||||
|
|
||||||
|
assertThat(listResSender).contains(gpUuid);
|
||||||
|
assertThat(listResReceiver).contains(gpUuid);
|
||||||
|
|
||||||
|
final String rejectRes = cordaClient.rejectGameProposal(
|
||||||
|
holdingIdentityResolver.getByUsername(gpReceiver),
|
||||||
|
"1ed70601-c79a-486d-b907-8537f317083a"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(rejectRes).isEqualToIgnoringCase("REJECTED");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package djmil.cordacheckers.contracts;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.states.GameProposalResolutionState;
|
||||||
import djmil.cordacheckers.states.GameProposalState;
|
import djmil.cordacheckers.states.GameProposalState;
|
||||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||||
import net.corda.v5.ledger.utxo.Command;
|
import net.corda.v5.ledger.utxo.Command;
|
||||||
@ -15,7 +16,7 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
|
|||||||
public static class Create implements Command { }
|
public static class Create implements Command { }
|
||||||
public static class Accept implements Command { }
|
public static class Accept implements Command { }
|
||||||
public static class Reject implements Command { }
|
public static class Reject implements Command { }
|
||||||
public static class Cancle implements Command { }
|
public static class Cancel implements Command { }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verify(UtxoLedgerTransaction trx) {
|
public void verify(UtxoLedgerTransaction trx) {
|
||||||
@ -31,7 +32,7 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
|
|||||||
GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0);
|
GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0);
|
||||||
requireThat(outputState != null, CREATE_OUTPUT_STATE);
|
requireThat(outputState != null, CREATE_OUTPUT_STATE);
|
||||||
|
|
||||||
requireThat(outputState.getYouPlayAs() != null, CREATE_NOT_NULL_YOU_PLAY_AS);
|
requireThat(outputState.getRecipientColor() != null, NON_NULL_RECIPIENT_COLOR);
|
||||||
} else
|
} else
|
||||||
if (command instanceof Accept) {
|
if (command instanceof Accept) {
|
||||||
// TODO outputState -> Game
|
// TODO outputState -> Game
|
||||||
@ -39,14 +40,27 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
|
|||||||
} else
|
} else
|
||||||
if (command instanceof Reject) {
|
if (command instanceof Reject) {
|
||||||
requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE);
|
requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE);
|
||||||
requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE);
|
requireThat(trx.getOutputContractStates().size() == 1, REJECT_OUTPUT_STATE);
|
||||||
|
|
||||||
GameProposalState inputState = trx.getInputStates(GameProposalState.class).get(0);
|
GameProposalState inputState = trx.getInputStates(GameProposalState.class).get(0);
|
||||||
requireThat(inputState != null, REJECT_INPUT_STATE);
|
requireThat(inputState != null, REJECT_INPUT_STATE);
|
||||||
|
|
||||||
|
GameProposalResolutionState outputState = trx.getOutputStates(GameProposalResolutionState.class).get(0);
|
||||||
|
requireThat(outputState != null, REJECT_OUTPUT_STATE);
|
||||||
|
|
||||||
|
requireThat(outputState.outcome == GameProposalResolutionState.Resolution.REJECT, REJECT_OUTPUT_OUTCOME);
|
||||||
} else
|
} else
|
||||||
if (command instanceof Cancle) {
|
if (command instanceof Cancel) {
|
||||||
// TODO cancle game state
|
requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE);
|
||||||
throw new CordaRuntimeException("Unimplemented!");
|
requireThat(trx.getOutputContractStates().size() == 1, CANCEL_OUTPUT_STATE);
|
||||||
|
|
||||||
|
GameProposalState inputState = trx.getInputStates(GameProposalState.class).get(0);
|
||||||
|
requireThat(inputState != null, CANCEL_INPUT_STATE);
|
||||||
|
|
||||||
|
GameProposalResolutionState outputState = trx.getOutputStates(GameProposalResolutionState.class).get(0);
|
||||||
|
requireThat(outputState != null, CANCEL_OUTPUT_STATE);
|
||||||
|
|
||||||
|
requireThat(outputState.outcome == GameProposalResolutionState.Resolution.CANCEL, CANCEL_OUTPUT_OUTCOME);
|
||||||
} else {
|
} else {
|
||||||
throw new CordaRuntimeException(UNKNOWN_COMMAND);
|
throw new CordaRuntimeException(UNKNOWN_COMMAND);
|
||||||
}
|
}
|
||||||
@ -63,9 +77,13 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
|
|||||||
|
|
||||||
static final String CREATE_INPUT_STATE = "Create command should have no input states";
|
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 CREATE_OUTPUT_STATE = "Create command should output exactly one GameProposal state";
|
||||||
static final String CREATE_NOT_NULL_YOU_PLAY_AS = "GameProposal.youPlayAs field can not be null";
|
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 state";
|
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 REJECT_OUTPUT_STATE = "Reject command should have exactly one GameProposalResolution output states";
|
||||||
|
static final String REJECT_OUTPUT_OUTCOME = "Reject output state should have Resolution value set to REJECT";
|
||||||
|
|
||||||
|
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 exactly one GameProposalResolution output states";
|
||||||
|
static final String CANCEL_OUTPUT_OUTCOME = "Cancel output state should have Resolution value set to CANCEL";
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package djmil.cordacheckers.states;
|
||||||
|
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.contracts.GameProposalContract;
|
||||||
|
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||||
|
import net.corda.v5.base.annotations.CordaSerializable;
|
||||||
|
import net.corda.v5.ledger.utxo.BelongsToContract;
|
||||||
|
import net.corda.v5.ledger.utxo.ContractState;
|
||||||
|
|
||||||
|
@BelongsToContract(GameProposalContract.class)
|
||||||
|
public class GameProposalResolutionState implements ContractState {
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
public enum Resolution {
|
||||||
|
ACCEPT,
|
||||||
|
REJECT,
|
||||||
|
CANCEL
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Resolution outcome;
|
||||||
|
public final List<PublicKey> participants;
|
||||||
|
|
||||||
|
@ConstructorForDeserialization
|
||||||
|
public GameProposalResolutionState(
|
||||||
|
Resolution outcome,
|
||||||
|
List<PublicKey> participants
|
||||||
|
) {
|
||||||
|
this.outcome = outcome;
|
||||||
|
this.participants = participants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resolution getOutcome() {
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PublicKey> getParticipants() {
|
||||||
|
return this.participants;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,24 +22,25 @@ public class GameProposalState implements ContractState {
|
|||||||
|
|
||||||
public final MemberX500Name sender;
|
public final MemberX500Name sender;
|
||||||
public final MemberX500Name recipient;
|
public final MemberX500Name recipient;
|
||||||
public final Color youPlayAs;
|
public final Color recipientColor;
|
||||||
public final String additionalMessage;
|
public final String message;
|
||||||
public final UUID id;
|
public final UUID id;
|
||||||
public final List<PublicKey> participants;
|
public final List<PublicKey> participants;
|
||||||
|
|
||||||
// Allows serialisation and to use a specified UUID
|
// Allows serialisation and to use a specified UUID
|
||||||
@ConstructorForDeserialization
|
@ConstructorForDeserialization
|
||||||
public GameProposalState(
|
public GameProposalState(
|
||||||
MemberX500Name sender,
|
MemberX500Name sender,
|
||||||
MemberX500Name recipient,
|
MemberX500Name recipient,
|
||||||
Color youPlayAs,
|
Color recipientColor,
|
||||||
String additionalMessage,
|
String message,
|
||||||
UUID id,
|
UUID id,
|
||||||
List<PublicKey> participants) {
|
List<PublicKey> participants
|
||||||
|
) {
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
this.recipient = recipient;
|
this.recipient = recipient;
|
||||||
this.youPlayAs = youPlayAs;
|
this.recipientColor = recipientColor;
|
||||||
this.additionalMessage = additionalMessage;
|
this.message = message;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.participants = participants;
|
this.participants = participants;
|
||||||
}
|
}
|
||||||
@ -52,12 +53,12 @@ public class GameProposalState implements ContractState {
|
|||||||
return recipient;
|
return recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color getYouPlayAs() {
|
public Color getRecipientColor() {
|
||||||
return youPlayAs;
|
return recipientColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAdditionalMessage() {
|
public String getMessage() {
|
||||||
return additionalMessage;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getId() {
|
public UUID getId() {
|
||||||
@ -67,12 +68,4 @@ public class GameProposalState implements ContractState {
|
|||||||
public List<PublicKey> getParticipants() {
|
public List<PublicKey> getParticipants() {
|
||||||
return participants;
|
return participants;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRecipientCommonName() {
|
|
||||||
return recipient == null ? "" : recipient.getCommonName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSenderCommonName() {
|
|
||||||
return sender == null ? "" : sender.getCommonName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
package djmil.cordacheckers.gameproposal;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.contracts.GameProposalContract;
|
||||||
|
import djmil.cordacheckers.states.GameProposalResolutionState;
|
||||||
|
import djmil.cordacheckers.states.GameProposalState;
|
||||||
|
import djmil.cordacheckers.states.GameProposalResolutionState.Resolution;
|
||||||
|
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.exceptions.CordaRuntimeException;
|
||||||
|
import net.corda.v5.ledger.utxo.Command;
|
||||||
|
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||||
|
import net.corda.v5.ledger.utxo.TransactionState;
|
||||||
|
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||||
|
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||||
|
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
|
||||||
|
|
||||||
|
import static java.util.Map.entry;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@InitiatingFlow(protocol = "game-proposal-action")
|
||||||
|
public class ActionFlow implements ClientStartableFlow {
|
||||||
|
|
||||||
|
@CordaInject
|
||||||
|
public JsonMarshallingService jsonMarshallingService;
|
||||||
|
|
||||||
|
@CordaInject
|
||||||
|
public UtxoLedgerService ledgerService;
|
||||||
|
|
||||||
|
@CordaInject
|
||||||
|
public FlowMessaging flowMessaging;
|
||||||
|
|
||||||
|
@CordaInject
|
||||||
|
public MemberLookup memberLookup;
|
||||||
|
|
||||||
|
private final static Map<GameProposalResolutionState.Resolution, Command> resoultion2command = Map.ofEntries(
|
||||||
|
entry(GameProposalResolutionState.Resolution.CANCEL, new GameProposalContract.Cancel()),
|
||||||
|
entry(GameProposalResolutionState.Resolution.REJECT, new GameProposalContract.Reject()),
|
||||||
|
entry(GameProposalResolutionState.Resolution.ACCEPT, new GameProposalContract.Accept())
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Suspendable
|
||||||
|
public String call(ClientRequestBody requestBody) {
|
||||||
|
ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look up the latest unconsumed ChatState with the given id.
|
||||||
|
* Note, this code brings all unconsumed states back, then filters them. This is an
|
||||||
|
* inefficient way to perform this operation when there are a large number of chats
|
||||||
|
*/
|
||||||
|
List<StateAndRef<GameProposalState>> stateAndRefs = this.ledgerService
|
||||||
|
.findUnconsumedStatesByType(GameProposalState.class);
|
||||||
|
|
||||||
|
List<StateAndRef<GameProposalState>> stateAndRefsWithId = stateAndRefs
|
||||||
|
.stream()
|
||||||
|
.filter(sar -> sar.getState().getContractState().getId().equals(args.gameProposalUuid))
|
||||||
|
.collect(toList());
|
||||||
|
if (stateAndRefsWithId.size() != 1)
|
||||||
|
throw new CordaRuntimeException("Multiple or zero GameProposal states with id " + args.gameProposalUuid + " found");
|
||||||
|
|
||||||
|
StateAndRef<GameProposalState> stateAndRef = stateAndRefsWithId.get(0);
|
||||||
|
TransactionState<GameProposalState> trxState = stateAndRef.getState();
|
||||||
|
GameProposalState state = trxState.getContractState();
|
||||||
|
|
||||||
|
GameProposalResolutionState outputState = new GameProposalResolutionState(
|
||||||
|
GameProposalResolutionState.Resolution.valueOf(args.action),
|
||||||
|
state.getParticipants()
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build draft trx
|
||||||
|
*/
|
||||||
|
UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder()
|
||||||
|
.setNotary(trxState.getNotaryName())
|
||||||
|
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||||
|
.addInputState(stateAndRef.getRef())
|
||||||
|
.addOutputState(outputState)
|
||||||
|
.addCommand(resoultion2command.get(outputState.outcome))
|
||||||
|
.addSignatories(state.getParticipants());
|
||||||
|
|
||||||
|
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
|
||||||
|
|
||||||
|
FlowSession session = flowMessaging.initiateFlow(
|
||||||
|
outputState.outcome == Resolution.CANCEL ? state.getRecipient() : state.getSender() // TODO: readability
|
||||||
|
);
|
||||||
|
|
||||||
|
List<FlowSession> sessionsList = Arrays.asList(session);
|
||||||
|
|
||||||
|
ledgerService.finalize(signedTransaction, sessionsList);
|
||||||
|
|
||||||
|
return args.action+"ED"; // REJECT+ED
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package djmil.cordacheckers.gameproposal;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class ActionFlowArgs {
|
||||||
|
public final UUID gameProposalUuid;
|
||||||
|
public final String action;
|
||||||
|
|
||||||
|
// Serialisation service requires a default constructor
|
||||||
|
public ActionFlowArgs() {
|
||||||
|
this.gameProposalUuid = null;
|
||||||
|
this.action = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package djmil.cordacheckers.gameproposal;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import djmil.cordacheckers.contracts.GameProposalContract.Accept;
|
||||||
|
import djmil.cordacheckers.contracts.GameProposalContract.Cancel;
|
||||||
|
import djmil.cordacheckers.contracts.GameProposalContract.Reject;
|
||||||
|
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.Command;
|
||||||
|
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||||
|
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||||
|
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator;
|
||||||
|
|
||||||
|
@InitiatedBy(protocol = "game-proposal-action")
|
||||||
|
public class ActionResponder implements ResponderFlow {
|
||||||
|
private final static Logger log = LoggerFactory.getLogger(CreateResponder.class);
|
||||||
|
|
||||||
|
@CordaInject
|
||||||
|
public MemberLookup memberLookup;
|
||||||
|
|
||||||
|
@CordaInject
|
||||||
|
public UtxoLedgerService utxoLedgerService;
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
@Override
|
||||||
|
public void call(FlowSession session) {
|
||||||
|
try {
|
||||||
|
// Defines the lambda validator used in receiveFinality below.
|
||||||
|
UtxoTransactionValidator txValidator = ledgerTransaction -> {
|
||||||
|
GameProposalState gameProposal = (GameProposalState) ledgerTransaction.getInputContractStates().get(0);
|
||||||
|
// Uses checkForBannedWords() and checkMessageFromMatchesCounterparty() functions
|
||||||
|
// to check whether to sign the transaction.
|
||||||
|
|
||||||
|
Command command = ledgerTransaction.getCommands(Command.class).get(0);
|
||||||
|
|
||||||
|
if (!checkParticipants(gameProposal, session.getCounterparty(), command)) {
|
||||||
|
throw new CordaRuntimeException("Failed verification");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Verified the transaction - " + ledgerTransaction.getId());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calls receiveFinality() function which provides the responder to the finalise() function
|
||||||
|
// in the Initiating Flow. Accepts a lambda validator containing the business logic to decide whether
|
||||||
|
// responder should sign the Transaction.
|
||||||
|
UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService
|
||||||
|
.receiveFinality(session, txValidator)
|
||||||
|
.getTransaction();
|
||||||
|
|
||||||
|
log.info("Finished responder flow - " + finalizedSignedTransaction.getId());
|
||||||
|
}
|
||||||
|
// Soft fails the flow and log the exception.
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
log.warn("Exceptionally finished responder flow", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
Boolean checkParticipants(GameProposalState gameProposal, MemberX500Name counterpartyName, Command command) {
|
||||||
|
MemberX500Name myName = memberLookup.myInfo().getName();
|
||||||
|
log.info("Responder validation:\n command "+command+"\n me " + myName.toString() + "\n opponent " + counterpartyName.toString());
|
||||||
|
if (command instanceof Reject || command instanceof Accept) {
|
||||||
|
if (gameProposal.getRecipient().compareTo(counterpartyName) == 0 &&
|
||||||
|
gameProposal.getSender().compareTo(myName) == 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command instanceof Cancel) {
|
||||||
|
if (gameProposal.getRecipient().compareTo(myName) == 0 &&
|
||||||
|
gameProposal.getSender().compareTo(counterpartyName) == 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -92,8 +92,8 @@ public class CreateFlow implements ClientStartableFlow{
|
|||||||
return new GameProposalState(
|
return new GameProposalState(
|
||||||
myInfo.getName(),
|
myInfo.getName(),
|
||||||
opponentInfo.getName(),
|
opponentInfo.getName(),
|
||||||
GameProposalState.Color.valueOf(args.opponentColor),
|
opponentColor,
|
||||||
args.additionalMessage,
|
args.message,
|
||||||
UUID.randomUUID(),
|
UUID.randomUUID(),
|
||||||
Arrays.asList(myInfo.getLedgerKeys().get(0), opponentInfo.getLedgerKeys().get(0))
|
Arrays.asList(myInfo.getLedgerKeys().get(0), opponentInfo.getLedgerKeys().get(0))
|
||||||
);
|
);
|
||||||
@ -120,6 +120,9 @@ public class CreateFlow implements ClientStartableFlow{
|
|||||||
.finalize(signedTransaction, sessionsList)
|
.finalize(signedTransaction, sessionsList)
|
||||||
.getTransaction();
|
.getTransaction();
|
||||||
|
|
||||||
return finalizedSignedTransaction.getId().toString();
|
|
||||||
|
// final String trxId = finalizedSignedTransaction.getId().toString();
|
||||||
|
|
||||||
|
return gameProposal.id.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,12 @@ package djmil.cordacheckers.gameproposal;
|
|||||||
public class CreateFlowArgs {
|
public class CreateFlowArgs {
|
||||||
public final String opponentName;
|
public final String opponentName;
|
||||||
public final String opponentColor;
|
public final String opponentColor;
|
||||||
public final String additionalMessage;
|
public final String message;
|
||||||
|
|
||||||
public CreateFlowArgs(String opponentName, String opponentColor, String additionalMessage) {
|
|
||||||
this.opponentName = opponentName;
|
|
||||||
this.opponentColor = opponentColor;
|
|
||||||
this.additionalMessage = additionalMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialisation service requires a default constructor
|
// Serialisation service requires a default constructor
|
||||||
public CreateFlowArgs() {
|
public CreateFlowArgs() {
|
||||||
this(null, null, null);
|
opponentName = null;
|
||||||
|
opponentColor = null;
|
||||||
|
message = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,24 +12,24 @@ import djmil.cordacheckers.states.GameProposalState;
|
|||||||
public class ListItem {
|
public class ListItem {
|
||||||
public final String sender;
|
public final String sender;
|
||||||
public final String recipient;
|
public final String recipient;
|
||||||
public final String youPlayAs;
|
public final String recipientColor;
|
||||||
public final String additionalMessage;
|
public final String message;
|
||||||
public final UUID id;
|
public final UUID id;
|
||||||
|
|
||||||
// Serialisation service requires a default constructor
|
// Serialisation service requires a default constructor
|
||||||
public ListItem() {
|
public ListItem() {
|
||||||
this.sender = null;
|
this.sender = null;
|
||||||
this.recipient = null;
|
this.recipient = null;
|
||||||
this.youPlayAs = null;
|
this.recipientColor = null;
|
||||||
this.additionalMessage = null;
|
this.message = null;
|
||||||
this.id = null;
|
this.id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListItem(GameProposalState state) {
|
public ListItem(GameProposalState state) {
|
||||||
this.sender = state.getSenderCommonName();
|
this.sender = state.getSender().getCommonName();
|
||||||
this.recipient = state.getRecipientCommonName();
|
this.recipient = state.getRecipient().getCommonName();
|
||||||
this.youPlayAs = state.getYouPlayAs().name();
|
this.recipientColor = state.getRecipientColor().name();
|
||||||
this.additionalMessage = state.getAdditionalMessage();
|
this.message = state.getMessage();
|
||||||
this.id = state.getId();
|
this.id = state.getId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package djmil.cordacheckers.gameproposal;
|
|
||||||
|
|
||||||
public class RejectFlow {
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user