Compare commits

...

5 Commits

Author SHA1 Message Date
e235ecb942 Corda: CommitSubFlow 2023-09-07 21:33:37 +02:00
159bcd706e GameProposal updates
- remove GameProposalResolution state
use ladger trx history instead

- use issuer/acquier instead of sennder/receiver
2023-09-07 14:47:53 +02:00
5cc579230f SpringBoot: test GP can not be rejected twice 2023-09-07 14:00:27 +02:00
c1dbb3d213 Corda: Piece class 2023-09-07 09:57:54 +02:00
a9b70b963c CordaClient: use FlowResult 2023-09-06 12:39:52 +02:00
22 changed files with 509 additions and 453 deletions

View File

@ -14,17 +14,23 @@ 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.HoldingIdentity;
import djmil.cordacheckers.cordaclient.dao.Color;
import djmil.cordacheckers.cordaclient.dao.GameProposal;
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.CreateGameProposal;
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.GameProposalAction;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionRes;
@Service
public class CordaClient {
@ -59,7 +65,7 @@ public class CordaClient {
* @param holdingIdentity
* @return GameProposals list in JSON form
*/
public String listGameProposals(HoldingIdentity holdingIdentity) {
public List<GameProposal> gameProposalList(HoldingIdentity holdingIdentity) {
final RequestBody requestBody = new RequestBody(
"list-" + UUID.randomUUID(),
@ -67,21 +73,27 @@ public class CordaClient {
new Empty()
);
final String gameProposalsJsonString = cordaFlowExecute(
final GameProposalListRes listFlowResult = cordaFlowExecute(
holdingIdentity,
requestBody
requestBody,
GameProposalListRes.class
);
return gameProposalsJsonString;
if (listFlowResult.failureStatus() != null) {
System.out.println("GameProposalCreateFlow failed: " + listFlowResult.failureStatus());
throw new RuntimeException("GameProsal: CreateFlow execution has failed");
}
return listFlowResult.successStatus();
}
public String createGameProposal(
public UUID gameProposalCreate(
HoldingIdentity sender,
HoldingIdentity receiver,
Color receiverColor,
String message
) {
final CreateGameProposal createGameProposal = new CreateGameProposal(
) throws JsonMappingException, JsonProcessingException {
final GameProposalCreateReq createGameProposal = new GameProposalCreateReq(
receiver.x500Name(),
receiverColor,
message
@ -93,21 +105,28 @@ public class CordaClient {
createGameProposal
);
final String createdGameProposalUuid = cordaFlowExecute(
final GameProposalCreateRes createResult = cordaFlowExecute(
sender,
requestBody
requestBody,
GameProposalCreateRes.class
);
return createdGameProposalUuid;
if (createResult.failureStatus() != null) {
System.out.println("GameProposalCreateFlow failed: " + createResult.failureStatus());
throw new RuntimeException("GameProsal: CreateFlow execution has failed");
}
return createResult.successStatus();
}
public String rejectGameProposal(
public String gameProposalAction(
HoldingIdentity self,
String gameProposalUuid
UUID gameProposalUuid,
GameProposalActionReq.Action action
) {
final GameProposalAction rejectGameProposal = new GameProposalAction(
gameProposalUuid,
GameProposalAction.Action.REJECT
final GameProposalActionReq rejectGameProposal = new GameProposalActionReq(
gameProposalUuid.toString(),
action
);
final RequestBody requestBody = new RequestBody(
@ -116,15 +135,21 @@ public class CordaClient {
rejectGameProposal
);
final String createdGameProposalUuid = cordaFlowExecute(
final GameProposalActionRes actionResult = cordaFlowExecute(
self,
requestBody
requestBody,
GameProposalActionRes.class
);
return createdGameProposalUuid;
if (actionResult.failureStatus() != null) {
System.out.println("GameProposalActionFlow failed: " + actionResult.failureStatus());
throw new RuntimeException("GameProsal: ActionFlow execution has failed");
}
return actionResult.successStatus();
}
private String cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody) {
private <T> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowResultType) {
try {
final String requestBodyJson = this.jsonMapper.writeValueAsString(requestBody);
@ -134,15 +159,9 @@ public class CordaClient {
requestBodyJson
);
final String flowExecutionResult = cordaFlowPoll(startedFlow);
final String flowResult = cordaFlowPoll(startedFlow);
// NOTE:
// At this point, real production code, probably should convert data between CordaFlow
// abstarction into ReactApp abstraction. Instead, to limit boring json shuffling, all
// family of Corda.List flows were deliberatly designed to return frontend frendly JSONs.
// At the same time, all other Corda flows, simply return plain text string with
// operation result.
return flowExecutionResult;
return this.jsonMapper.readValue(flowResult, flowResultType);
}
catch (Exception e) {
throw new RuntimeException("Unable to perform "+requestBody.flowClassName()
@ -183,7 +202,7 @@ public class CordaClient {
for (int retry = 0; retry < 6; retry++) {
// Give Corda cluster some time to process our request
TimeUnit.SECONDS.sleep(retry*retry +1); // 1 2 5 8 17 33 sec
TimeUnit.SECONDS.sleep(retry*retry +2); // 2 3 6 10 18 27 sec
final ResponseEntity<ResponseBody> responce = this.restTemplate.exchange(
"/flow/"
@ -204,15 +223,16 @@ public class CordaClient {
"CordaClient.cordaFlowPoll: empty getBody()"
);
if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) {
System.out.println("Completed "+responseBody.flowResult());
return responseBody.flowResult();
} else
if (responseBody.flowError() != null) {
return "Flow execution error: " +responseBody.flowError();
throw new RuntimeException("CordaClient.cordaFlowPoll: flow execution error: "
+responseBody.flowError());
}
if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) {
return responseBody.flowResult();
}
}
return "CordaClient.cordaFlowPoll: retry limit";
throw new RuntimeException ("CordaClient.cordaFlowPoll: retry limit");
}
}

View File

@ -1,13 +1,15 @@
package djmil.cordacheckers.cordaclient.dao;
import java.util.UUID;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize
public record GameProposal(
String sender,
String recipient,
Color recipientColor,
String issuer,
String acquier,
Color acquierColor,
String message,
String id) {
UUID id) {
}

View File

@ -1,6 +1,6 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
public record GameProposalAction(String gameProposalUuid, Action action) {
public record GameProposalActionReq(String gameProposalUuid, Action action) {
public enum Action {
ACCEPT,
REJECT,

View File

@ -0,0 +1,5 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
public record GameProposalActionRes(String successStatus, String transactionId, String failureStatus) {
}

View File

@ -2,6 +2,6 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import djmil.cordacheckers.cordaclient.dao.Color;
public record CreateGameProposal(String opponentName, Color opponentColor, String message) {
public record GameProposalCreateReq(String opponentName, Color opponentColor, String message) {
}

View File

@ -0,0 +1,10 @@
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) {
}

View File

@ -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.GameProposal;
@JsonIgnoreProperties(ignoreUnknown = true)
public record GameProposalListRes(List<GameProposal> successStatus, String failureStatus) {
}

View File

@ -1,6 +1,8 @@
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;
@ -9,9 +11,12 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import djmil.cordacheckers.cordaclient.CordaClient;
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.CreateGameProposal;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq;
import djmil.cordacheckers.cordaclient.dao.Color;
import djmil.cordacheckers.cordaclient.dao.GameProposal;
import djmil.cordacheckers.user.HoldingIdentityResolver;
@ -36,9 +41,9 @@ public class GameProposalController {
public ResponseEntity<String> findAllUnconsumed(
@AuthenticationPrincipal User player
) {
String gpList = cordaClient.listGameProposals(player.getHoldingIdentity());
List<GameProposal> gpList = cordaClient.gameProposalList(player.getHoldingIdentity());
return ResponseEntity.ok(gpList);
return ResponseEntity.ok(gpList.toString());
}
// @PostMapping()
@ -50,15 +55,16 @@ public class GameProposalController {
@PostMapping()
public ResponseEntity<Void> createGameProposal(
@AuthenticationPrincipal User sender,
@RequestBody CreateGameProposal gpRequest,
@RequestBody GameProposalCreateReq gpRequest,
UriComponentsBuilder ucb
) {
) throws JsonMappingException, JsonProcessingException {
final HoldingIdentity gpSender = sender.getHoldingIdentity();
// TODO: throw execption with custom type
final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName());
final Color gpReceiverColor = gpRequest.opponentColor();
String newGameProposalUuid = cordaClient.createGameProposal(
// TODO handle expectionns here
UUID newGameProposalUuid = cordaClient.gameProposalCreate(
gpSender,
gpReceiver,
gpReceiverColor,

View File

@ -6,14 +6,19 @@ 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;
import java.util.List;
import java.util.UUID;
import javax.naming.InvalidNameException;
@ -34,44 +39,51 @@ public class CordaClientTest {
@Test
void testGameProposalList() throws JsonProcessingException {
String resp = cordaClient.listGameProposals(
holdingIdentityResolver.getByUsername("alice"));
List<GameProposal> gpList = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername("Alice"));
System.out.println("testListGameProposals "+ resp);
System.out.println("testListGameProposals\n"+ gpList);
}
@Test
void testGemeProposalCreate() {
final String gpSender = "alice";
final String gpReceiver = "bob";
final Color gpReceiverColor = Color.WHITE;
void testGemeProposalCreate() throws JsonMappingException, JsonProcessingException {
final String gpIssuer = "alice";
final String gpAcquier = "bob";
final Color gpAcquierColor = Color.WHITE;
final String gpMessage = "GameProposal create test";
final String gpUuid = cordaClient.createGameProposal(
holdingIdentityResolver.getByUsername(gpSender),
holdingIdentityResolver.getByUsername(gpReceiver),
gpReceiverColor,
final UUID createdGpUuid = cordaClient.gameProposalCreate(
holdingIdentityResolver.getByUsername(gpIssuer),
holdingIdentityResolver.getByUsername(gpAcquier),
gpAcquierColor,
gpMessage
);
String listResSender = cordaClient.listGameProposals(
holdingIdentityResolver.getByUsername(gpSender));
List<GameProposal> gpListSender = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername(gpIssuer));
String listResReceiver = cordaClient.listGameProposals(
holdingIdentityResolver.getByUsername(gpReceiver));
assertThat(findByUuid(gpListSender, createdGpUuid)).isNotNull();
assertThat(listResSender).contains(gpUuid);
assertThat(listResReceiver).contains(gpUuid);
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 testGemeProposalReject() {
void testGemeProposalReject() throws JsonMappingException, JsonProcessingException {
final String gpSender = "alice";
final String gpReceiver = "bob";
final Color gpReceiverColor = Color.WHITE;
final String gpMessage = "GameProposal create test";
final String gpMessage = "GameProposal REJECT test";
final String gpUuid = cordaClient.createGameProposal(
final UUID gpUuid = cordaClient.gameProposalCreate(
holdingIdentityResolver.getByUsername(gpSender),
holdingIdentityResolver.getByUsername(gpReceiver),
gpReceiverColor,
@ -80,20 +92,45 @@ public class CordaClientTest {
System.out.println("Create GP UUID "+ gpUuid);
String listResSender = cordaClient.listGameProposals(
holdingIdentityResolver.getByUsername(gpSender));
assertThatThrownBy(() -> {
cordaClient.gameProposalAction(
holdingIdentityResolver.getByUsername(gpSender),
gpUuid,
Action.REJECT);
});
String listResReceiver = cordaClient.listGameProposals(
holdingIdentityResolver.getByUsername(gpReceiver));
assertThat(listResSender).contains(gpUuid);
assertThat(listResReceiver).contains(gpUuid);
final String rejectRes = cordaClient.rejectGameProposal(
final String rejectRes = cordaClient.gameProposalAction(
holdingIdentityResolver.getByUsername(gpReceiver),
"1ed70601-c79a-486d-b907-8537f317083a"
gpUuid,
Action.REJECT
);
assertThat(rejectRes).isEqualToIgnoringCase("REJECTED");
List<GameProposal> gpListSender = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername(gpSender));
assertThat(findByUuid(gpListSender, gpUuid)).isNull();
List<GameProposal> gpListReceiver = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername(gpReceiver));
assertThat(findByUuid(gpListReceiver, gpUuid)).isNull();
// GameProposal can not be rejected twice
assertThatThrownBy(() -> {
cordaClient.gameProposalAction(
holdingIdentityResolver.getByUsername(gpSender),
gpUuid,
Action.REJECT);
});
}
private GameProposal findByUuid(List<GameProposal> gpList, UUID uuid) {
for (GameProposal gameProposal : gpList) {
if (gameProposal.id().compareTo(uuid) == 0)
return gameProposal;
};
return null;
}
}

View File

@ -3,66 +3,96 @@ package djmil.cordacheckers.contracts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import djmil.cordacheckers.states.GameProposalResolutionState;
//import djmil.cordacheckers.states.GameProposalResolutionState;
import djmil.cordacheckers.states.GameProposalState;
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.StateAndRef;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
private final static Logger log = LoggerFactory.getLogger(GameProposalContract.class);
public static class Create implements Command { }
public static class Accept implements Command { }
public static class Reject implements Command { }
public static class Cancel implements Command { }
public static enum Action 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_ACTION_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_ACTION_VALUE_OF + this.name());
}
}
public MemberX500Name getSender(StateAndRef<GameProposalState> utxoGameProposal) {
return getInitiator(utxoGameProposal.getState().getContractState());
}
public MemberX500Name getReceiver(StateAndRef<GameProposalState> utxoGameProposal) {
return getRespondent(utxoGameProposal.getState().getContractState());
}
public static final String UNSUPPORTED_ACTION_VALUE_OF = "Unsupported Action value: ";
}
@Override
public void verify(UtxoLedgerTransaction trx) {
log.info("GameProposalContract.verify() called");
requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND);
Command command = trx.getCommands().get(0);
if (command instanceof Create) {
requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE);
switch ((Action)(trx.getCommands().get(0))) {
case CREATE: {
requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE);
GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0);
requireThat(outputState != null, CREATE_OUTPUT_STATE);
GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0);
requireThat(outputState != null, CREATE_OUTPUT_STATE);
requireThat(outputState.getRecipientColor() != null, NON_NULL_RECIPIENT_COLOR);
} else
if (command instanceof Accept) {
// TODO outputState -> Game
throw new CordaRuntimeException("Unimplemented!");
} else
if (command instanceof Reject) {
requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, REJECT_OUTPUT_STATE);
requireThat(outputState.getRecipientColor() != null, NON_NULL_RECIPIENT_COLOR);
break; }
GameProposalState inputState = trx.getInputStates(GameProposalState.class).get(0);
requireThat(inputState != null, REJECT_INPUT_STATE);
case ACCEPT:
throw new CordaRuntimeException("Unimplemented!");
//break;
GameProposalResolutionState outputState = trx.getOutputStates(GameProposalResolutionState.class).get(0);
requireThat(outputState != null, REJECT_OUTPUT_STATE);
case REJECT:
requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE);
requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE);
requireThat(trx.getInputStates(GameProposalState.class).get(0) != null, REJECT_INPUT_STATE);
break;
requireThat(outputState.outcome == GameProposalResolutionState.Resolution.REJECT, REJECT_OUTPUT_OUTCOME);
} else
if (command instanceof Cancel) {
requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, CANCEL_OUTPUT_STATE);
case CANCEL:
requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE);
requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE);
requireThat(trx.getInputStates(GameProposalState.class).get(0) != null, CANCEL_INPUT_STATE);
break;
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 {
throw new CordaRuntimeException(UNKNOWN_COMMAND);
default:
throw new CordaRuntimeException(UNKNOWN_COMMAND);
}
}
@ -80,10 +110,8 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
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 exactly one GameProposalResolution output states";
static final String REJECT_OUTPUT_OUTCOME = "Reject output state should have Resolution value set to REJECT";
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 exactly one GameProposalResolution output states";
static final String CANCEL_OUTPUT_OUTCOME = "Cancel output state should have Resolution value set to CANCEL";
static final String CANCEL_OUTPUT_STATE = "Cancel command should have no output states";
}

View File

@ -1,63 +0,0 @@
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.base.types.MemberX500Name;
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;
public GameProposalResolutionState(
Resolution outcome,
GameProposalState gameProposal
) {
this.outcome = outcome;
this.participants = gameProposal.getParticipants();
}
@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;
}
public MemberX500Name getRecipient(GameProposalState gameProposal) {
switch (outcome) {
case ACCEPT:
case REJECT:
return gameProposal.getSender();
case CANCEL:
return gameProposal.getRecipient();
default:
throw new RuntimeException("Unknown Resolution value: "+outcome.toString());
}
}
}

View File

@ -6,7 +6,6 @@ import java.util.UUID;
import djmil.cordacheckers.contracts.GameProposalContract;
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.BelongsToContract;
import net.corda.v5.ledger.utxo.ContractState;
@ -14,46 +13,40 @@ import net.corda.v5.ledger.utxo.ContractState;
@BelongsToContract(GameProposalContract.class)
public class GameProposalState implements ContractState {
@CordaSerializable
public enum Color {
WHITE,
BLACK,
}
public final MemberX500Name sender;
public final MemberX500Name recipient;
public final Color recipientColor;
public final String message;
public final UUID id;
public final List<PublicKey> participants;
MemberX500Name issuer;
MemberX500Name acquier;
Piece.Color recipientColor;
String message;
UUID id;
List<PublicKey> participants;
// Allows serialisation and to use a specified UUID
@ConstructorForDeserialization
public GameProposalState(
MemberX500Name sender,
MemberX500Name recipient,
Color recipientColor,
MemberX500Name issuer,
MemberX500Name acquier,
Piece.Color recipientColor,
String message,
UUID id,
List<PublicKey> participants
) {
this.sender = sender;
this.recipient = recipient;
this.issuer = issuer;
this.acquier = acquier;
this.recipientColor = recipientColor;
this.message = message;
this.id = id;
this.participants = participants;
}
public MemberX500Name getSender() {
return sender;
public MemberX500Name getIssuer() {
return issuer;
}
public MemberX500Name getRecipient() {
return recipient;
public MemberX500Name getAcquier() {
return acquier;
}
public Color getRecipientColor() {
public Piece.Color getRecipientColor() {
return recipientColor;
}

View File

@ -0,0 +1,24 @@
package djmil.cordacheckers.states;
import net.corda.v5.base.annotations.CordaSerializable;
@CordaSerializable
public class Piece {
@CordaSerializable
public enum Type {
MAN,
KING,
}
@CordaSerializable
public enum Color {
WHITE,
BLACK,
}
Color color;
Type type;
}

View File

@ -1,18 +1,28 @@
package djmil.cordacheckers;
import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.crypto.SecureHash;
public class FlowResult {
public final Object successStatus;
public final String transactionId;
public final String failureStatus;
public FlowResult(Object success) {
this.successStatus = success;
this.transactionId = null;
this.failureStatus = null;
}
public FlowResult(Object success, SecureHash transactionId) {
this.successStatus = success;
this.transactionId = transactionId.toString();
this.failureStatus = null;
}
public FlowResult(Exception exception) {
this.successStatus = null;
this.transactionId = null;
this.failureStatus = exception.getMessage();
}

View File

@ -1,8 +1,6 @@
package djmil.cordacheckers.gameproposal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@ -10,21 +8,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import djmil.cordacheckers.FlowResult;
import djmil.cordacheckers.contracts.GameProposalContract;
import djmil.cordacheckers.states.GameProposalResolutionState;
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.flows.InitiatingFlow;
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.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.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.Command;
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;
@ -33,7 +25,8 @@ import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
import java.time.Duration;
import java.time.Instant;
@InitiatingFlow(protocol = "game-proposal-action")
import static djmil.cordacheckers.contracts.GameProposalContract.Action;
public class ActionFlow implements ClientStartableFlow {
private final static Logger log = LoggerFactory.getLogger(CreateFlow.class);
@ -45,33 +38,24 @@ public class ActionFlow implements ClientStartableFlow {
public UtxoLedgerService ledgerService;
@CordaInject
public FlowMessaging flowMessaging;
@CordaInject
public MemberLookup memberLookup;
private final static Map<GameProposalResolutionState.Resolution, Command> resoultion2command = Map.ofEntries(
Map.entry(GameProposalResolutionState.Resolution.CANCEL, new GameProposalContract.Cancel()),
Map.entry(GameProposalResolutionState.Resolution.REJECT, new GameProposalContract.Reject()),
Map.entry(GameProposalResolutionState.Resolution.ACCEPT, new GameProposalContract.Accept())
);
public FlowEngine flowEngine;
@Override
@Suspendable
public String call(ClientRequestBody requestBody) {
try {
ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class);
final ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class);
final Action action = args.getAction();
StateAndRef<GameProposalState> inputState = findUnconsumedGameProposalState(args.getGameProposalUuid());
final StateAndRef<GameProposalState> inputState = findUnconsumedGameProposalState(args.getGameProposalUuid());
GameProposalResolutionState outputState = new GameProposalResolutionState(
args.getAction(),
inputState.getState().getContractState()
);
final UtxoSignedTransaction trx = prepareSignedTransaction(action, inputState);
String trxResult = doTrunsaction(inputState, outputState);
final SecureHash trxId = this.flowEngine
.subFlow( new CommitSubFlow(trx, action.getReceiver(inputState)) );
return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService);
return new FlowResult(action+"ED", trxId) // REJECT+ED
.toJsonEncodedString(jsonMarshallingService);
}
catch (Exception e) {
log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +
@ -83,7 +67,7 @@ public class ActionFlow implements ClientStartableFlow {
@Suspendable
private StateAndRef<GameProposalState> findUnconsumedGameProposalState (UUID gameProposalUuid) {
/*
* Get list of all unconsumed aka 'actuve' GameProposalStates, then filter by UUID.
* Get list of all unconsumed aka 'active' GameProposalStates, then filter by UUID.
* Note, this is an inefficient way to perform this operation if there are a large
* number of 'active' GameProposals exists in storage.
*/
@ -102,26 +86,24 @@ public class ActionFlow implements ClientStartableFlow {
}
@Suspendable
private String doTrunsaction(StateAndRef<GameProposalState> inputState, GameProposalResolutionState outputState) {
UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder()
private UtxoSignedTransaction prepareSignedTransaction(
Action action,
StateAndRef<GameProposalState> inputState
) {
UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder()
.setNotary(inputState.getState().getNotaryName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addInputState(inputState.getRef())
.addOutputState(outputState)
.addCommand(resoultion2command.get(outputState.outcome))
.addSignatories(outputState.getParticipants());
.addCommand(action)
.addSignatories(inputState.getState().getContractState().getParticipants());
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
if (action == Action.ACCEPT) {
// TODO
// .addOutputState(outputState)
throw new RuntimeException(action +" unimplemented");
}
FlowSession session = flowMessaging.initiateFlow(
outputState.getRecipient(inputState.getState().getContractState())
);
List<FlowSession> sessionsList = Arrays.asList(session);
ledgerService.finalize(signedTransaction, sessionsList);
return outputState.getOutcome()+"ED"; // REJECT+ED
return trxBuilder.toSignedTransaction();
}
}

View File

@ -2,8 +2,7 @@ package djmil.cordacheckers.gameproposal;
import java.util.UUID;
import djmil.cordacheckers.states.GameProposalResolutionState;
import djmil.cordacheckers.states.GameProposalResolutionState.Resolution;
import djmil.cordacheckers.contracts.GameProposalContract;
public class ActionFlowArgs {
private UUID gameProposalUuid;
@ -15,8 +14,8 @@ public class ActionFlowArgs {
this.action = null;
}
public Resolution getAction() {
return GameProposalResolutionState.Resolution.valueOf(this.action);
public GameProposalContract.Action getAction() {
return GameProposalContract.Action.valueOf(this.action);
}
public UUID getGameProposalUuid() {

View File

@ -1,83 +0,0 @@
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 {
UtxoTransactionValidator txValidator = ledgerTransaction -> {
GameProposalState gameProposal = (GameProposalState) ledgerTransaction.getInputContractStates().get(0);
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();
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;
}
}

View File

@ -0,0 +1,95 @@
package djmil.cordacheckers.gameproposal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.GameProposalContract.Action;
@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) {
try {
UtxoTransactionValidator txValidator = ledgerTransaction -> {
final Action action = ledgerTransaction.getCommands(Action.class).get(0);
final GameProposalState gameProposal = getGameProposal(ledgerTransaction);
checkSessionParticipants(session, gameProposal, action);
/*
* 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 checkSessionParticipants(
FlowSession session,
GameProposalState gameProposal,
Action action
) {
final MemberX500Name myName = memberLookup.myInfo().getName();
final MemberX500Name otherName = session.getCounterparty();
if (action.getRespondent(gameProposal).compareTo(myName) != 0)
throw new CordaRuntimeException("Bad GameProposal acquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'");
if (action.getInitiator(gameProposal).compareTo(otherName) != 0)
throw new CordaRuntimeException("Bad GameProposal issuer: expected '"+otherName+"', actual '"+gameProposal.getIssuer()+"'");
}
@Suspendable
GameProposalState getGameProposal(UtxoLedgerTransaction trx) {
final Action action = trx.getCommands(Action.class).get(0);
switch (action) {
case CREATE:
return (GameProposalState)trx.getOutputContractStates().get(0);
case ACCEPT:
case REJECT:
case CANCEL:
return (GameProposalState)trx.getInputContractStates().get(0);
default:
throw new RuntimeException(Action.UNSUPPORTED_ACTION_VALUE_OF +action);
}
}
}

View File

@ -0,0 +1,60 @@
package djmil.cordacheckers.gameproposal;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.flows.InitiatingFlow;
import net.corda.v5.application.flows.SubFlow;
import net.corda.v5.application.messaging.FlowMessaging;
import net.corda.v5.application.messaging.FlowSession;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.crypto.SecureHash;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
@InitiatingFlow(protocol = "game-proposal")
public class CommitSubFlow implements SubFlow<SecureHash> {
private final static Logger log = LoggerFactory.getLogger(CommitSubFlow.class);
private final UtxoSignedTransaction signedTransaction;
private final MemberX500Name respondenName;
public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name respondenName) {
this.signedTransaction = signedTransaction;
this.respondenName = respondenName;
}
@CordaInject
public UtxoLedgerService ledgerService;
@CordaInject
public FlowMessaging flowMessaging;
@Override
@Suspendable
public SecureHash call() {
log.info("GamePropsal commit started");
final FlowSession session = flowMessaging.initiateFlow(this.respondenName);
/*
* Calls the Corda provided finalise() function which gather signatures from the counterparty,
* notarises the transaction and persists the transaction to each party's vault.
*/
final List<FlowSession> sessionsList = Arrays.asList(session);
final SecureHash trxId = ledgerService
.finalize(this.signedTransaction, sessionsList)
.getTransaction()
.getId();
log.info("GamePropsal commit " +trxId);
return trxId;
}
}

View File

@ -4,8 +4,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import djmil.cordacheckers.FlowResult;
import djmil.cordacheckers.contracts.GameProposalContract;
import djmil.cordacheckers.states.GameProposalState;
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;
@ -13,26 +13,23 @@ 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.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.common.NotaryLookup;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
import net.corda.v5.membership.MemberInfo;
import net.corda.v5.membership.NotaryInfo;
import static java.util.Objects.requireNonNull;
import static djmil.cordacheckers.contracts.GameProposalContract.Action;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@InitiatingFlow(protocol = "game-proposal-create")
public class CreateFlow implements ClientStartableFlow{
private final static Logger log = LoggerFactory.getLogger(CreateFlow.class);
@ -52,19 +49,22 @@ public class CreateFlow implements ClientStartableFlow{
@CordaInject
public FlowEngine flowEngine;
@CordaInject
public FlowMessaging flowMessaging;
@Suspendable
@Override
public String call(ClientRequestBody requestBody) {
try {
log.info("flow: Create Game Proposal");
final Action actino = Action.CREATE;
GameProposalState gameProposal = buildGameProposalStateFrom(requestBody);
String trxResult = doTrunsaction(gameProposal);
final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody);
return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService);
final UtxoSignedTransaction trx = prepareSignedTransaction(newGameProposal);
final SecureHash trxId = this.flowEngine
.subFlow(new CommitSubFlow(trx, actino.getRespondent(newGameProposal)));
return new FlowResult(newGameProposal.getId(), trxId)
.toJsonEncodedString(jsonMarshallingService);
}
catch (Exception e) {
log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody +
@ -77,10 +77,10 @@ public class CreateFlow implements ClientStartableFlow{
private GameProposalState buildGameProposalStateFrom(ClientRequestBody requestBody) {
CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class);
GameProposalState.Color opponentColor = GameProposalState.Color.valueOf(args.opponentColor);
Piece.Color opponentColor = Piece.Color.valueOf(args.opponentColor);
if (opponentColor == null) {
throw new RuntimeException("Allowed values for opponentColor are: "
+ GameProposalState.Color.WHITE.name() +", " + GameProposalState.Color.BLACK.name()
+ Piece.Color.WHITE.name() +", " + Piece.Color.BLACK.name()
+ ". Actual value was: " + args.opponentColor);
}
@ -101,24 +101,15 @@ public class CreateFlow implements ClientStartableFlow{
}
@Suspendable
private String doTrunsaction(GameProposalState gameProposal) {
NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next();
private UtxoSignedTransaction prepareSignedTransaction(GameProposalState outputGameProposalState) {
final NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next();
UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder()
return ledgerService.createTransactionBuilder()
.setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(gameProposal)
.addCommand(new GameProposalContract.Create())
.addSignatories(gameProposal.getParticipants());
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
FlowSession session = flowMessaging.initiateFlow(gameProposal.getRecipient());
List<FlowSession> sessionsList = Arrays.asList(session);
ledgerService.finalize(signedTransaction, sessionsList);
return gameProposal.id.toString();
.addOutputState(outputGameProposalState)
.addCommand(Action.CREATE)
.addSignatories(outputGameProposalState.getParticipants())
.toSignedTransaction();
}
}

View File

@ -1,72 +0,0 @@
package djmil.cordacheckers.gameproposal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.UtxoSignedTransaction;
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator;
@InitiatedBy(protocol = "game-proposal-create")
public class CreateResponder 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.getOutputContractStates().get(0);
// Uses checkForBannedWords() and checkMessageFromMatchesCounterparty() functions
// to check whether to sign the transaction.
if (!checkParticipants(gameProposal, session.getCounterparty())) {
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) {
MemberX500Name myName = memberLookup.myInfo().getName();
if (gameProposal.getRecipient().compareTo(myName) == 0 &&
gameProposal.getSender().compareTo(counterpartyName) == 0)
return true;
return false;
}
}

View File

@ -10,26 +10,26 @@ import djmil.cordacheckers.states.GameProposalState;
// and a UUID. It is possible to create custom serializers for the JsonMarshallingService in the future.
public class ListItem {
public final String sender;
public final String recipient;
public final String recipientColor;
public final String issuer;
public final String acquier;
public final String acquierColor;
public final String message;
public final UUID id;
// Serialisation service requires a default constructor
public ListItem() {
this.sender = null;
this.recipient = null;
this.recipientColor = null;
this.issuer = null;
this.acquier = null;
this.acquierColor = null;
this.message = null;
this.id = null;
}
public ListItem(GameProposalState state) {
this.sender = state.getSender().getCommonName();
this.recipient = state.getRecipient().getCommonName();
this.recipientColor = state.getRecipientColor().name();
this.message = state.getMessage();
this.id = state.getId();
this.issuer = state.getIssuer().getCommonName();
this.acquier = state.getAcquier().getCommonName();
this.acquierColor = state.getRecipientColor().name();
this.message = state.getMessage();
this.id = state.getId();
}
}