Corda: add FlowResult class
refactor ActionFlow
This commit is contained in:
parent
218482034d
commit
7df57cb4d2
@ -6,6 +6,7 @@ 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;
|
||||
|
||||
@ -22,6 +23,14 @@ public class GameProposalResolutionState implements ContractState {
|
||||
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,
|
||||
@ -39,4 +48,16 @@ public class GameProposalResolutionState implements ContractState {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
package djmil.cordacheckers;
|
||||
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
|
||||
public class FlowResult {
|
||||
public final Object successStatus;
|
||||
public final String failureStatus;
|
||||
|
||||
public FlowResult(Object success) {
|
||||
this.successStatus = success;
|
||||
this.failureStatus = null;
|
||||
}
|
||||
|
||||
public FlowResult(Exception exception) {
|
||||
this.successStatus = null;
|
||||
this.failureStatus = exception.getMessage();
|
||||
}
|
||||
|
||||
public String toJsonEncodedString(JsonMarshallingService jsonMarshallingService) {
|
||||
return jsonMarshallingService.format(this);
|
||||
}
|
||||
|
||||
}
|
@ -3,11 +3,16 @@ 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;
|
||||
|
||||
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 djmil.cordacheckers.states.GameProposalResolutionState.Resolution;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
@ -18,22 +23,21 @@ 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.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.TransactionState;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
@InitiatingFlow(protocol = "game-proposal-action")
|
||||
public class ActionFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CreateFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@ -47,62 +51,77 @@ public class ActionFlow implements ClientStartableFlow {
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
private final static Map<GameProposalResolutionState.Resolution, Command> resoultion2command = Map.ofEntries(
|
||||
entry(GameProposalResolutionState.Resolution.CANCEL, new GameProposalContract.Cancel()),
|
||||
entry(GameProposalResolutionState.Resolution.REJECT, new GameProposalContract.Reject()),
|
||||
entry(GameProposalResolutionState.Resolution.ACCEPT, new GameProposalContract.Accept())
|
||||
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
|
||||
@Suspendable
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class);
|
||||
try {
|
||||
ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class);
|
||||
|
||||
StateAndRef<GameProposalState> inputState = findUnconsumedGameProposalState(args.getGameProposalUuid());
|
||||
|
||||
GameProposalResolutionState outputState = new GameProposalResolutionState(
|
||||
args.getAction(),
|
||||
inputState.getState().getContractState()
|
||||
);
|
||||
|
||||
String trxResult = doTrunsaction(inputState, outputState);
|
||||
|
||||
return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +
|
||||
" because: " + e.getMessage());
|
||||
return new FlowResult(e).toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private StateAndRef<GameProposalState> findUnconsumedGameProposalState (UUID gameProposalUuid) {
|
||||
/*
|
||||
* Look up the latest unconsumed ChatState with the given id.
|
||||
* Note, this code brings all unconsumed states back, then filters them. This is an
|
||||
* inefficient way to perform this operation when there are a large number of chats
|
||||
* Get list of all unconsumed aka 'actuve' 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.
|
||||
*/
|
||||
List<StateAndRef<GameProposalState>> stateAndRefs = this.ledgerService
|
||||
.findUnconsumedStatesByType(GameProposalState.class);
|
||||
|
||||
List<StateAndRef<GameProposalState>> stateAndRefsWithId = stateAndRefs
|
||||
.findUnconsumedStatesByType(GameProposalState.class)
|
||||
.stream()
|
||||
.filter(sar -> sar.getState().getContractState().getId().equals(args.gameProposalUuid))
|
||||
.collect(toList());
|
||||
if (stateAndRefsWithId.size() != 1)
|
||||
throw new CordaRuntimeException("Multiple or zero GameProposal states with id " + args.gameProposalUuid + " found");
|
||||
.filter(sar -> sar.getState().getContractState().getId().equals(gameProposalUuid))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
StateAndRef<GameProposalState> stateAndRef = stateAndRefsWithId.get(0);
|
||||
TransactionState<GameProposalState> trxState = stateAndRef.getState();
|
||||
GameProposalState state = trxState.getContractState();
|
||||
if (stateAndRefs.size() != 1) {
|
||||
throw new CordaRuntimeException("Expected only one GameProposal state with id " + gameProposalUuid +
|
||||
", but found " + stateAndRefs.size());
|
||||
}
|
||||
|
||||
GameProposalResolutionState outputState = new GameProposalResolutionState(
|
||||
GameProposalResolutionState.Resolution.valueOf(args.action),
|
||||
state.getParticipants()
|
||||
);
|
||||
return stateAndRefs.get(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Build draft trx
|
||||
*/
|
||||
@Suspendable
|
||||
private String doTrunsaction(StateAndRef<GameProposalState> inputState, GameProposalResolutionState outputState) {
|
||||
UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder()
|
||||
.setNotary(trxState.getNotaryName())
|
||||
.setNotary(inputState.getState().getNotaryName())
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addInputState(stateAndRef.getRef())
|
||||
.addInputState(inputState.getRef())
|
||||
.addOutputState(outputState)
|
||||
.addCommand(resoultion2command.get(outputState.outcome))
|
||||
.addSignatories(state.getParticipants());
|
||||
.addSignatories(outputState.getParticipants());
|
||||
|
||||
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
|
||||
|
||||
FlowSession session = flowMessaging.initiateFlow(
|
||||
outputState.outcome == Resolution.CANCEL ? state.getRecipient() : state.getSender() // TODO: readability
|
||||
outputState.getRecipient(inputState.getState().getContractState())
|
||||
);
|
||||
|
||||
List<FlowSession> sessionsList = Arrays.asList(session);
|
||||
|
||||
ledgerService.finalize(signedTransaction, sessionsList);
|
||||
|
||||
return args.action+"ED"; // REJECT+ED
|
||||
return outputState.getOutcome()+"ED"; // REJECT+ED
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,9 +2,12 @@ package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import djmil.cordacheckers.states.GameProposalResolutionState;
|
||||
import djmil.cordacheckers.states.GameProposalResolutionState.Resolution;
|
||||
|
||||
public class ActionFlowArgs {
|
||||
public final UUID gameProposalUuid;
|
||||
public final String action;
|
||||
private UUID gameProposalUuid;
|
||||
private String action;
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public ActionFlowArgs() {
|
||||
@ -12,4 +15,11 @@ public class ActionFlowArgs {
|
||||
this.action = null;
|
||||
}
|
||||
|
||||
public Resolution getAction() {
|
||||
return GameProposalResolutionState.Resolution.valueOf(this.action);
|
||||
}
|
||||
|
||||
public UUID getGameProposalUuid() {
|
||||
return this.gameProposalUuid;
|
||||
}
|
||||
}
|
||||
|
@ -34,11 +34,8 @@ public class ActionResponder implements ResponderFlow {
|
||||
@Override
|
||||
public void call(FlowSession session) {
|
||||
try {
|
||||
// Defines the lambda validator used in receiveFinality below.
|
||||
UtxoTransactionValidator txValidator = ledgerTransaction -> {
|
||||
GameProposalState gameProposal = (GameProposalState) ledgerTransaction.getInputContractStates().get(0);
|
||||
// Uses checkForBannedWords() and checkMessageFromMatchesCounterparty() functions
|
||||
// to check whether to sign the transaction.
|
||||
|
||||
Command command = ledgerTransaction.getCommands(Command.class).get(0);
|
||||
|
||||
@ -68,7 +65,7 @@ public class ActionResponder implements ResponderFlow {
|
||||
@Suspendable
|
||||
Boolean checkParticipants(GameProposalState gameProposal, MemberX500Name counterpartyName, Command command) {
|
||||
MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
log.info("Responder validation:\n command "+command+"\n me " + myName.toString() + "\n opponent " + counterpartyName.toString());
|
||||
|
||||
if (command instanceof Reject || command instanceof Accept) {
|
||||
if (gameProposal.getRecipient().compareTo(counterpartyName) == 0 &&
|
||||
gameProposal.getSender().compareTo(myName) == 0)
|
||||
|
@ -3,6 +3,7 @@ package djmil.cordacheckers.gameproposal;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.FlowResult;
|
||||
import djmil.cordacheckers.contracts.GameProposalContract;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
@ -15,7 +16,6 @@ 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.common.NotaryLookup;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
@ -62,13 +62,14 @@ public class CreateFlow implements ClientStartableFlow{
|
||||
log.info("flow: Create Game Proposal");
|
||||
|
||||
GameProposalState gameProposal = buildGameProposalStateFrom(requestBody);
|
||||
String result = doTrunsaction(gameProposal);
|
||||
String trxResult = doTrunsaction(gameProposal);
|
||||
|
||||
return result;
|
||||
return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody + " because: " + e.getMessage());
|
||||
throw new CordaRuntimeException(e.getMessage());
|
||||
log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody +
|
||||
" because: " + e.getMessage());
|
||||
return new FlowResult(e).toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,12 +117,7 @@ public class CreateFlow implements ClientStartableFlow{
|
||||
|
||||
List<FlowSession> sessionsList = Arrays.asList(session);
|
||||
|
||||
UtxoSignedTransaction finalizedSignedTransaction = ledgerService
|
||||
.finalize(signedTransaction, sessionsList)
|
||||
.getTransaction();
|
||||
|
||||
|
||||
// final String trxId = finalizedSignedTransaction.getId().toString();
|
||||
ledgerService.finalize(signedTransaction, sessionsList);
|
||||
|
||||
return gameProposal.id.toString();
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.FlowResult;
|
||||
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.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
|
||||
public class ListFlow implements ClientStartableFlow {
|
||||
@ -23,19 +25,25 @@ public class ListFlow implements ClientStartableFlow {
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
log.info("ListChatsFlow.call() called");
|
||||
try {
|
||||
log.info("ListChatsFlow.call() called");
|
||||
|
||||
// Queries the VNode's vault for unconsumed states and converts the result to a serializable DTO.
|
||||
java.util.List<StateAndRef<GameProposalState>> states = utxoLedgerService.findUnconsumedStatesByType(GameProposalState.class);
|
||||
// Queries the VNode's vault for unconsumed states and converts the resulting
|
||||
// List<StateAndRef<GameProposalState>> to a _serializable_ List<ListItem> DTO
|
||||
List<ListItem> unconsumedGameProposaList = utxoLedgerService
|
||||
.findUnconsumedStatesByType(GameProposalState.class)
|
||||
.stream()
|
||||
.map( stateAndRef -> new ListItem(stateAndRef.getState().getContractState()) )
|
||||
.collect(Collectors.toList());
|
||||
|
||||
java.util.List<ListItem> results = states.stream().map( stateAndRef ->
|
||||
new ListItem(stateAndRef.getState().getContractState())
|
||||
).collect(Collectors.toList());
|
||||
|
||||
// Uses the JsonMarshallingService's format() function to serialize the DTO to Json.
|
||||
return jsonMarshallingService.format(results);
|
||||
return new FlowResult(unconsumedGameProposaList).toJsonEncodedString(jsonMarshallingService);
|
||||
} catch (Exception e) {
|
||||
log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody + " because: " + e.getMessage());
|
||||
return new FlowResult(e).toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user