Corda: CommitSubFlow

This commit is contained in:
djmil 2023-09-07 21:33:37 +02:00
parent 159bcd706e
commit e235ecb942
10 changed files with 225 additions and 189 deletions

View File

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

View File

@ -2,9 +2,9 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import java.util.UUID; import java.util.UUID;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonSerialize @JsonIgnoreProperties(ignoreUnknown = true)
public record GameProposalCreateRes(UUID successStatus, String failureStatus) { public record GameProposalCreateRes(UUID successStatus, String failureStatus) {
} }

View File

@ -2,8 +2,11 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import djmil.cordacheckers.cordaclient.dao.GameProposal; import djmil.cordacheckers.cordaclient.dao.GameProposal;
@JsonIgnoreProperties(ignoreUnknown = true)
public record GameProposalListRes(List<GameProposal> successStatus, String failureStatus) { public record GameProposalListRes(List<GameProposal> successStatus, String failureStatus) {
} }

View File

@ -3,66 +3,96 @@ 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.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.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.Command; import net.corda.v5.ledger.utxo.Command;
import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
private final static Logger log = LoggerFactory.getLogger(GameProposalContract.class); private final static Logger log = LoggerFactory.getLogger(GameProposalContract.class);
public static class Create implements Command { } public static enum Action implements Command {
public static class Accept implements Command { } CREATE,
public static class Reject implements Command { } ACCEPT,
public static class Cancel implements Command { } 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 @Override
public void verify(UtxoLedgerTransaction trx) { public void verify(UtxoLedgerTransaction trx) {
log.info("GameProposalContract.verify() called"); log.info("GameProposalContract.verify() called");
requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND);
Command command = trx.getCommands().get(0);
if (command instanceof Create) { switch ((Action)(trx.getCommands().get(0))) {
requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); case CREATE: {
requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE); 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);
requireThat(outputState.getRecipientColor() != null, NON_NULL_RECIPIENT_COLOR); GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0);
} else requireThat(outputState != null, CREATE_OUTPUT_STATE);
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);
GameProposalState inputState = trx.getInputStates(GameProposalState.class).get(0); requireThat(outputState.getRecipientColor() != null, NON_NULL_RECIPIENT_COLOR);
requireThat(inputState != null, REJECT_INPUT_STATE); break; }
GameProposalResolutionState outputState = trx.getOutputStates(GameProposalResolutionState.class).get(0);
requireThat(outputState != null, REJECT_OUTPUT_STATE);
requireThat(outputState.outcome == GameProposalResolutionState.Resolution.REJECT, REJECT_OUTPUT_OUTCOME); case ACCEPT:
} else throw new CordaRuntimeException("Unimplemented!");
if (command instanceof Cancel) { //break;
requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE);
requireThat(trx.getOutputContractStates().size() == 1, CANCEL_OUTPUT_STATE);
GameProposalState inputState = trx.getInputStates(GameProposalState.class).get(0); case REJECT:
requireThat(inputState != null, CANCEL_INPUT_STATE); requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE);
requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE);
GameProposalResolutionState outputState = trx.getOutputStates(GameProposalResolutionState.class).get(0); requireThat(trx.getInputStates(GameProposalState.class).get(0) != null, REJECT_INPUT_STATE);
requireThat(outputState != null, CANCEL_OUTPUT_STATE); break;
requireThat(outputState.outcome == GameProposalResolutionState.Resolution.CANCEL, CANCEL_OUTPUT_OUTCOME); case CANCEL:
} else { requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE);
throw new CordaRuntimeException(UNKNOWN_COMMAND); requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE);
requireThat(trx.getInputStates(GameProposalState.class).get(0) != null, CANCEL_INPUT_STATE);
break;
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 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_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_STATE = "Reject command should have no 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_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_STATE = "Cancel command should have no output states";
static final String CANCEL_OUTPUT_OUTCOME = "Cancel output state should have Resolution value set to CANCEL";
} }

View File

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

View File

@ -1,6 +1,5 @@
package djmil.cordacheckers.gameproposal; package djmil.cordacheckers.gameproposal;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -9,18 +8,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import djmil.cordacheckers.FlowResult; import djmil.cordacheckers.FlowResult;
import djmil.cordacheckers.contracts.GameProposalContract;
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;
import net.corda.v5.application.flows.CordaInject; 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.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.annotations.Suspendable;
import net.corda.v5.base.exceptions.CordaRuntimeException; import net.corda.v5.base.exceptions.CordaRuntimeException;
import net.corda.v5.crypto.SecureHash;
import net.corda.v5.ledger.utxo.StateAndRef; import net.corda.v5.ledger.utxo.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;
@ -29,7 +25,8 @@ import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@InitiatingFlow(protocol = "game-proposal-action") import static djmil.cordacheckers.contracts.GameProposalContract.Action;
public class ActionFlow implements ClientStartableFlow { public class ActionFlow implements ClientStartableFlow {
private final static Logger log = LoggerFactory.getLogger(CreateFlow.class); private final static Logger log = LoggerFactory.getLogger(CreateFlow.class);
@ -41,22 +38,24 @@ public class ActionFlow implements ClientStartableFlow {
public UtxoLedgerService ledgerService; public UtxoLedgerService ledgerService;
@CordaInject @CordaInject
public FlowMessaging flowMessaging; public FlowEngine flowEngine;
@CordaInject
public MemberLookup memberLookup;
@Override @Override
@Suspendable @Suspendable
public String call(ClientRequestBody requestBody) { public String call(ClientRequestBody requestBody) {
try { 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());
String trxResult = doTrunsaction(args.getAction(), inputState /*, outputState*/); final UtxoSignedTransaction trx = prepareSignedTransaction(action, inputState);
return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService); final SecureHash trxId = this.flowEngine
.subFlow( new CommitSubFlow(trx, action.getReceiver(inputState)) );
return new FlowResult(action+"ED", trxId) // REJECT+ED
.toJsonEncodedString(jsonMarshallingService);
} }
catch (Exception e) { catch (Exception e) {
log.warn("GameProposalAction flow failed to process utxo request body " + requestBody + log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +
@ -87,30 +86,24 @@ public class ActionFlow implements ClientStartableFlow {
} }
@Suspendable @Suspendable
private String doTrunsaction( private UtxoSignedTransaction prepareSignedTransaction(
GameProposalContract.Action action, Action action,
StateAndRef<GameProposalState> inputState StateAndRef<GameProposalState> inputState
/* GameProposalResolutionState outputState*/
) { ) {
UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder() UtxoTransactionBuilder trxBuilder = 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)
.addCommand(action) .addCommand(action)
.addSignatories(inputState.getState().getContractState().getParticipants()); .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( return trxBuilder.toSignedTransaction();
action.getReceiver(inputState)
);
List<FlowSession> sessionsList = Arrays.asList(session);
ledgerService.finalize(signedTransaction, sessionsList);
return action+"ED"; // REJECT+ED
} }
} }

View File

@ -3,7 +3,6 @@ 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;
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;
@ -14,12 +13,15 @@ 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.UtxoLedgerService; 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.UtxoSignedTransaction;
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator;
@InitiatedBy(protocol = "game-proposal-action") import static djmil.cordacheckers.contracts.GameProposalContract.Action;
public class ActionResponder implements ResponderFlow {
private final static Logger log = LoggerFactory.getLogger(CreateResponder.class); @InitiatedBy(protocol = "game-proposal")
public class CommitResponderFlow implements ResponderFlow {
private final static Logger log = LoggerFactory.getLogger(CommitResponderFlow.class);
@CordaInject @CordaInject
public MemberLookup memberLookup; public MemberLookup memberLookup;
@ -32,12 +34,16 @@ public class ActionResponder implements ResponderFlow {
public void call(FlowSession session) { public void call(FlowSession session) {
try { try {
UtxoTransactionValidator txValidator = ledgerTransaction -> { UtxoTransactionValidator txValidator = ledgerTransaction -> {
GameProposalState gameProposal = (GameProposalState) ledgerTransaction.getInputContractStates().get(0); final Action action = ledgerTransaction.getCommands(Action.class).get(0);
GameProposalContract.Action action = ledgerTransaction.getCommands(GameProposalContract.Action.class).get(0); final GameProposalState gameProposal = getGameProposal(ledgerTransaction);
checkSessionParticipants(session, gameProposal, action); checkSessionParticipants(session, gameProposal, action);
/*
* Other checks / actions ?
*/
log.info("Verified the transaction - " + ledgerTransaction.getId()); log.info("Verified the transaction - " + ledgerTransaction.getId());
}; };
@ -47,9 +53,8 @@ public class ActionResponder implements ResponderFlow {
log.info("Finished responder flow - " + finalizedSignedTransaction.getId()); log.info("Finished responder flow - " + finalizedSignedTransaction.getId());
} }
catch(Exception e) catch(Exception e) {
{ log.warn("Responder flow failed: ", e);
log.warn("Exceptionally finished responder flow", e);
} }
} }
@ -57,15 +62,34 @@ public class ActionResponder implements ResponderFlow {
void checkSessionParticipants( void checkSessionParticipants(
FlowSession session, FlowSession session,
GameProposalState gameProposal, GameProposalState gameProposal,
GameProposalContract.Action action Action action
) { ) {
final MemberX500Name myName = memberLookup.myInfo().getName(); final MemberX500Name myName = memberLookup.myInfo().getName();
final MemberX500Name otherName = session.getCounterparty(); final MemberX500Name otherName = session.getCounterparty();
if (action.getReceiver(gameProposal).compareTo(myName) != 0) if (action.getRespondent(gameProposal).compareTo(myName) != 0)
throw new CordaRuntimeException("Bad GameProposal aquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'"); throw new CordaRuntimeException("Bad GameProposal acquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'");
if (action.getSender(gameProposal).compareTo(otherName) != 0) if (action.getInitiator(gameProposal).compareTo(otherName) != 0)
throw new CordaRuntimeException("Bad GameProposal issuer: expected '"+otherName+"', actual '"+gameProposal.getIssuer()+"'"); 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,7 +4,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import djmil.cordacheckers.FlowResult; import djmil.cordacheckers.FlowResult;
import djmil.cordacheckers.contracts.GameProposalContract;
import djmil.cordacheckers.states.GameProposalState; import djmil.cordacheckers.states.GameProposalState;
import djmil.cordacheckers.states.Piece; import djmil.cordacheckers.states.Piece;
import net.corda.v5.application.flows.ClientRequestBody; import net.corda.v5.application.flows.ClientRequestBody;
@ -14,26 +13,23 @@ import net.corda.v5.application.flows.FlowEngine;
import net.corda.v5.application.flows.InitiatingFlow; import net.corda.v5.application.flows.InitiatingFlow;
import net.corda.v5.application.marshalling.JsonMarshallingService; import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.application.membership.MemberLookup; 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.annotations.Suspendable;
import net.corda.v5.base.types.MemberX500Name; 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.common.NotaryLookup;
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.UtxoTransactionBuilder;
import net.corda.v5.membership.MemberInfo; import net.corda.v5.membership.MemberInfo;
import net.corda.v5.membership.NotaryInfo; import net.corda.v5.membership.NotaryInfo;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static djmil.cordacheckers.contracts.GameProposalContract.Action;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@InitiatingFlow(protocol = "game-proposal-create")
public class CreateFlow implements ClientStartableFlow{ public class CreateFlow implements ClientStartableFlow{
private final static Logger log = LoggerFactory.getLogger(CreateFlow.class); private final static Logger log = LoggerFactory.getLogger(CreateFlow.class);
@ -53,19 +49,22 @@ public class CreateFlow implements ClientStartableFlow{
@CordaInject @CordaInject
public FlowEngine flowEngine; public FlowEngine flowEngine;
@CordaInject
public FlowMessaging flowMessaging;
@Suspendable @Suspendable
@Override @Override
public String call(ClientRequestBody requestBody) { public String call(ClientRequestBody requestBody) {
try { try {
log.info("flow: Create Game Proposal"); log.info("flow: Create Game Proposal");
final Action actino = Action.CREATE;
GameProposalState gameProposal = buildGameProposalStateFrom(requestBody); final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody);
String trxResult = doTrunsaction(gameProposal);
final UtxoSignedTransaction trx = prepareSignedTransaction(newGameProposal);
return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService); final SecureHash trxId = this.flowEngine
.subFlow(new CommitSubFlow(trx, actino.getRespondent(newGameProposal)));
return new FlowResult(newGameProposal.getId(), trxId)
.toJsonEncodedString(jsonMarshallingService);
} }
catch (Exception e) { catch (Exception e) {
log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody + log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody +
@ -102,24 +101,15 @@ public class CreateFlow implements ClientStartableFlow{
} }
@Suspendable @Suspendable
private String doTrunsaction(GameProposalState gameProposal) { private UtxoSignedTransaction prepareSignedTransaction(GameProposalState outputGameProposalState) {
NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next(); final NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next();
UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder() return ledgerService.createTransactionBuilder()
.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(outputGameProposalState)
.addCommand(GameProposalContract.Action.CREATE) .addCommand(Action.CREATE)
.addSignatories(gameProposal.getParticipants()); .addSignatories(outputGameProposalState.getParticipants())
.toSignedTransaction();
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
FlowSession session = flowMessaging.initiateFlow(gameProposal.getAcquier());
List<FlowSession> sessionsList = Arrays.asList(session);
ledgerService.finalize(signedTransaction, sessionsList);
return gameProposal.getId().toString();
} }
} }

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.getAcquier().compareTo(myName) == 0 &&
gameProposal.getIssuer().compareTo(counterpartyName) == 0)
return true;
return false;
}
}