GameProposal updates

- remove GameProposalResolution state
use ladger trx history instead

- use issuer/acquier instead of sennder/receiver
This commit is contained in:
djmil 2023-09-07 14:47:53 +02:00
parent 5cc579230f
commit 159bcd706e
10 changed files with 73 additions and 154 deletions

View File

@ -6,9 +6,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize @JsonDeserialize
public record GameProposal( public record GameProposal(
String sender, String issuer,
String recipient, String acquier,
Color recipientColor, Color acquierColor,
String message, String message,
UUID id) { UUID id) {

View File

@ -47,27 +47,33 @@ public class CordaClientTest {
@Test @Test
void testGemeProposalCreate() throws JsonMappingException, JsonProcessingException { void testGemeProposalCreate() throws JsonMappingException, JsonProcessingException {
final String gpSender = "alice"; final String gpIssuer = "alice";
final String gpReceiver = "bob"; final String gpAcquier = "bob";
final Color gpReceiverColor = Color.WHITE; final Color gpAcquierColor = Color.WHITE;
final String gpMessage = "GameProposal create test"; final String gpMessage = "GameProposal create test";
final UUID createdGpUuid = cordaClient.gameProposalCreate( final UUID createdGpUuid = cordaClient.gameProposalCreate(
holdingIdentityResolver.getByUsername(gpSender), holdingIdentityResolver.getByUsername(gpIssuer),
holdingIdentityResolver.getByUsername(gpReceiver), holdingIdentityResolver.getByUsername(gpAcquier),
gpReceiverColor, gpAcquierColor,
gpMessage gpMessage
); );
List<GameProposal> gpListSender = cordaClient.gameProposalList( List<GameProposal> gpListSender = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername(gpSender)); holdingIdentityResolver.getByUsername(gpIssuer));
assertThat(findByUuid(gpListSender, createdGpUuid)).isNotNull(); assertThat(findByUuid(gpListSender, createdGpUuid)).isNotNull();
List<GameProposal> gpListReceiver = cordaClient.gameProposalList( List<GameProposal> gpListReceiver = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername(gpReceiver)); holdingIdentityResolver.getByUsername(gpAcquier));
assertThat(findByUuid(gpListReceiver, createdGpUuid)).isNotNull(); 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 @Test

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

@ -13,8 +13,8 @@ import net.corda.v5.ledger.utxo.ContractState;
@BelongsToContract(GameProposalContract.class) @BelongsToContract(GameProposalContract.class)
public class GameProposalState implements ContractState { public class GameProposalState implements ContractState {
MemberX500Name sender; MemberX500Name issuer;
MemberX500Name recipient; MemberX500Name acquier;
Piece.Color recipientColor; Piece.Color recipientColor;
String message; String message;
UUID id; UUID id;
@ -23,27 +23,27 @@ public class GameProposalState implements ContractState {
// 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 issuer,
MemberX500Name recipient, MemberX500Name acquier,
Piece.Color recipientColor, Piece.Color recipientColor,
String message, String message,
UUID id, UUID id,
List<PublicKey> participants List<PublicKey> participants
) { ) {
this.sender = sender; this.issuer = issuer;
this.recipient = recipient; this.acquier = acquier;
this.recipientColor = recipientColor; this.recipientColor = recipientColor;
this.message = message; this.message = message;
this.id = id; this.id = id;
this.participants = participants; this.participants = participants;
} }
public MemberX500Name getSender() { public MemberX500Name getIssuer() {
return sender; return issuer;
} }
public MemberX500Name getRecipient() { public MemberX500Name getAcquier() {
return recipient; return acquier;
} }
public Piece.Color getRecipientColor() { public Piece.Color getRecipientColor() {

View File

@ -2,7 +2,6 @@ package djmil.cordacheckers.gameproposal;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -11,7 +10,6 @@ import org.slf4j.LoggerFactory;
import djmil.cordacheckers.FlowResult; import djmil.cordacheckers.FlowResult;
import djmil.cordacheckers.contracts.GameProposalContract; import djmil.cordacheckers.contracts.GameProposalContract;
import djmil.cordacheckers.states.GameProposalResolutionState;
import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.GameProposalState;
import net.corda.v5.application.flows.ClientRequestBody; import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow; import net.corda.v5.application.flows.ClientStartableFlow;
@ -23,8 +21,6 @@ import net.corda.v5.application.messaging.FlowMessaging;
import net.corda.v5.application.messaging.FlowSession; import net.corda.v5.application.messaging.FlowSession;
import net.corda.v5.base.annotations.Suspendable; import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.exceptions.CordaRuntimeException; import net.corda.v5.base.exceptions.CordaRuntimeException;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.Command;
import net.corda.v5.ledger.utxo.StateAndRef; import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.UtxoLedgerService; import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
@ -50,12 +46,6 @@ public class ActionFlow implements ClientStartableFlow {
@CordaInject @CordaInject
public MemberLookup memberLookup; 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())
);
@Override @Override
@Suspendable @Suspendable
public String call(ClientRequestBody requestBody) { public String call(ClientRequestBody requestBody) {
@ -63,13 +53,8 @@ public class ActionFlow implements ClientStartableFlow {
ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class); ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class);
StateAndRef<GameProposalState> inputState = findUnconsumedGameProposalState(args.getGameProposalUuid()); StateAndRef<GameProposalState> inputState = findUnconsumedGameProposalState(args.getGameProposalUuid());
GameProposalResolutionState outputState = new GameProposalResolutionState(
args.getAction(),
inputState.getState().getContractState()
);
String trxResult = doTrunsaction(inputState, outputState); String trxResult = doTrunsaction(args.getAction(), inputState /*, outputState*/);
return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService); return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService);
} }
@ -83,7 +68,7 @@ public class ActionFlow implements ClientStartableFlow {
@Suspendable @Suspendable
private StateAndRef<GameProposalState> findUnconsumedGameProposalState (UUID gameProposalUuid) { 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 * Note, this is an inefficient way to perform this operation if there are a large
* number of 'active' GameProposals exists in storage. * number of 'active' GameProposals exists in storage.
*/ */
@ -102,26 +87,30 @@ public class ActionFlow implements ClientStartableFlow {
} }
@Suspendable @Suspendable
private String doTrunsaction(StateAndRef<GameProposalState> inputState, GameProposalResolutionState outputState) { private String doTrunsaction(
GameProposalContract.Action action,
StateAndRef<GameProposalState> inputState
/* GameProposalResolutionState outputState*/
) {
UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder() UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder()
.setNotary(inputState.getState().getNotaryName()) .setNotary(inputState.getState().getNotaryName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addInputState(inputState.getRef()) .addInputState(inputState.getRef())
.addOutputState(outputState) //.addOutputState(outputState)
.addCommand(resoultion2command.get(outputState.outcome)) .addCommand(action)
.addSignatories(outputState.getParticipants()); .addSignatories(inputState.getState().getContractState().getParticipants());
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(); UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
FlowSession session = flowMessaging.initiateFlow( FlowSession session = flowMessaging.initiateFlow(
outputState.getRecipient(inputState.getState().getContractState()) action.getReceiver(inputState)
); );
List<FlowSession> sessionsList = Arrays.asList(session); List<FlowSession> sessionsList = Arrays.asList(session);
ledgerService.finalize(signedTransaction, sessionsList); ledgerService.finalize(signedTransaction, sessionsList);
return outputState.getOutcome()+"ED"; // REJECT+ED return action+"ED"; // REJECT+ED
} }
} }

View File

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

View File

@ -3,9 +3,7 @@ package djmil.cordacheckers.gameproposal;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import djmil.cordacheckers.contracts.GameProposalContract.Accept; import djmil.cordacheckers.contracts.GameProposalContract;
import djmil.cordacheckers.contracts.GameProposalContract.Cancel;
import djmil.cordacheckers.contracts.GameProposalContract.Reject;
import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.GameProposalState;
import net.corda.v5.application.flows.CordaInject; import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.flows.InitiatedBy; import net.corda.v5.application.flows.InitiatedBy;
@ -15,7 +13,6 @@ import net.corda.v5.application.messaging.FlowSession;
import net.corda.v5.base.annotations.Suspendable; import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.exceptions.CordaRuntimeException; import net.corda.v5.base.exceptions.CordaRuntimeException;
import net.corda.v5.base.types.MemberX500Name; 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.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator;
@ -37,25 +34,19 @@ public class ActionResponder implements ResponderFlow {
UtxoTransactionValidator txValidator = ledgerTransaction -> { UtxoTransactionValidator txValidator = ledgerTransaction -> {
GameProposalState gameProposal = (GameProposalState) ledgerTransaction.getInputContractStates().get(0); GameProposalState gameProposal = (GameProposalState) ledgerTransaction.getInputContractStates().get(0);
Command command = ledgerTransaction.getCommands(Command.class).get(0); GameProposalContract.Action action = ledgerTransaction.getCommands(GameProposalContract.Action.class).get(0);
if (!checkParticipants(gameProposal, session.getCounterparty(), command)) { checkSessionParticipants(session, gameProposal, action);
throw new CordaRuntimeException("Failed verification");
}
log.info("Verified the transaction - " + ledgerTransaction.getId()); 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 UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService
.receiveFinality(session, txValidator) .receiveFinality(session, txValidator)
.getTransaction(); .getTransaction();
log.info("Finished responder flow - " + finalizedSignedTransaction.getId()); log.info("Finished responder flow - " + finalizedSignedTransaction.getId());
} }
// Soft fails the flow and log the exception.
catch(Exception e) catch(Exception e)
{ {
log.warn("Exceptionally finished responder flow", e); log.warn("Exceptionally finished responder flow", e);
@ -63,21 +54,18 @@ public class ActionResponder implements ResponderFlow {
} }
@Suspendable @Suspendable
Boolean checkParticipants(GameProposalState gameProposal, MemberX500Name counterpartyName, Command command) { void checkSessionParticipants(
MemberX500Name myName = memberLookup.myInfo().getName(); FlowSession session,
GameProposalState gameProposal,
GameProposalContract.Action action
) {
final MemberX500Name myName = memberLookup.myInfo().getName();
final MemberX500Name otherName = session.getCounterparty();
if (command instanceof Reject || command instanceof Accept) { if (action.getReceiver(gameProposal).compareTo(myName) != 0)
if (gameProposal.getRecipient().compareTo(counterpartyName) == 0 && throw new CordaRuntimeException("Bad GameProposal aquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'");
gameProposal.getSender().compareTo(myName) == 0)
return true; if (action.getSender(gameProposal).compareTo(otherName) != 0)
} throw new CordaRuntimeException("Bad GameProposal issuer: expected '"+otherName+"', actual '"+gameProposal.getIssuer()+"'");
if (command instanceof Cancel) {
if (gameProposal.getRecipient().compareTo(myName) == 0 &&
gameProposal.getSender().compareTo(counterpartyName) == 0)
return true;
}
return false;
} }
} }

View File

@ -109,12 +109,12 @@ public class CreateFlow implements ClientStartableFlow{
.setNotary(notary.getName()) .setNotary(notary.getName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(gameProposal) .addOutputState(gameProposal)
.addCommand(new GameProposalContract.Create()) .addCommand(GameProposalContract.Action.CREATE)
.addSignatories(gameProposal.getParticipants()); .addSignatories(gameProposal.getParticipants());
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(); UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
FlowSession session = flowMessaging.initiateFlow(gameProposal.getRecipient()); FlowSession session = flowMessaging.initiateFlow(gameProposal.getAcquier());
List<FlowSession> sessionsList = Arrays.asList(session); List<FlowSession> sessionsList = Arrays.asList(session);

View File

@ -62,8 +62,8 @@ public class CreateResponder implements ResponderFlow {
Boolean checkParticipants(GameProposalState gameProposal, MemberX500Name counterpartyName) { Boolean checkParticipants(GameProposalState gameProposal, MemberX500Name counterpartyName) {
MemberX500Name myName = memberLookup.myInfo().getName(); MemberX500Name myName = memberLookup.myInfo().getName();
if (gameProposal.getRecipient().compareTo(myName) == 0 && if (gameProposal.getAcquier().compareTo(myName) == 0 &&
gameProposal.getSender().compareTo(counterpartyName) == 0) gameProposal.getIssuer().compareTo(counterpartyName) == 0)
return true; return true;
return false; 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. // and a UUID. It is possible to create custom serializers for the JsonMarshallingService in the future.
public class ListItem { public class ListItem {
public final String sender; public final String issuer;
public final String recipient; public final String acquier;
public final String recipientColor; public final String acquierColor;
public final String message; 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.issuer = null;
this.recipient = null; this.acquier = null;
this.recipientColor = null; this.acquierColor = null;
this.message = null; this.message = null;
this.id = null; this.id = null;
} }
public ListItem(GameProposalState state) { public ListItem(GameProposalState state) {
this.sender = state.getSender().getCommonName(); this.issuer = state.getIssuer().getCommonName();
this.recipient = state.getRecipient().getCommonName(); this.acquier = state.getAcquier().getCommonName();
this.recipientColor = state.getRecipientColor().name(); this.acquierColor = state.getRecipientColor().name();
this.message = state.getMessage(); this.message = state.getMessage();
this.id = state.getId(); this.id = state.getId();
} }
} }