Corda: CommitSubFlow
This commit is contained in:
parent
159bcd706e
commit
e235ecb942
@ -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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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";
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user