Compare commits
6 Commits
7f7722ecc0
...
4c2569810a
Author | SHA1 | Date | |
---|---|---|---|
4c2569810a | |||
d9b885b550 | |||
fee93a2b10 | |||
fdfcd711a7 | |||
9a49e68a1f | |||
01fd273c3a |
@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
||||
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
||||
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
|
||||
@ -29,12 +30,13 @@ import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalListRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq.Action;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardCommandReq;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardCommandRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameBoardListRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionAcceptRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandAcceptRes;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandReq;
|
||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCommandRes;
|
||||
|
||||
@Service
|
||||
public class CordaClient {
|
||||
@ -66,10 +68,10 @@ public class CordaClient {
|
||||
|
||||
/**
|
||||
* Obtain list of unconsumed (active) GameProposals
|
||||
* @param holdingIdentity
|
||||
* @param myHoldingIdentity
|
||||
* @return GameProposals list in JSON form
|
||||
*/
|
||||
public List<GameProposal> gameProposalList(HoldingIdentity holdingIdentity) {
|
||||
public List<GameProposal> gameProposalList(HoldingIdentity myHoldingIdentity) {
|
||||
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gp.list-" + UUID.randomUUID(),
|
||||
@ -78,7 +80,7 @@ public class CordaClient {
|
||||
);
|
||||
|
||||
final GameProposalListRes listFlowResult = cordaFlowExecute(
|
||||
holdingIdentity,
|
||||
myHoldingIdentity,
|
||||
requestBody,
|
||||
GameProposalListRes.class
|
||||
);
|
||||
@ -127,49 +129,49 @@ public class CordaClient {
|
||||
) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gp.reject-" +UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameproposal.ActionFlow",
|
||||
new GameProposalActionReq(
|
||||
"djmil.cordacheckers.gameproposal.CommandFlow",
|
||||
new GameProposalCommandReq(
|
||||
gameProposalUuid.toString(),
|
||||
Action.REJECT
|
||||
GameProposalCommandReq.Command.REJECT
|
||||
)
|
||||
);
|
||||
|
||||
final GameProposalActionRes actionResult = cordaFlowExecute(
|
||||
final GameProposalCommandRes actionResult = cordaFlowExecute(
|
||||
myHoldingIdentity,
|
||||
requestBody,
|
||||
GameProposalActionRes.class
|
||||
GameProposalCommandRes.class
|
||||
);
|
||||
|
||||
if (actionResult.failureStatus() != null) {
|
||||
System.out.println("GameProposal.ActionFlow failed: " + actionResult.failureStatus());
|
||||
throw new RuntimeException("GameProsal: ActionFlow execution has failed");
|
||||
System.out.println("GameProposal.CommandFlow failed: " + actionResult.failureStatus());
|
||||
throw new RuntimeException("GameProsal: CommandFlow execution has failed");
|
||||
}
|
||||
|
||||
return actionResult.successStatus();
|
||||
}
|
||||
|
||||
public UUID gameProposalAccept(
|
||||
public UUID gameProposalAccept( // TODO shall return newGameBoard
|
||||
HoldingIdentity myHoldingIdentity,
|
||||
UUID gameProposalUuid
|
||||
) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gp.accept-" +UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameproposal.ActionFlow",
|
||||
new GameProposalActionReq(
|
||||
"djmil.cordacheckers.gameproposal.CommandFlow",
|
||||
new GameProposalCommandReq(
|
||||
gameProposalUuid.toString(),
|
||||
Action.ACCEPT
|
||||
GameProposalCommandReq.Command.ACCEPT
|
||||
)
|
||||
);
|
||||
|
||||
final GameProposalActionAcceptRes actionResult = cordaFlowExecute(
|
||||
final GameProposalCommandAcceptRes actionResult = cordaFlowExecute(
|
||||
myHoldingIdentity,
|
||||
requestBody,
|
||||
GameProposalActionAcceptRes.class
|
||||
GameProposalCommandAcceptRes.class
|
||||
);
|
||||
|
||||
if (actionResult.failureStatus() != null) {
|
||||
System.out.println("GameProposal.ActionFlow failed: " + actionResult.failureStatus());
|
||||
throw new RuntimeException("GameProsal: ActionFlow execution has failed");
|
||||
System.out.println("GameProposal.CommandFlow failed: " + actionResult.failureStatus());
|
||||
throw new RuntimeException("GameProsal: CommandFlow execution has failed");
|
||||
}
|
||||
|
||||
return actionResult.successStatus();
|
||||
@ -197,6 +199,34 @@ public class CordaClient {
|
||||
return listFlowResult.successStatus();
|
||||
}
|
||||
|
||||
public GameBoard gameBoardCommand(
|
||||
HoldingIdentity myHoldingIdentity,
|
||||
UUID gameBoardUuid,
|
||||
GameBoardCommand command
|
||||
) {
|
||||
final RequestBody requestBody = new RequestBody(
|
||||
"gb.command-" +command.getType() +UUID.randomUUID(),
|
||||
"djmil.cordacheckers.gameboard.CommandFlow",
|
||||
new GameBoardCommandReq(
|
||||
gameBoardUuid,
|
||||
command
|
||||
)
|
||||
);
|
||||
|
||||
final GameBoardCommandRes moveResult = cordaFlowExecute(
|
||||
myHoldingIdentity,
|
||||
requestBody,
|
||||
GameBoardCommandRes.class
|
||||
);
|
||||
|
||||
if (moveResult.failureStatus() != null) {
|
||||
System.out.println("GameBoard.MoveFlow failed: " + moveResult.failureStatus());
|
||||
throw new RuntimeException("GameBoard: MoveFlow execution has failed");
|
||||
}
|
||||
|
||||
return moveResult.successStatus();
|
||||
}
|
||||
|
||||
private <T> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowResultType) {
|
||||
|
||||
try {
|
||||
@ -283,4 +313,5 @@ public class CordaClient {
|
||||
|
||||
throw new RuntimeException ("CordaClient.cordaFlowPoll: retry limit");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ public record GameBoard(
|
||||
Piece.Color opponentColor,
|
||||
Boolean opponentMove,
|
||||
Map<Integer, Piece> board,
|
||||
GameBoardCommand previousCommand,
|
||||
String message,
|
||||
UUID id) implements CordaState {
|
||||
|
||||
|
@ -0,0 +1,48 @@
|
||||
package djmil.cordacheckers.cordaclient.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GameBoardCommand {
|
||||
public static enum Type {
|
||||
MOVE,
|
||||
SURRENDER,
|
||||
REQUEST_DRAW,
|
||||
REQUEST_VICTORY,
|
||||
FINISH; // aka accept DRAW or VICTORY request
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final List<Integer> move; // [0] from, [1] to
|
||||
|
||||
public GameBoardCommand() {
|
||||
this.type = null;
|
||||
this.move = null;
|
||||
}
|
||||
|
||||
public GameBoardCommand(Type type) {
|
||||
this.type = type;
|
||||
this.move = null;
|
||||
}
|
||||
|
||||
public GameBoardCommand(List<Integer> move) {
|
||||
this.type = Type.MOVE;
|
||||
this.move = move;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public List<Integer> getMove() {
|
||||
return move;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (type == Type.MOVE)
|
||||
return move.get(0) +"->" +move.get(1);
|
||||
else
|
||||
return type.name();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import java.util.UUID;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
|
||||
|
||||
public record GameBoardCommandReq(UUID gameBoardUuid, GameBoardCommand command) {
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||
|
||||
public record GameBoardCommandRes(GameBoard successStatus, String failureStatus) {
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record GameProposalActionAcceptRes(UUID successStatus, String transactionId, String failureStatus) {
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
public record GameProposalActionRes(String successStatus, String transactionId, String failureStatus) {
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record GameProposalCommandAcceptRes(UUID successStatus, String transactionId, String failureStatus) {
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
public record GameProposalActionReq(String gameProposalUuid, Action action) {
|
||||
public enum Action {
|
||||
public record GameProposalCommandReq(String gameProposalUuid, Command command) {
|
||||
public enum Command {
|
||||
ACCEPT,
|
||||
REJECT,
|
||||
CANCEL
|
@ -0,0 +1,5 @@
|
||||
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
|
||||
|
||||
public record GameProposalCommandRes(String successStatus, String transactionId, String failureStatus) {
|
||||
|
||||
}
|
@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.CordaState;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoard;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameBoardCommand;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameProposal;
|
||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
||||
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
||||
@ -46,7 +47,7 @@ public class CordaClientTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGemeProposalCreate() throws JsonMappingException, JsonProcessingException {
|
||||
void testGameProposalCreate() throws JsonMappingException, JsonProcessingException {
|
||||
final String gpIssuer = "alice";
|
||||
final String gpAcquier = "bob";
|
||||
final Piece.Color gpAcquierColor = Piece.Color.WHITE;
|
||||
@ -56,8 +57,7 @@ public class CordaClientTest {
|
||||
holdingIdentityResolver.getByUsername(gpIssuer),
|
||||
holdingIdentityResolver.getByUsername(gpAcquier),
|
||||
gpAcquierColor,
|
||||
gpMessage
|
||||
);
|
||||
gpMessage);
|
||||
|
||||
List<GameProposal> gpListSender = cordaClient.gameProposalList(
|
||||
holdingIdentityResolver.getByUsername(gpIssuer));
|
||||
@ -77,7 +77,7 @@ public class CordaClientTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGemeProposalReject() throws JsonMappingException, JsonProcessingException {
|
||||
void testGameProposalReject() throws JsonMappingException, JsonProcessingException {
|
||||
final String gpIssuer = "alice";
|
||||
final String gpAcquier = "bob";
|
||||
final Piece.Color gpReceiverColor = Piece.Color.WHITE;
|
||||
@ -87,8 +87,7 @@ public class CordaClientTest {
|
||||
holdingIdentityResolver.getByUsername(gpIssuer),
|
||||
holdingIdentityResolver.getByUsername(gpAcquier),
|
||||
gpReceiverColor,
|
||||
gpMessage
|
||||
);
|
||||
gpMessage);
|
||||
|
||||
System.out.println("Create GP UUID "+ gpUuid);
|
||||
|
||||
@ -100,8 +99,7 @@ public class CordaClientTest {
|
||||
|
||||
final String rejectRes = cordaClient.gameProposalReject(
|
||||
holdingIdentityResolver.getByUsername(gpAcquier),
|
||||
gpUuid
|
||||
);
|
||||
gpUuid);
|
||||
|
||||
assertThat(rejectRes).isEqualToIgnoringCase("REJECTED");
|
||||
|
||||
@ -124,7 +122,7 @@ public class CordaClientTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGemeProposalAccept() throws JsonMappingException, JsonProcessingException {
|
||||
void testGameProposalAccept() throws JsonMappingException, JsonProcessingException {
|
||||
final String gpIssuer = "alice";
|
||||
final String gpAcquier = "bob";
|
||||
final Piece.Color gpAcquierColor = Piece.Color.WHITE;
|
||||
@ -147,8 +145,7 @@ public class CordaClientTest {
|
||||
|
||||
final UUID newGameBoardId = cordaClient.gameProposalAccept(
|
||||
holdingIdentityResolver.getByUsername(gpAcquier),
|
||||
gpUuid
|
||||
);
|
||||
gpUuid);
|
||||
|
||||
System.out.println("New GameBoard UUID "+newGameBoardId);
|
||||
|
||||
@ -179,13 +176,40 @@ public class CordaClientTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGemeProposalList() throws JsonMappingException, JsonProcessingException {
|
||||
void testGameBoardList() throws JsonMappingException, JsonProcessingException {
|
||||
List <GameBoard> gbList = cordaClient.gameBoardList(
|
||||
holdingIdentityResolver.getByUsername("bob"));
|
||||
|
||||
System.out.println("GB list: " +gbList);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGameBoardSurrender() throws JsonMappingException, JsonProcessingException {
|
||||
final var hiAlice = holdingIdentityResolver.getByUsername("alice");
|
||||
final var hiBob = holdingIdentityResolver.getByUsername("bob");
|
||||
final var bobColor = Piece.Color.WHITE;
|
||||
|
||||
final UUID gpUuid = cordaClient.gameProposalCreate(
|
||||
hiAlice, hiBob,
|
||||
bobColor, "GameBoard SURRENDER test"
|
||||
);
|
||||
|
||||
System.out.println("New GameProposal UUID "+ gpUuid);
|
||||
|
||||
final UUID newGameBoardId = cordaClient.gameProposalAccept(
|
||||
hiBob, gpUuid
|
||||
);
|
||||
|
||||
System.out.println("New GameBoard UUID "+ newGameBoardId);
|
||||
|
||||
GameBoard gbSurrender = cordaClient.gameBoardCommand(
|
||||
hiBob, newGameBoardId,
|
||||
new GameBoardCommand(GameBoardCommand.Type.SURRENDER)
|
||||
);
|
||||
|
||||
System.out.println("SURRENDER GB: "+gbSurrender);
|
||||
}
|
||||
|
||||
private <T extends CordaState> T findByUuid(List<T> statesList, UUID uuid) {
|
||||
for (T state : statesList) {
|
||||
if (state.id().compareTo(uuid) == 0)
|
||||
|
10
corda/.ci/Jenkinsfile
vendored
10
corda/.ci/Jenkinsfile
vendored
@ -1,10 +0,0 @@
|
||||
@Library('corda-shared-build-pipeline-steps@5.1') _
|
||||
|
||||
cordaPipeline(
|
||||
nexusAppId: 'com.corda.CSDE-Java.5.0',
|
||||
publishRepoPrefix: '',
|
||||
slimBuild: true,
|
||||
runUnitTests: false,
|
||||
dedicatedJobForSnykDelta: false,
|
||||
slackChannel: '#corda-corda5-dev-ex-build-notifications'
|
||||
)
|
@ -1,6 +0,0 @@
|
||||
@Library('corda-shared-build-pipeline-steps@5.1') _
|
||||
|
||||
cordaSnykScanPipeline (
|
||||
snykTokenId: 'r3-snyk-corda5',
|
||||
snykAdditionalCommands: "--all-sub-projects -d"
|
||||
)
|
14
corda/.github/workflows/check-pr-title.yaml
vendored
14
corda/.github/workflows/check-pr-title.yaml
vendored
@ -1,14 +0,0 @@
|
||||
name: 'Check PR Title'
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened]
|
||||
|
||||
jobs:
|
||||
check-pr-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: morrisoncole/pr-lint-action@v1.6.1
|
||||
with:
|
||||
title-regex: '^((CORDA|EG|ENT|INFRA|CORE|ES)-\d+)(.*)'
|
||||
on-failed-regex-comment: "PR title failed to match regex -> `%regex%`"
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
@ -1,15 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="DebugCorDapp" type="Remote">
|
||||
<option name="USE_SOCKET_TRANSPORT" value="true" />
|
||||
<option name="SERVER_MODE" value="false" />
|
||||
<option name="SHMEM_ADDRESS" />
|
||||
<option name="HOST" value="localhost" />
|
||||
<option name="PORT" value="5005" />
|
||||
<option name="AUTO_RESTART" value="false" />
|
||||
<RunnerSettings RunnerId="Debug">
|
||||
<option name="DEBUG_PORT" value="5005" />
|
||||
<option name="LOCAL" value="false" />
|
||||
</RunnerSettings>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
@ -18,14 +18,14 @@ allprojects {
|
||||
// Configure the CSDE
|
||||
csde {
|
||||
cordaClusterURL = "https://localhost:8888"
|
||||
networkConfigFile = "$rootDir/config/static-network-config.json"
|
||||
networkConfigFile = "config/static-network-config.json"
|
||||
r3RootCertFile = "config/r3-ca-key.pem"
|
||||
corDappCpiName = "MyCorDapp"
|
||||
notaryCpiName = "NotaryServer"
|
||||
cordaRpcUser = "admin"
|
||||
cordaRpcPasswd ="admin"
|
||||
workflowsModuleName = workflowsModule
|
||||
csdeWorkspaceDir = "$rootDir/workspace"
|
||||
csdeWorkspaceDir = "workspace"
|
||||
notaryVersion = cordaNotaryPluginsVersion
|
||||
combinedWorkerVersion = combinedWorkerJarVersion
|
||||
postgresJdbcVersion = "42.4.3"
|
||||
|
@ -1,72 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.contracts;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.Contract;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
public class ChatContract implements Contract {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(ChatContract.class);
|
||||
|
||||
// Use constants to hold the error messages
|
||||
// This allows the tests to use them, meaning if they are updated you won't need to fix tests just because the wording was updated
|
||||
static final String REQUIRE_SINGLE_COMMAND = "Require a single command.";
|
||||
static final String UNKNOWN_COMMAND = "Unsupported command";
|
||||
static final String OUTPUT_STATE_SHOULD_ONLY_HAVE_TWO_PARTICIPANTS = "The output state should have two and only two participants.";
|
||||
static final String TRANSACTION_SHOULD_BE_SIGNED_BY_ALL_PARTICIPANTS = "The transaction should have been signed by both participants.";
|
||||
|
||||
static final String CREATE_COMMAND_SHOULD_HAVE_NO_INPUT_STATES = "When command is Create there should be no input states.";
|
||||
static final String CREATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE = "When command is Create there should be one and only one output state.";
|
||||
|
||||
static final String UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_INPUT_STATE = "When command is Update there should be one and only one input state.";
|
||||
static final String UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE = "When command is Update there should be one and only one output state.";
|
||||
static final String UPDATE_COMMAND_ID_SHOULD_NOT_CHANGE = "When command is Update id must not change.";
|
||||
static final String UPDATE_COMMAND_CHATNAME_SHOULD_NOT_CHANGE = "When command is Update chatName must not change.";
|
||||
static final String UPDATE_COMMAND_PARTICIPANTS_SHOULD_NOT_CHANGE = "When command is Update participants must not change.";
|
||||
|
||||
public static class Create implements Command { }
|
||||
public static class Update implements Command { }
|
||||
|
||||
@Override
|
||||
public void verify(UtxoLedgerTransaction transaction) {
|
||||
|
||||
requireThat( transaction.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND);
|
||||
Command command = transaction.getCommands().get(0);
|
||||
|
||||
ChatState output = transaction.getOutputStates(ChatState.class).get(0);
|
||||
|
||||
requireThat(output.getParticipants().size() == 2, OUTPUT_STATE_SHOULD_ONLY_HAVE_TWO_PARTICIPANTS);
|
||||
requireThat(transaction.getSignatories().containsAll(output.getParticipants()), TRANSACTION_SHOULD_BE_SIGNED_BY_ALL_PARTICIPANTS);
|
||||
|
||||
if(command.getClass() == Create.class) {
|
||||
requireThat(transaction.getInputContractStates().isEmpty(), CREATE_COMMAND_SHOULD_HAVE_NO_INPUT_STATES);
|
||||
requireThat(transaction.getOutputContractStates().size() == 1, CREATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE);
|
||||
}
|
||||
else if(command.getClass() == Update.class) {
|
||||
requireThat(transaction.getInputContractStates().size() == 1, UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_INPUT_STATE);
|
||||
requireThat(transaction.getOutputContractStates().size() == 1, UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE);
|
||||
|
||||
ChatState input = transaction.getInputStates(ChatState.class).get(0);
|
||||
requireThat(input.getId().equals(output.getId()), UPDATE_COMMAND_ID_SHOULD_NOT_CHANGE);
|
||||
requireThat(input.getChatName().equals(output.getChatName()), UPDATE_COMMAND_CHATNAME_SHOULD_NOT_CHANGE);
|
||||
requireThat(
|
||||
input.getParticipants().containsAll(output.getParticipants()) &&
|
||||
output.getParticipants().containsAll(input.getParticipants()),
|
||||
UPDATE_COMMAND_PARTICIPANTS_SHOULD_NOT_CHANGE);
|
||||
}
|
||||
else {
|
||||
throw new CordaRuntimeException(UNKNOWN_COMMAND);
|
||||
}
|
||||
}
|
||||
|
||||
private void requireThat(boolean asserted, String errorMessage) {
|
||||
if(!asserted) {
|
||||
throw new CordaRuntimeException("Failed requirement: " + errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.states;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
|
||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.BelongsToContract;
|
||||
import net.corda.v5.ledger.utxo.ContractState;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.*;
|
||||
|
||||
@BelongsToContract(ChatContract.class)
|
||||
public class ChatState implements ContractState {
|
||||
|
||||
private UUID id;
|
||||
private String chatName;
|
||||
private MemberX500Name messageFrom;
|
||||
private String message;
|
||||
public List<PublicKey> participants;
|
||||
|
||||
// Allows serialisation and to use a specified UUID.
|
||||
@ConstructorForDeserialization
|
||||
public ChatState(UUID id,
|
||||
String chatName,
|
||||
MemberX500Name messageFrom,
|
||||
String message,
|
||||
List<PublicKey> participants) {
|
||||
this.id = id;
|
||||
this.chatName = chatName;
|
||||
this.messageFrom = messageFrom;
|
||||
this.message = message;
|
||||
this.participants = participants;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
public String getChatName() {
|
||||
return chatName;
|
||||
}
|
||||
public MemberX500Name getMessageFrom() {
|
||||
return messageFrom;
|
||||
}
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public List<PublicKey> getParticipants() {
|
||||
return participants;
|
||||
}
|
||||
|
||||
public ChatState updateMessage(MemberX500Name name, String message) {
|
||||
return new ChatState(id, chatName, name, message, participants);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
|
||||
public class GameBoardCommand implements Command {
|
||||
public static enum Type {
|
||||
MOVE,
|
||||
SURRENDER,
|
||||
REQUEST_DRAW,
|
||||
REQUEST_VICTORY,
|
||||
FINISH; // aka accept DRAW or VICTORY request
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final List<Integer> move; // [0] from, [1] to
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public GameBoardCommand() {
|
||||
this.type = null;
|
||||
this.move = null;
|
||||
}
|
||||
|
||||
public GameBoardCommand(Type type) {
|
||||
if (type == Type.MOVE)
|
||||
throw new CordaRuntimeException (BAD_ACTIONMOVE_CONSTRUCTOR);
|
||||
|
||||
this.type = type;
|
||||
this.move = null;
|
||||
}
|
||||
|
||||
public GameBoardCommand(List<Integer> move) {
|
||||
this.type = Type.MOVE;
|
||||
this.move = move;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public List<Integer> getMove() {
|
||||
return this.move;
|
||||
}
|
||||
|
||||
static final String BAD_ACTIONMOVE_CONSTRUCTOR = "Bad constructor for Action.MOVE";
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import java.util.List;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleReferenceState;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -11,45 +14,47 @@ import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
import static djmil.cordacheckers.contracts.GameProposalContract.Action;
|
||||
|
||||
public class GameBoardContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(GameBoardContract.class);
|
||||
|
||||
public static class Move implements Command { }
|
||||
public static class Surrender implements Command { }
|
||||
public static class ClaimVictory implements Command { }
|
||||
public static class ProposeDraw implements Command { }
|
||||
|
||||
@Override
|
||||
public void verify(UtxoLedgerTransaction trx) {
|
||||
log.info("GameBoardContract.verify()");
|
||||
log.info("GameBoardContract.verify() called");
|
||||
|
||||
requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND);
|
||||
Command command = trx.getCommands().get(0);
|
||||
final Command command = getSingleCommand(trx, Command.class);
|
||||
|
||||
if (command instanceof GameProposalCommand) {
|
||||
log.info("GameBoardContract.verify() as GameProposalCommand "+(GameProposalCommand)command);
|
||||
} else
|
||||
if (command instanceof GameBoardCommand) {
|
||||
log.info("GameBoardContract.verify() as GameBoardCommand "+((GameBoardCommand)command).getType());
|
||||
switch (((GameBoardCommand)command).getType()) {
|
||||
case MOVE:
|
||||
break;
|
||||
case SURRENDER:
|
||||
break;
|
||||
case REQUEST_DRAW:
|
||||
break;
|
||||
case REQUEST_VICTORY:
|
||||
break;
|
||||
case FINISH:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
throw new CordaRuntimeException(UNKNOWN_COMMAND);
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
public static GameProposalState getReferanceGameProposalState(UtxoLedgerTransaction trx) {
|
||||
final List<Command> commandList = trx.getCommands();
|
||||
requireThat(commandList.size() == 1, REQUIRE_SINGLE_COMMAND);
|
||||
|
||||
final Command command = commandList.get(0);
|
||||
|
||||
if (command instanceof Action && (Action)command == Action.ACCEPT) {
|
||||
return trx.getInputStates(GameProposalState.class)
|
||||
.stream()
|
||||
.reduce( (a, b) -> {throw new IllegalStateException(SINGLE_STATE_EXPECTED);} )
|
||||
.get();
|
||||
final Command command = getSingleCommand(trx, Command.class);
|
||||
|
||||
if (command instanceof GameProposalCommand && (GameProposalCommand)command == GameProposalCommand.ACCEPT) {
|
||||
return getSingleInputState(trx, GameProposalState.class);
|
||||
} else
|
||||
if (command instanceof GameBoardContract.Move)
|
||||
{
|
||||
List<GameProposalState> refStates = trx.getReferenceStates(GameProposalState.class);
|
||||
if (refStates.size() == 1) {
|
||||
return (GameProposalState) refStates.get(0);
|
||||
}
|
||||
if (command instanceof GameBoardCommand) {
|
||||
return getSingleReferenceState(trx, GameProposalState.class);
|
||||
}
|
||||
|
||||
throw new IllegalStateException(NO_REFERANCE_GAMEPROPOSAL_STATE_FOR_TRXID +trx.getId());
|
||||
|
@ -0,0 +1,49 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
|
||||
public enum GameProposalCommand implements Command {
|
||||
CREATE,
|
||||
ACCEPT,
|
||||
REJECT,
|
||||
CANCEL;
|
||||
|
||||
public MemberX500Name getInitiator(GameProposalState gameProposalState) {
|
||||
switch (this) {
|
||||
case CREATE:
|
||||
case CANCEL:
|
||||
return gameProposalState.getIssuer();
|
||||
case ACCEPT:
|
||||
case REJECT:
|
||||
return gameProposalState.getAcquier();
|
||||
default:
|
||||
throw new RuntimeException(UNSUPPORTED_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_VALUE_OF + this.name());
|
||||
}
|
||||
}
|
||||
|
||||
public MemberX500Name getInitiator(StateAndRef<GameProposalState> utxoGameProposal) {
|
||||
return getInitiator(utxoGameProposal.getState().getContractState());
|
||||
}
|
||||
|
||||
public MemberX500Name getRespondent(StateAndRef<GameProposalState> utxoGameProposal) {
|
||||
return getRespondent(utxoGameProposal.getState().getContractState());
|
||||
}
|
||||
|
||||
public static final String UNSUPPORTED_VALUE_OF = "Unsupported GameProposalCommand value: ";
|
||||
}
|
@ -1,103 +1,59 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleCommand;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleInputState;
|
||||
import static djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil.getSingleOutputState;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(GameProposalContract.class);
|
||||
|
||||
public static enum Action implements Command {
|
||||
CREATE,
|
||||
ACCEPT,
|
||||
REJECT,
|
||||
CANCEL;
|
||||
|
||||
public MemberX500Name getInitiator(GameProposalState gameProposalState) {
|
||||
switch (this) {
|
||||
case CREATE:
|
||||
case CANCEL:
|
||||
return gameProposalState.getIssuer();
|
||||
case ACCEPT:
|
||||
case REJECT:
|
||||
return gameProposalState.getAcquier();
|
||||
default:
|
||||
throw new RuntimeException(UNSUPPORTED_ACTION_VALUE_OF + this.name());
|
||||
}
|
||||
}
|
||||
|
||||
public MemberX500Name getRespondent(GameProposalState gameProposalState) {
|
||||
switch (this) {
|
||||
case CREATE:
|
||||
case CANCEL:
|
||||
return gameProposalState.getAcquier();
|
||||
case ACCEPT:
|
||||
case REJECT:
|
||||
return gameProposalState.getIssuer();
|
||||
default:
|
||||
throw new RuntimeException(UNSUPPORTED_ACTION_VALUE_OF + this.name());
|
||||
}
|
||||
}
|
||||
|
||||
public MemberX500Name getInitiator(StateAndRef<GameProposalState> utxoGameProposal) {
|
||||
return getInitiator(utxoGameProposal.getState().getContractState());
|
||||
}
|
||||
|
||||
public MemberX500Name getRespondent(StateAndRef<GameProposalState> utxoGameProposal) {
|
||||
return getRespondent(utxoGameProposal.getState().getContractState());
|
||||
}
|
||||
|
||||
public static final String UNSUPPORTED_ACTION_VALUE_OF = "Unsupported Action value: ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(UtxoLedgerTransaction trx) {
|
||||
log.info("GameProposalContract.verify() called");
|
||||
|
||||
requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND);
|
||||
final GameProposalCommand command = getSingleCommand(trx, GameProposalCommand.class);
|
||||
|
||||
switch ((trx.getCommands(Action.class).get(0))) {
|
||||
switch (command) {
|
||||
case CREATE: {
|
||||
requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE);
|
||||
requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE);
|
||||
|
||||
GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0);
|
||||
requireThat(outputState != null, CREATE_OUTPUT_STATE);
|
||||
GameProposalState outputState = getSingleOutputState(trx, GameProposalState.class);
|
||||
|
||||
requireThat(outputState.getAcquierColor() != null, NON_NULL_RECIPIENT_COLOR);
|
||||
break; }
|
||||
|
||||
case ACCEPT:
|
||||
case ACCEPT: {
|
||||
requireThat(trx.getInputContractStates().size() == 1, ACCEPT_INPUT_STATE);
|
||||
requireThat(trx.getOutputContractStates().size() == 1, ACCEPT_OUTPUT_STATE);
|
||||
|
||||
GameProposalState inGameProposal = trx.getInputStates(GameProposalState.class).get(0);
|
||||
requireThat(inGameProposal != null, ACCEPT_INPUT_STATE);
|
||||
|
||||
GameBoardState outGameBoard = trx.getOutputStates(GameBoardState.class).get(0);
|
||||
requireThat(outGameBoard != null, ACCEPT_INPUT_STATE);
|
||||
GameProposalState inGameProposal = getSingleInputState(trx, GameProposalState.class);
|
||||
GameBoardState outGameBoard = getSingleOutputState(trx, GameBoardState.class);
|
||||
|
||||
requireThat(outGameBoard.getParticipants().containsAll(inGameProposal.getParticipants()), ACCEPT_PARTICIPANTS);
|
||||
break;
|
||||
break; }
|
||||
|
||||
case REJECT:
|
||||
case REJECT: {
|
||||
requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE);
|
||||
requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE);
|
||||
requireThat(trx.getInputStates(GameProposalState.class).get(0) != null, REJECT_INPUT_STATE);
|
||||
break;
|
||||
|
||||
getSingleInputState(trx, GameProposalState.class);
|
||||
break; }
|
||||
|
||||
case CANCEL:
|
||||
requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE);
|
||||
requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE);
|
||||
requireThat(trx.getInputStates(GameProposalState.class).get(0) != null, CANCEL_INPUT_STATE);
|
||||
|
||||
getSingleInputState(trx, GameProposalState.class);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -106,7 +62,7 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract {
|
||||
}
|
||||
|
||||
private void requireThat(boolean asserted, String errorMessage) {
|
||||
if(!asserted) {
|
||||
if (!asserted) {
|
||||
throw new CordaRuntimeException("Failed requirement: " + errorMessage);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
package djmil.cordacheckers.contracts;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.ContractState;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
public class UtxoLedgerTransactionUtil {
|
||||
public static <T extends Command> T getSingleCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return single(utxoTrx.getCommands(clazz), clazz);
|
||||
}
|
||||
|
||||
public static <T extends ContractState> T getSingleReferenceState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return single(utxoTrx.getReferenceStates(clazz), clazz);
|
||||
}
|
||||
|
||||
public static <T extends ContractState> T getSingleInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return single(utxoTrx.getInputStates(clazz), clazz);
|
||||
}
|
||||
|
||||
public static <T extends ContractState> T getSingleOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return single(utxoTrx.getOutputStates(clazz), clazz);
|
||||
}
|
||||
|
||||
public static <T extends Command> Optional<T> getOptionalCommand(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return optional(utxoTrx.getCommands(clazz), clazz);
|
||||
}
|
||||
|
||||
public static <T extends ContractState> Optional<T> getOptionalReferenceState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return optional(utxoTrx.getReferenceStates(clazz), clazz);
|
||||
}
|
||||
|
||||
public static <T extends ContractState> Optional<T> getOptionalInputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return optional(utxoTrx.getInputStates(clazz), clazz);
|
||||
}
|
||||
|
||||
public static <T extends ContractState> Optional<T> getOptionalOutputState(UtxoLedgerTransaction utxoTrx, Class<T> clazz) {
|
||||
return optional(utxoTrx.getOutputStates(clazz), clazz);
|
||||
}
|
||||
|
||||
private static <T> Optional<T> optional(List<T> list, Class<T> clazz) {
|
||||
return list
|
||||
.stream()
|
||||
.reduce((a, b) -> {throw new IllegalStateException(MULTIPLE_INSTANCES_OF +clazz.getName());});
|
||||
}
|
||||
|
||||
private static <T> T single(List<T> list, Class<T> clazz) {
|
||||
return optional(list, clazz)
|
||||
.orElseThrow( () -> new IllegalStateException(NO_INSTANCES_OF +clazz.getName()) );
|
||||
}
|
||||
|
||||
private static String MULTIPLE_INSTANCES_OF = "Multiple instances of ";
|
||||
private static String NO_INSTANCES_OF = "No instances of ";
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
|
||||
import net.corda.v5.base.annotations.CordaSerializable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
|
||||
// Where a class contains a message, mark it with @CordaSerializable to enable Corda to
|
||||
// send it from one virtual node to another.
|
||||
@CordaSerializable
|
||||
public class Message {
|
||||
|
||||
private MemberX500Name sender;
|
||||
private String message;
|
||||
|
||||
public Message(MemberX500Name sender, String message) {
|
||||
this.sender = sender;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public MemberX500Name getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
|
||||
import net.corda.v5.application.flows.*;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
import net.corda.v5.application.messaging.FlowMessaging;
|
||||
import net.corda.v5.application.messaging.FlowSession;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
// MyFirstFlow is an initiating flow, its corresponding responder flow is called MyFirstFlowResponder (defined below)
|
||||
// to link the two sides of the flow together they need to have the same protocol.
|
||||
@InitiatingFlow(protocol = "my-first-flow")
|
||||
// MyFirstFlow should inherit from ClientStartableFlow, which tells Corda it can be started via an REST call
|
||||
public class MyFirstFlow implements ClientStartableFlow {
|
||||
|
||||
// Log messages from the flows for debugging.
|
||||
private final static Logger log = LoggerFactory.getLogger(MyFirstFlow.class);
|
||||
|
||||
// Corda has a set of injectable services which are injected into the flow at runtime.
|
||||
// Flows declare them with @CordaInjectable, then the flows have access to their services.
|
||||
|
||||
// JsonMarshallingService provides a service for manipulating JSON.
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
// FlowMessaging provides a service that establishes flow sessions between virtual nodes
|
||||
// that send and receive payloads between them.
|
||||
@CordaInject
|
||||
public FlowMessaging flowMessaging;
|
||||
|
||||
// MemberLookup provides a service for looking up information about members of the virtual network which
|
||||
// this CorDapp operates in.
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
public MyFirstFlow() {}
|
||||
|
||||
// When a flow is invoked its call() method is called.
|
||||
// Call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
|
||||
// for a response from the other flows and services.
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
|
||||
// Follow what happens in the console or logs.
|
||||
log.info("MFF: MyFirstFlow.call() called");
|
||||
|
||||
// Show the requestBody in the logs - this can be used to help establish the format for starting a flow on Corda.
|
||||
log.info("MFF: requestBody: " + requestBody.getRequestBody());
|
||||
|
||||
// Deserialize the Json requestBody into the MyfirstFlowStartArgs class using the JsonSerialisation service.
|
||||
MyFirstFlowStartArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, MyFirstFlowStartArgs.class);
|
||||
|
||||
// Obtain the MemberX500Name of the counterparty.
|
||||
MemberX500Name otherMember = flowArgs.getOtherMember();
|
||||
|
||||
// Get our identity from the MemberLookup service.
|
||||
MemberX500Name ourIdentity = memberLookup.myInfo().getName();
|
||||
|
||||
// Create the message payload using the MessageClass we defined.
|
||||
Message message = new Message(otherMember, "Hello from " + ourIdentity + ".");
|
||||
|
||||
// Log the message to be sent.
|
||||
log.info("MFF: message.message: " + message.getMessage());
|
||||
|
||||
// Start a flow session with the otherMember using the FlowMessaging service.
|
||||
// The otherMember's virtual node will run the corresponding MyFirstFlowResponder responder flow.
|
||||
FlowSession session = flowMessaging.initiateFlow(otherMember);
|
||||
|
||||
// Send the Payload using the send method on the session to the MyFirstFlowResponder responder flow.
|
||||
session.send(message);
|
||||
|
||||
// Receive a response from the responder flow.
|
||||
Message response = session.receive(Message.class);
|
||||
|
||||
// The return value of a ClientStartableFlow must always be a String. This will be passed
|
||||
// back as the REST response when the status of the flow is queried on Corda.
|
||||
return response.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
RequestBody for triggering the flow via REST:
|
||||
{
|
||||
"clientRequestId": "r1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.flowexample.workflows.MyFirstFlow",
|
||||
"requestBody": {
|
||||
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,62 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
|
||||
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.types.MemberX500Name;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
// MyFirstFlowResponder is a responder flow, its corresponding initiating flow is called MyFirstFlow (defined in MyFirstFlow.java)
|
||||
// to link the two sides of the flow together they need to have the same protocol.
|
||||
@InitiatedBy(protocol = "my-first-flow")
|
||||
// Responder flows must inherit from ResponderFlow
|
||||
public class MyFirstFlowResponder implements ResponderFlow {
|
||||
|
||||
// Log messages from the flows for debugging.
|
||||
private final static Logger log = LoggerFactory.getLogger(MyFirstFlowResponder.class);
|
||||
|
||||
// MemberLookup looks for information about members of the virtual network which
|
||||
// this CorDapp operates in.
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
public MyFirstFlowResponder() {}
|
||||
|
||||
// Responder flows are invoked when an initiating flow makes a call via a session set up with the virtual
|
||||
// node hosting the responder flow. When a responder flow is invoked its call() method is called.
|
||||
// call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
|
||||
// for a response from the other flows and services.
|
||||
// The call() method has the flow session passed in as a parameter by Corda so the session is available to
|
||||
// responder flow code, you don't need to inject the FlowMessaging service.
|
||||
@Suspendable
|
||||
@Override
|
||||
public void call(FlowSession session) {
|
||||
|
||||
// Follow what happens in the console or logs.
|
||||
log.info("MFF: MyFirstResponderFlow.call() called");
|
||||
|
||||
// Receive the payload and deserialize it into a message class.
|
||||
Message receivedMessage = session.receive(Message.class);
|
||||
|
||||
// Log the message as a proxy for performing some useful operation on it.
|
||||
log.info("MFF: Message received from " + receivedMessage.getSender() + ":" + receivedMessage.getMessage());
|
||||
|
||||
// Get our identity from the MemberLookup service.
|
||||
MemberX500Name ourIdentity = memberLookup.myInfo().getName();
|
||||
|
||||
// Create a message to greet the sender.
|
||||
Message response = new Message(ourIdentity,
|
||||
"Hello " + session.getCounterparty().getCommonName() + ", best wishes from " + ourIdentity.getCommonName());
|
||||
|
||||
// Log the response to be sent.
|
||||
log.info("MFF: response.message: " + response.getMessage());
|
||||
|
||||
// Send the response via the send method on the flow session
|
||||
session.send(response);
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
|
||||
// A class to hold the arguments required to start the flow
|
||||
public class MyFirstFlowStartArgs {
|
||||
private MemberX500Name otherMember;
|
||||
|
||||
// The JSON Marshalling Service, which handles serialisation, needs this constructor.
|
||||
public MyFirstFlowStartArgs() {}
|
||||
|
||||
public MyFirstFlowStartArgs(MemberX500Name otherMember) {
|
||||
this.otherMember = otherMember;
|
||||
}
|
||||
|
||||
public MemberX500Name getOtherMember() {
|
||||
return otherMember;
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
// Class to hold the ListChatFlow results.
|
||||
// The ChatState(s) cannot be returned directly as the JsonMarshallingService can only serialize simple classes
|
||||
// that the underlying Jackson serializer recognises, hence creating a DTO style object which consists only of Strings
|
||||
// and a UUID. It is possible to create custom serializers for the JsonMarshallingService, but this beyond the scope
|
||||
// of this simple example.
|
||||
public class ChatStateResults {
|
||||
|
||||
private UUID id;
|
||||
private String chatName;
|
||||
private String messageFromName;
|
||||
private String message;
|
||||
|
||||
public ChatStateResults() {}
|
||||
|
||||
public ChatStateResults(UUID id, String chatName, String messageFromName, String message) {
|
||||
this.id = id;
|
||||
this.chatName = chatName;
|
||||
this.messageFromName = messageFromName;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getChatName() {
|
||||
return chatName;
|
||||
}
|
||||
|
||||
public String getMessageFromName() {
|
||||
return messageFromName;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
import net.corda.v5.application.flows.*;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
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;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
|
||||
import net.corda.v5.membership.MemberInfo;
|
||||
import net.corda.v5.membership.NotaryInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Objects.*;
|
||||
|
||||
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
||||
public class CreateNewChatFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CreateNewChatFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
|
||||
@CordaInject
|
||||
public UtxoLedgerService ledgerService;
|
||||
|
||||
@CordaInject
|
||||
public NotaryLookup notaryLookup;
|
||||
|
||||
// FlowEngine service is required to run SubFlows.
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call( ClientRequestBody requestBody) {
|
||||
|
||||
log.info("CreateNewChatFlow.call() called");
|
||||
|
||||
try {
|
||||
// Obtain the deserialized input arguments to the flow from the requestBody.
|
||||
CreateNewChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, CreateNewChatFlowArgs.class);
|
||||
|
||||
// Get MemberInfos for the Vnode running the flow and the otherMember.
|
||||
MemberInfo myInfo = memberLookup.myInfo();
|
||||
MemberInfo otherMember = requireNonNull(
|
||||
memberLookup.lookup(MemberX500Name.parse(flowArgs.getOtherMember())),
|
||||
"MemberLookup can't find otherMember specified in flow arguments."
|
||||
);
|
||||
|
||||
// Create the ChatState from the input arguments and member information.
|
||||
ChatState chatState = new ChatState(
|
||||
UUID.randomUUID(),
|
||||
flowArgs.getChatName(),
|
||||
myInfo.getName(),
|
||||
flowArgs.getMessage(),
|
||||
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
|
||||
);
|
||||
|
||||
// Obtain the Notary name and public key.
|
||||
NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next();
|
||||
|
||||
// Use UTXOTransactionBuilder to build up the draft transaction.
|
||||
UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder()
|
||||
.setNotary(notary.getName())
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addOutputState(chatState)
|
||||
.addCommand(new ChatContract.Create())
|
||||
.addSignatories(chatState.getParticipants());
|
||||
|
||||
// Convert the transaction builder to a UTXOSignedTransaction. Verifies the content of the
|
||||
// UtxoTransactionBuilder and signs the transaction with any required signatories that belong to
|
||||
// the current node.
|
||||
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
|
||||
|
||||
// Call FinalizeChatSubFlow which will finalise the transaction.
|
||||
// If successful the flow will return a String of the created transaction id,
|
||||
// if not successful it will return an error message.
|
||||
return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName()));
|
||||
}
|
||||
// Catch any exceptions, log them and rethrow the exception.
|
||||
catch (Exception e) {
|
||||
log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage());
|
||||
throw new CordaRuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
RequestBody for triggering the flow via REST:
|
||||
{
|
||||
"clientRequestId": "create-1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.CreateNewChatFlow",
|
||||
"requestBody": {
|
||||
"chatName":"Chat with Bob",
|
||||
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
|
||||
"message": "Hello Bob"
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,30 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
// A class to hold the deserialized arguments required to start the flow.
|
||||
public class CreateNewChatFlowArgs{
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public CreateNewChatFlowArgs() {}
|
||||
|
||||
private String chatName;
|
||||
private String message;
|
||||
private String otherMember;
|
||||
|
||||
public CreateNewChatFlowArgs(String chatName, String message, String otherMember) {
|
||||
this.chatName = chatName;
|
||||
this.message = message;
|
||||
this.otherMember = otherMember;
|
||||
}
|
||||
|
||||
public String getChatName() {
|
||||
return chatName;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getOtherMember() {
|
||||
return otherMember;
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
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.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;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
||||
|
||||
//@InitiatingBy declares the protocol which will be used to link the initiator to the responder.
|
||||
@InitiatedBy(protocol = "finalize-chat-protocol")
|
||||
public class FinalizeChatResponderFlow implements ResponderFlow {
|
||||
private final static Logger log = LoggerFactory.getLogger(FinalizeChatResponderFlow.class);
|
||||
|
||||
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public void call(FlowSession session) {
|
||||
|
||||
log.info("FinalizeChatResponderFlow.call() called");
|
||||
|
||||
try {
|
||||
// Defines the lambda validator used in receiveFinality below.
|
||||
UtxoTransactionValidator txValidator = ledgerTransaction -> {
|
||||
ChatState state = (ChatState) ledgerTransaction.getOutputContractStates().get(0);
|
||||
// Uses checkForBannedWords() and checkMessageFromMatchesCounterparty() functions
|
||||
// to check whether to sign the transaction.
|
||||
if (checkForBannedWords(state.getMessage()) || !checkMessageFromMatchesCounterparty(state, 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 checkForBannedWords(String str) {
|
||||
List<String> bannedWords = Arrays.asList("banana", "apple", "pear");
|
||||
return bannedWords.stream().anyMatch(str::contains);
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
Boolean checkMessageFromMatchesCounterparty(ChatState state, MemberX500Name otherMember) {
|
||||
return state.getMessageFrom().equals(otherMember);
|
||||
}
|
||||
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import net.corda.v5.application.flows.*;
|
||||
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.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
||||
|
||||
// @InitiatingFlow declares the protocol which will be used to link the initiator to the responder.
|
||||
@InitiatingFlow(protocol = "finalize-chat-protocol")
|
||||
public class FinalizeChatSubFlow implements SubFlow<String> {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(FinalizeChatSubFlow.class);
|
||||
private final UtxoSignedTransaction signedTransaction;
|
||||
private final MemberX500Name otherMember;
|
||||
|
||||
public FinalizeChatSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name otherMember) {
|
||||
this.signedTransaction = signedTransaction;
|
||||
this.otherMember = otherMember;
|
||||
}
|
||||
|
||||
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
|
||||
@CordaInject
|
||||
public UtxoLedgerService ledgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowMessaging flowMessaging;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public String call() {
|
||||
|
||||
log.info("FinalizeChatFlow.call() called");
|
||||
|
||||
// Initiates a session with the other Member.
|
||||
FlowSession session = flowMessaging.initiateFlow(otherMember);
|
||||
|
||||
// Calls the Corda provided finalise() function which gather signatures from the counterparty,
|
||||
// notarises the transaction and persists the transaction to each party's vault.
|
||||
// On success returns the id of the transaction created. (This is different to the ChatState id)
|
||||
String result;
|
||||
try {
|
||||
List<FlowSession> sessionsList = Arrays.asList(session);
|
||||
|
||||
UtxoSignedTransaction finalizedSignedTransaction = ledgerService.finalize(
|
||||
signedTransaction,
|
||||
sessionsList
|
||||
).getTransaction();
|
||||
|
||||
result = finalizedSignedTransaction.getId().toString();
|
||||
log.info("Success! Response: " + result);
|
||||
|
||||
}
|
||||
// Soft fails the flow and returns the error message without throwing a flow exception.
|
||||
catch (Exception e) {
|
||||
log.warn("Finality failed", e);
|
||||
result = "Finality failed, " + e.getMessage();
|
||||
}
|
||||
// Returns the transaction id converted as a string
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
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.base.annotations.Suspendable;
|
||||
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.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import static java.util.Objects.*;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
||||
public class GetChatFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(GetChatFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
|
||||
@CordaInject
|
||||
public UtxoLedgerService ledgerService;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
|
||||
// Obtain the deserialized input arguments to the flow from the requestBody.
|
||||
GetChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, GetChatFlowArgs.class);
|
||||
|
||||
// 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.
|
||||
// Note, you will get this error if you input an id which has no corresponding ChatState (common error).
|
||||
List<StateAndRef<ChatState>> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
|
||||
List<StateAndRef<ChatState>> chatStateAndRefsWithId = chatStateAndRefs.stream()
|
||||
.filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList());
|
||||
if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found");
|
||||
StateAndRef<ChatState> chatStateAndRef = chatStateAndRefsWithId.get(0);
|
||||
|
||||
// Calls resolveMessagesFromBackchain() which retrieves the chat history from the backchain.
|
||||
return jsonMarshallingService.format(resolveMessagesFromBackchain(chatStateAndRef, flowArgs.getNumberOfRecords() ));
|
||||
}
|
||||
|
||||
// resoveMessageFromBackchain() starts at the stateAndRef provided, which represents the unconsumed head of the
|
||||
// backchain for this particular chat, then walks the chain backwards for the number of transaction specified in
|
||||
// the numberOfRecords argument. For each transaction it adds the MessageAndSender representing the
|
||||
// message and who sent it to a list which is then returned.
|
||||
@Suspendable
|
||||
private List<MessageAndSender> resolveMessagesFromBackchain(StateAndRef<?> stateAndRef, int numberOfRecords) {
|
||||
|
||||
// Set up a Mutable List to collect the MessageAndSender(s)
|
||||
List<MessageAndSender> messages = new LinkedList<>();
|
||||
|
||||
// Set up initial conditions for walking the backchain.
|
||||
StateAndRef<?> currentStateAndRef = stateAndRef;
|
||||
int recordsToFetch = numberOfRecords;
|
||||
boolean moreBackchain = true;
|
||||
|
||||
// Continue to loop until the start of the backchain or enough records have been retrieved.
|
||||
while (moreBackchain) {
|
||||
|
||||
// Obtain the transaction id from the current StateAndRef and fetch the transaction from the vault.
|
||||
SecureHash transactionId = currentStateAndRef.getRef().getTransactionId();
|
||||
UtxoLedgerTransaction transaction = requireNonNull(
|
||||
ledgerService.findLedgerTransaction(transactionId),
|
||||
"Transaction " + transactionId + " not found."
|
||||
);
|
||||
|
||||
// Get the output state from the transaction and use it to create a MessageAndSender Object which
|
||||
// is appended to the mutable list.
|
||||
List<ChatState> chatStates = transaction.getOutputStates(ChatState.class);
|
||||
if (chatStates.size() != 1) throw new CordaRuntimeException(
|
||||
"Expecting one and only one ChatState output for transaction " + transactionId + ".");
|
||||
ChatState output = chatStates.get(0);
|
||||
|
||||
messages.add(new MessageAndSender(output.getMessageFrom().toString(), output.getMessage()));
|
||||
// Decrement the number of records to fetch.
|
||||
recordsToFetch--;
|
||||
|
||||
// Get the reference to the input states.
|
||||
List<StateAndRef<?>> inputStateAndRefs = transaction.getInputStateAndRefs();
|
||||
|
||||
// Check if there are no more input states (start of chain) or we have retrieved enough records.
|
||||
// Check the transaction is not malformed by having too many input states.
|
||||
// Set the currentStateAndRef to the input StateAndRef, then repeat the loop.
|
||||
if (inputStateAndRefs.isEmpty() || recordsToFetch == 0) {
|
||||
moreBackchain = false;
|
||||
} else if (inputStateAndRefs.size() > 1) {
|
||||
throw new CordaRuntimeException("More than one input state found for transaction " + transactionId + ".");
|
||||
} else {
|
||||
currentStateAndRef = inputStateAndRefs.get(0);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
RequestBody for triggering the flow via REST:
|
||||
{
|
||||
"clientRequestId": "get-1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.GetChatFlow",
|
||||
"requestBody": {
|
||||
"id":"** fill in id **",
|
||||
"numberOfRecords":"4"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -1,24 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
// A class to hold the deserialized arguments required to start the flow.
|
||||
public class GetChatFlowArgs {
|
||||
|
||||
private UUID id;
|
||||
private int numberOfRecords;
|
||||
public GetChatFlowArgs() {}
|
||||
|
||||
public GetChatFlowArgs(UUID id, int numberOfRecords ) {
|
||||
this.id = id;
|
||||
this.numberOfRecords = numberOfRecords;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int getNumberOfRecords() {
|
||||
return numberOfRecords;
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
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.base.annotations.Suspendable;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
||||
public class ListChatsFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(ListChatsFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
|
||||
log.info("ListChatsFlow.call() called");
|
||||
|
||||
// Queries the VNode's vault for unconsumed states and converts the result to a serializable DTO.
|
||||
List<StateAndRef<ChatState>> states = utxoLedgerService.findUnconsumedStatesByType(ChatState.class);
|
||||
List<ChatStateResults> results = states.stream().map( stateAndRef ->
|
||||
new ChatStateResults(
|
||||
stateAndRef.getState().getContractState().getId(),
|
||||
stateAndRef.getState().getContractState().getChatName(),
|
||||
stateAndRef.getState().getContractState().getMessageFrom().toString(),
|
||||
stateAndRef.getState().getContractState().getMessage()
|
||||
)
|
||||
).collect(Collectors.toList());
|
||||
|
||||
// Uses the JsonMarshallingService's format() function to serialize the DTO to Json.
|
||||
return jsonMarshallingService.format(results);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
RequestBody for triggering the flow via REST:
|
||||
{
|
||||
"clientRequestId": "list-1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.ListChatsFlow",
|
||||
"requestBody": {}
|
||||
}
|
||||
*/
|
@ -1,22 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
// A class to pair the messageFrom and message together.
|
||||
public class MessageAndSender {
|
||||
|
||||
private String messageFrom;
|
||||
private String message;
|
||||
public MessageAndSender() {}
|
||||
|
||||
public MessageAndSender(String messageFrom, String message) {
|
||||
this.messageFrom = messageFrom;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessageFrom() {
|
||||
return messageFrom;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.FlowEngine;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
|
||||
import net.corda.v5.membership.MemberInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Objects.*;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
|
||||
public class UpdateChatFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(UpdateChatFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
|
||||
@CordaInject
|
||||
public UtxoLedgerService ledgerService;
|
||||
|
||||
// FlowEngine service is required to run SubFlows.
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
|
||||
log.info("UpdateNewChatFlow.call() called");
|
||||
|
||||
try {
|
||||
// Obtain the deserialized input arguments to the flow from the requestBody.
|
||||
UpdateChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, UpdateChatFlowArgs.class);
|
||||
|
||||
// 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.
|
||||
// Note, you will get this error if you input an id which has no corresponding ChatState (common error).
|
||||
List<StateAndRef<ChatState>> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
|
||||
List<StateAndRef<ChatState>> chatStateAndRefsWithId = chatStateAndRefs.stream()
|
||||
.filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList());
|
||||
if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found");
|
||||
StateAndRef<ChatState> chatStateAndRef = chatStateAndRefsWithId.get(0);
|
||||
|
||||
// Get MemberInfos for the Vnode running the flow and the otherMember.
|
||||
MemberInfo myInfo = memberLookup.myInfo();
|
||||
ChatState state = chatStateAndRef.getState().getContractState();
|
||||
|
||||
List<MemberInfo> members = state.getParticipants().stream().map(
|
||||
it -> requireNonNull(memberLookup.lookup(it), "Member not found from public Key "+ it + ".")
|
||||
).collect(toList());
|
||||
members.remove(myInfo);
|
||||
if(members.size() != 1) throw new RuntimeException("Should be only one participant other than the initiator");
|
||||
MemberInfo otherMember = members.get(0);
|
||||
|
||||
// Create a new ChatState using the updateMessage helper function.
|
||||
ChatState newChatState = state.updateMessage(myInfo.getName(), flowArgs.getMessage());
|
||||
|
||||
// Use UTXOTransactionBuilder to build up the draft transaction.
|
||||
UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder()
|
||||
.setNotary(chatStateAndRef.getState().getNotaryName())
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addOutputState(newChatState)
|
||||
.addInputState(chatStateAndRef.getRef())
|
||||
.addCommand(new ChatContract.Update())
|
||||
.addSignatories(newChatState.getParticipants());
|
||||
|
||||
// Convert the transaction builder to a UTXOSignedTransaction. Verifies the content of the
|
||||
// UtxoTransactionBuilder and signs the transaction with any required signatories that belong to
|
||||
// the current node.
|
||||
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
|
||||
|
||||
// Call FinalizeChatSubFlow which will finalise the transaction.
|
||||
// If successful the flow will return a String of the created transaction id,
|
||||
// if not successful it will return an error message.
|
||||
return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName()));
|
||||
|
||||
|
||||
}
|
||||
// Catch any exceptions, log them and rethrow the exception.
|
||||
catch (Exception e) {
|
||||
log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
RequestBody for triggering the flow via REST:
|
||||
{
|
||||
"clientRequestId": "update-1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.UpdateChatFlow",
|
||||
"requestBody": {
|
||||
"id":" ** fill in id **",
|
||||
"message": "How are you today?"
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,24 +0,0 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
// A class to hold the deserialized arguments required to start the flow.
|
||||
public class UpdateChatFlowArgs {
|
||||
public UpdateChatFlowArgs() {}
|
||||
|
||||
private UUID id;
|
||||
private String message;
|
||||
|
||||
public UpdateChatFlowArgs(UUID id, String message) {
|
||||
this.id = id;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import net.corda.v5.crypto.SecureHash;
|
||||
|
||||
public class FlowResult {
|
||||
public final Object successStatus;
|
||||
public final String transactionId;
|
||||
public final SecureHash transactionId;
|
||||
public final String failureStatus;
|
||||
|
||||
public FlowResult(Object success) {
|
||||
@ -16,7 +16,7 @@ public class FlowResult {
|
||||
|
||||
public FlowResult(Object success, SecureHash transactionId) {
|
||||
this.successStatus = success;
|
||||
this.transactionId = transactionId.toString();
|
||||
this.transactionId = transactionId;
|
||||
this.failureStatus = null;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,135 @@
|
||||
package djmil.cordacheckers.gameboard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.FlowResult;
|
||||
import djmil.cordacheckers.contracts.GameBoardCommand;
|
||||
import djmil.cordacheckers.contracts.GameBoardContract;
|
||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.FlowEngine;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
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.StateAndRef;
|
||||
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.UtxoTransactionBuilder;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
public class CommandFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CommandFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
try {
|
||||
final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class);
|
||||
//final GameCommand command = args.getCommand();
|
||||
log.info("GameBoardCommandFlow: findUnconsumedGameBoardState");
|
||||
final StateAndRef<GameBoardState> gbStateAndRef = findUnconsumedGameBoardState(args.getGameBoardUuid());
|
||||
|
||||
// final UtxoSignedTransaction trx = prepareSignedTransaction(command, utxoGameProposal);
|
||||
|
||||
// final SecureHash trxId = this.flowEngine
|
||||
// .subFlow( new CommitSubFlow(trx, command.getRespondent(utxoGameProposal)) );
|
||||
|
||||
// if (command == GameProposalCommand.ACCEPT) {
|
||||
// GameBoardState newGb = (GameBoardState)trx.getOutputStateAndRefs().get(0).getState().getContractState();
|
||||
|
||||
// return new FlowResult(newGb.getId(), trxId)
|
||||
// .toJsonEncodedString(jsonMarshallingService);
|
||||
// }
|
||||
log.info("GameBoardCommandFlow: prepareGameBoardView");
|
||||
GameBoardView gbView = prepareGameBoardView(gbStateAndRef);
|
||||
|
||||
return new FlowResult(gbView )
|
||||
.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<GameBoardState> findUnconsumedGameBoardState (UUID gameBoardUuid) {
|
||||
/*
|
||||
* Get list of all unconsumed aka 'active' GameProposalStates, then filter by UUID.
|
||||
* Note, this is an inefficient way to perform this operation if there are a large
|
||||
* number of 'active' GameProposals exists in storage.
|
||||
*/
|
||||
return this.utxoLedgerService
|
||||
.findUnconsumedStatesByType(GameBoardState.class)
|
||||
.stream()
|
||||
.filter(sar -> sar.getState().getContractState().getId().equals(gameBoardUuid))
|
||||
.reduce((a, b) -> {throw new IllegalStateException("Multiple states: " +a +", " +b);})
|
||||
.get();
|
||||
}
|
||||
|
||||
// @Suspendable
|
||||
// private UtxoSignedTransaction prepareSignedTransaction(
|
||||
// GameProposalCommand command,
|
||||
// StateAndRef<GameProposalState> utxoGameProposal
|
||||
// ) {
|
||||
// UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder()
|
||||
// .setNotary(utxoGameProposal.getState().getNotaryName())
|
||||
// .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
// .addInputState(utxoGameProposal.getRef())
|
||||
// .addCommand(command)
|
||||
// .addSignatories(utxoGameProposal.getState().getContractState().getParticipants());
|
||||
|
||||
// if (command == GameProposalCommand.ACCEPT) {
|
||||
// trxBuilder = trxBuilder
|
||||
// .addOutputState(new GameBoardState(utxoGameProposal));
|
||||
// //A state cannot be both an input and a reference input in the same transaction
|
||||
// //.addReferenceState(utxoGameProposal.getRef());
|
||||
// }
|
||||
|
||||
// return trxBuilder.toSignedTransaction();
|
||||
// }
|
||||
|
||||
@Suspendable
|
||||
private GameBoardView prepareGameBoardView(StateAndRef<GameBoardState> stateAndRef) {
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
|
||||
final SecureHash trxId = stateAndRef.getRef().getTransactionId();
|
||||
|
||||
final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService
|
||||
.findLedgerTransaction(trxId);
|
||||
log.info("GameBoardCommandFlow: createw gbView");
|
||||
GameBoardView gbView = new GameBoardView(myName, utxoGameBoard);
|
||||
|
||||
gbView.previousCommand = new GameBoardCommand(GameBoardCommand.Type.SURRENDER);
|
||||
|
||||
return gbView;
|
||||
// return new GameBoardView(myName, utxoGameBoard);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package djmil.cordacheckers.gameboard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameBoardCommand;
|
||||
|
||||
public class CommandFlowArgs {
|
||||
private UUID gameBoardUuid;
|
||||
private GameBoardCommand command;
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public CommandFlowArgs() {
|
||||
this.gameBoardUuid = null;
|
||||
this.command = null;
|
||||
}
|
||||
|
||||
public GameBoardCommand getCommand() {
|
||||
return this.command;
|
||||
}
|
||||
|
||||
public UUID getGameBoardUuid() {
|
||||
return this.gameBoardUuid;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package djmil.cordacheckers.gameboard;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameBoardCommand;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.InitiatedBy;
|
||||
import net.corda.v5.application.flows.ResponderFlow;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
import net.corda.v5.application.messaging.FlowSession;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator;
|
||||
|
||||
@InitiatedBy(protocol = "game-board")
|
||||
public class CommitResponderFlow implements ResponderFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CommitResponderFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public void call(FlowSession session) {
|
||||
try {
|
||||
UtxoTransactionValidator txValidator = ledgerTransaction -> {
|
||||
final GameBoardCommand command = ledgerTransaction.getCommands(GameBoardCommand.class).get(0);
|
||||
|
||||
// TODO
|
||||
//final GameProposalState gameProposal = getGameProposal(ledgerTransaction);
|
||||
|
||||
//checkParticipants(session, gameProposal, command);
|
||||
|
||||
/*
|
||||
* Other checks / actions ?
|
||||
*/
|
||||
|
||||
log.info("Verified the transaction - " + ledgerTransaction.getId());
|
||||
};
|
||||
|
||||
UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService
|
||||
.receiveFinality(session, txValidator)
|
||||
.getTransaction();
|
||||
|
||||
log.info("Finished responder flow - " + finalizedSignedTransaction.getId());
|
||||
}
|
||||
catch(Exception e) {
|
||||
log.warn("Responder flow failed: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
void checkParticipants(
|
||||
FlowSession session,
|
||||
GameProposalState gameProposal,
|
||||
GameBoardCommand command
|
||||
) {
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
final MemberX500Name otherName = session.getCounterparty();
|
||||
|
||||
// TODO
|
||||
// if (command.getRespondent(gameProposal).compareTo(myName) != 0)
|
||||
// throw new CordaRuntimeException("Bad GameProposal acquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'");
|
||||
|
||||
// if (command.getInitiator(gameProposal).compareTo(otherName) != 0)
|
||||
// throw new CordaRuntimeException("Bad GameProposal issuer: expected '"+otherName+"', actual '"+gameProposal.getIssuer()+"'");
|
||||
}
|
||||
|
||||
// @Suspendable
|
||||
// GameProposalState getGameProposal(UtxoLedgerTransaction trx) {
|
||||
// final Action action = trx.getCommands(Action.class).get(0);
|
||||
|
||||
// switch (action) {
|
||||
// case CREATE:
|
||||
// return (GameProposalState)trx.getOutputContractStates().get(0);
|
||||
|
||||
// case ACCEPT:
|
||||
// case REJECT:
|
||||
// case CANCEL:
|
||||
// return (GameProposalState)trx.getInputContractStates().get(0);
|
||||
|
||||
// default:
|
||||
// throw new RuntimeException(Action.UNSUPPORTED_ACTION_VALUE_OF +action);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package djmil.cordacheckers.gameboard;
|
||||
|
||||
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-board")
|
||||
public class CommitSubFlow implements SubFlow<SecureHash> {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CommitSubFlow.class);
|
||||
private final UtxoSignedTransaction signedTransaction;
|
||||
|
||||
public CommitSubFlow(UtxoSignedTransaction signedTransaction) {
|
||||
this.signedTransaction = signedTransaction;
|
||||
}
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService ledgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowMessaging flowMessaging;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public SecureHash call() {
|
||||
log.info("GameBoard commit started");
|
||||
|
||||
final MemberX500Name respondenName = null; // TODO
|
||||
|
||||
final FlowSession session = flowMessaging.initiateFlow(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("GameBoard commit " +trxId);
|
||||
return trxId;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package djmil.cordacheckers.gameboard;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameBoardCommand;
|
||||
import djmil.cordacheckers.contracts.GameBoardContract;
|
||||
import djmil.cordacheckers.contracts.UtxoLedgerTransactionUtil;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import djmil.cordacheckers.states.Piece;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
|
||||
|
||||
// GameBoard from the player's point of view
|
||||
public class GameBoardView {
|
||||
public final String opponentName;
|
||||
public final Piece.Color opponentColor;
|
||||
public final Boolean opponentMove;
|
||||
public final Map<Integer, Piece> board;
|
||||
public /*final*/ GameBoardCommand previousCommand;
|
||||
public final String message;
|
||||
public final UUID id;
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public GameBoardView() {
|
||||
this.opponentName = null;
|
||||
this.opponentColor = null;
|
||||
this.opponentMove = null;
|
||||
this.board = null;
|
||||
this.previousCommand = null;
|
||||
this.message = null;
|
||||
this.id = null;
|
||||
}
|
||||
|
||||
// A view from a perspective of a concrete player, on a ledger transaction that has
|
||||
// produced new GameBoardState
|
||||
public GameBoardView(MemberX500Name myName, UtxoLedgerTransaction utxoGameBoard) {
|
||||
|
||||
final GameProposalState referanceGameProposal = GameBoardContract
|
||||
.getReferanceGameProposalState(utxoGameBoard);
|
||||
|
||||
this.opponentName = referanceGameProposal.getOpponentName(myName).getCommonName();
|
||||
this.opponentColor = referanceGameProposal.getOpponentColor(myName);
|
||||
|
||||
final GameBoardState stateGameBoard = UtxoLedgerTransactionUtil
|
||||
.getSingleOutputState(utxoGameBoard, GameBoardState.class);
|
||||
|
||||
this.opponentMove = this.opponentColor == stateGameBoard.getMoveColor();
|
||||
this.board = stateGameBoard.getBoard();
|
||||
this.message = stateGameBoard.getMessage();
|
||||
this.id = stateGameBoard.getId();
|
||||
|
||||
this.previousCommand = UtxoLedgerTransactionUtil
|
||||
.getOptionalCommand(utxoGameBoard, GameBoardCommand.class)
|
||||
.orElseGet(() -> null);
|
||||
}
|
||||
|
||||
}
|
@ -43,10 +43,10 @@ public class ListFlow implements ClientStartableFlow {
|
||||
final var unconsumedGameBoardList = utxoLedgerService
|
||||
.findUnconsumedStatesByType(GameBoardState.class);
|
||||
|
||||
List<ListItem> res = new LinkedList<ListItem>();
|
||||
List<GameBoardView> res = new LinkedList<GameBoardView>();
|
||||
for (StateAndRef<GameBoardState> gbState :unconsumedGameBoardList) {
|
||||
// NOTE: lambda operations (aka .map) can not be used with UtxoLedgerService instances
|
||||
res.add(prepareListItem(gbState));
|
||||
res.add(prepareGameBoardView(gbState));
|
||||
}
|
||||
|
||||
return new FlowResult(res)
|
||||
@ -59,7 +59,7 @@ public class ListFlow implements ClientStartableFlow {
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private ListItem prepareListItem(StateAndRef<GameBoardState> stateAndRef) {
|
||||
private GameBoardView prepareGameBoardView(StateAndRef<GameBoardState> stateAndRef) {
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
|
||||
final SecureHash trxId = stateAndRef.getRef().getTransactionId();
|
||||
@ -67,14 +67,9 @@ public class ListFlow implements ClientStartableFlow {
|
||||
final UtxoLedgerTransaction utxoGameBoard = utxoLedgerService
|
||||
.findLedgerTransaction(trxId);
|
||||
|
||||
final GameProposalState referanceGameProposal = GameBoardContract
|
||||
.getReferanceGameProposalState(utxoGameBoard);
|
||||
var newGbView = new GameBoardView(myName, utxoGameBoard);
|
||||
|
||||
return new ListItem(
|
||||
stateAndRef.getState().getContractState(),
|
||||
referanceGameProposal.getOpponentName(myName),
|
||||
referanceGameProposal.getOpponentColor(myName)
|
||||
);
|
||||
return newGbView;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
package djmil.cordacheckers.gameboard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.Piece;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
|
||||
// Class to hold results of the List flow.
|
||||
// JsonMarshallingService can only serialize simple classes that the underlying Jackson serializer recognises,
|
||||
// hence creating a DTO style object which consists only of Strings and a UUIDs. It is possible to create custom
|
||||
// serializers for the JsonMarshallingService in the future.
|
||||
|
||||
public class ListItem {
|
||||
public final String opponentName;
|
||||
public final Piece.Color opponentColor;
|
||||
public final Boolean opponentMove;
|
||||
public final Object board;
|
||||
public final String message;
|
||||
public final UUID id;
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public ListItem() {
|
||||
this.opponentName = null;
|
||||
this.opponentColor = null;
|
||||
this.opponentMove = null;
|
||||
this.board = null;
|
||||
this.message = null;
|
||||
this.id = null;
|
||||
}
|
||||
|
||||
public ListItem(GameBoardState gameBoard, MemberX500Name opponentName, Piece.Color opponentColor) {
|
||||
this.opponentName = opponentName.getCommonName();
|
||||
this.opponentColor = opponentColor;
|
||||
this.opponentMove = opponentColor == gameBoard.getMoveColor();
|
||||
this.board = gameBoard.getBoard();
|
||||
this.message = gameBoard.getMessage();
|
||||
this.id = gameBoard.getId();
|
||||
}
|
||||
|
||||
}
|
@ -6,6 +6,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.FlowResult;
|
||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
@ -23,11 +24,9 @@ import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import static djmil.cordacheckers.contracts.GameProposalContract.Action;
|
||||
public class CommandFlow implements ClientStartableFlow {
|
||||
|
||||
public class ActionFlow implements ClientStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CreateFlow.class);
|
||||
private final static Logger log = LoggerFactory.getLogger(CommandFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
@ -42,24 +41,24 @@ public class ActionFlow implements ClientStartableFlow {
|
||||
@Suspendable
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
try {
|
||||
final ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class);
|
||||
final Action action = args.getAction();
|
||||
final CommandFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CommandFlowArgs.class);
|
||||
final GameProposalCommand command = args.getCommand();
|
||||
|
||||
final StateAndRef<GameProposalState> utxoGameProposal = findUnconsumedGameProposalState(args.getGameProposalUuid());
|
||||
|
||||
final UtxoSignedTransaction trx = prepareSignedTransaction(action, utxoGameProposal);
|
||||
final UtxoSignedTransaction trx = prepareSignedTransaction(command, utxoGameProposal);
|
||||
|
||||
final SecureHash trxId = this.flowEngine
|
||||
.subFlow( new CommitSubFlow(trx, action.getRespondent(utxoGameProposal)) );
|
||||
.subFlow( new CommitSubFlow(trx, command.getRespondent(utxoGameProposal)) );
|
||||
|
||||
if (action == Action.ACCEPT) {
|
||||
if (command == GameProposalCommand.ACCEPT) {
|
||||
GameBoardState newGb = (GameBoardState)trx.getOutputStateAndRefs().get(0).getState().getContractState();
|
||||
|
||||
return new FlowResult(newGb.getId(), trxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
|
||||
return new FlowResult(action+"ED", trxId) // REJECT+ED
|
||||
return new FlowResult(command+"ED", trxId) // REJECT+ED
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
}
|
||||
catch (Exception e) {
|
||||
@ -86,17 +85,17 @@ public class ActionFlow implements ClientStartableFlow {
|
||||
|
||||
@Suspendable
|
||||
private UtxoSignedTransaction prepareSignedTransaction(
|
||||
Action action,
|
||||
GameProposalCommand command,
|
||||
StateAndRef<GameProposalState> utxoGameProposal
|
||||
) {
|
||||
UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder()
|
||||
.setNotary(utxoGameProposal.getState().getNotaryName())
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addInputState(utxoGameProposal.getRef())
|
||||
.addCommand(action)
|
||||
.addCommand(command)
|
||||
.addSignatories(utxoGameProposal.getState().getContractState().getParticipants());
|
||||
|
||||
if (action == Action.ACCEPT) {
|
||||
if (command == GameProposalCommand.ACCEPT) {
|
||||
trxBuilder = trxBuilder
|
||||
.addOutputState(new GameBoardState(utxoGameProposal));
|
||||
//A state cannot be both an input and a reference input in the same transaction
|
@ -2,20 +2,20 @@ package djmil.cordacheckers.gameproposal;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameProposalContract;
|
||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
||||
|
||||
public class ActionFlowArgs {
|
||||
public class CommandFlowArgs {
|
||||
private UUID gameProposalUuid;
|
||||
private String action;
|
||||
private String command;
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public ActionFlowArgs() {
|
||||
public CommandFlowArgs() {
|
||||
this.gameProposalUuid = null;
|
||||
this.action = null;
|
||||
this.command = null;
|
||||
}
|
||||
|
||||
public GameProposalContract.Action getAction() {
|
||||
return GameProposalContract.Action.valueOf(this.action);
|
||||
public GameProposalCommand getCommand() {
|
||||
return GameProposalCommand.valueOf(this.command);
|
||||
}
|
||||
|
||||
public UUID getGameProposalUuid() {
|
@ -3,6 +3,7 @@ package djmil.cordacheckers.gameproposal;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.InitiatedBy;
|
||||
@ -17,8 +18,6 @@ import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator;
|
||||
|
||||
import static djmil.cordacheckers.contracts.GameProposalContract.Action;
|
||||
|
||||
@InitiatedBy(protocol = "game-proposal")
|
||||
public class CommitResponderFlow implements ResponderFlow {
|
||||
|
||||
@ -35,11 +34,11 @@ public class CommitResponderFlow implements ResponderFlow {
|
||||
public void call(FlowSession session) {
|
||||
try {
|
||||
UtxoTransactionValidator txValidator = ledgerTransaction -> {
|
||||
final Action action = ledgerTransaction.getCommands(Action.class).get(0);
|
||||
final GameProposalCommand command = ledgerTransaction.getCommands(GameProposalCommand.class).get(0);
|
||||
|
||||
final GameProposalState gameProposal = getGameProposal(ledgerTransaction);
|
||||
|
||||
checkParticipants(session, gameProposal, action);
|
||||
checkParticipants(session, gameProposal, command);
|
||||
|
||||
/*
|
||||
* Other checks / actions ?
|
||||
@ -63,23 +62,23 @@ public class CommitResponderFlow implements ResponderFlow {
|
||||
void checkParticipants(
|
||||
FlowSession session,
|
||||
GameProposalState gameProposal,
|
||||
Action action
|
||||
GameProposalCommand command
|
||||
) {
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
final MemberX500Name otherName = session.getCounterparty();
|
||||
|
||||
if (action.getRespondent(gameProposal).compareTo(myName) != 0)
|
||||
if (command.getRespondent(gameProposal).compareTo(myName) != 0)
|
||||
throw new CordaRuntimeException("Bad GameProposal acquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'");
|
||||
|
||||
if (action.getInitiator(gameProposal).compareTo(otherName) != 0)
|
||||
if (command.getInitiator(gameProposal).compareTo(otherName) != 0)
|
||||
throw new CordaRuntimeException("Bad GameProposal issuer: expected '"+otherName+"', actual '"+gameProposal.getIssuer()+"'");
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
GameProposalState getGameProposal(UtxoLedgerTransaction trx) {
|
||||
final Action action = trx.getCommands(Action.class).get(0);
|
||||
final GameProposalCommand command = trx.getCommands(GameProposalCommand.class).get(0);
|
||||
|
||||
switch (action) {
|
||||
switch (command) {
|
||||
case CREATE:
|
||||
return (GameProposalState)trx.getOutputContractStates().get(0);
|
||||
|
||||
@ -89,7 +88,7 @@ public class CommitResponderFlow implements ResponderFlow {
|
||||
return (GameProposalState)trx.getInputContractStates().get(0);
|
||||
|
||||
default:
|
||||
throw new RuntimeException(Action.UNSUPPORTED_ACTION_VALUE_OF +action);
|
||||
throw new RuntimeException(GameProposalCommand.UNSUPPORTED_VALUE_OF +command);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.FlowResult;
|
||||
import djmil.cordacheckers.contracts.GameProposalCommand;
|
||||
import djmil.cordacheckers.states.GameProposalState;
|
||||
import djmil.cordacheckers.states.Piece;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
@ -22,7 +23,6 @@ import net.corda.v5.membership.MemberInfo;
|
||||
import net.corda.v5.membership.NotaryInfo;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static djmil.cordacheckers.contracts.GameProposalContract.Action;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@ -53,14 +53,14 @@ public class CreateFlow implements ClientStartableFlow{
|
||||
public String call(ClientRequestBody requestBody) {
|
||||
try {
|
||||
log.info("flow: Create Game Proposal");
|
||||
final Action actino = Action.CREATE;
|
||||
final GameProposalCommand command = GameProposalCommand.CREATE;
|
||||
|
||||
final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody);
|
||||
|
||||
final UtxoSignedTransaction trx = prepareSignedTransaction(newGameProposal);
|
||||
|
||||
final SecureHash trxId = this.flowEngine
|
||||
.subFlow(new CommitSubFlow(trx, actino.getRespondent(newGameProposal)));
|
||||
.subFlow(new CommitSubFlow(trx, command.getRespondent(newGameProposal)));
|
||||
|
||||
return new FlowResult(newGameProposal.getId(), trxId)
|
||||
.toJsonEncodedString(jsonMarshallingService);
|
||||
@ -107,7 +107,7 @@ public class CreateFlow implements ClientStartableFlow{
|
||||
.setNotary(notary.getName())
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addOutputState(outputGameProposalState)
|
||||
.addCommand(Action.CREATE)
|
||||
.addCommand(GameProposalCommand.CREATE)
|
||||
.addSignatories(outputGameProposalState.getParticipants())
|
||||
.toSignedTransaction();
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
//package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
//
|
||||
//import net.corda.simulator.RequestData;
|
||||
//import net.corda.simulator.SimulatedVirtualNode;
|
||||
//import net.corda.simulator.Simulator;
|
||||
//import net.corda.v5.base.types.MemberX500Name;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//
|
||||
//class MyFirstFlowTest {
|
||||
// // Names picked to match the corda network in config/dev-net.json
|
||||
// private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB");
|
||||
// private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
|
||||
//
|
||||
// @Test
|
||||
// @SuppressWarnings("unchecked")
|
||||
// public void test_that_MyFirstFLow_returns_correct_message() {
|
||||
// // Instantiate an instance of the simulator.
|
||||
// Simulator simulator = new Simulator();
|
||||
//
|
||||
// // Create Alice's and Bob's virtual nodes, including the classes of the flows which will be registered on each node.
|
||||
// // Don't assign Bob's virtual node to a value. You don't need it for this particular test.
|
||||
// SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceX500, MyFirstFlow.class);
|
||||
// simulator.createVirtualNode(bobX500, MyFirstFlowResponder.class);
|
||||
//
|
||||
// // Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow.
|
||||
// MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
|
||||
//
|
||||
// // Create a requestData object.
|
||||
// RequestData requestData = RequestData.Companion.create(
|
||||
// "request no 1", // A unique reference for the instance of the flow request.
|
||||
// MyFirstFlow.class, // The name of the flow class which is to be started.
|
||||
// myFirstFlowStartArgs // The object which contains the start arguments of the flow.
|
||||
// );
|
||||
//
|
||||
// // Call the flow on Alice's virtual node and capture the response.
|
||||
// String flowResponse = aliceVN.callFlow(requestData);
|
||||
//
|
||||
// // Check that the flow has returned the expected string.
|
||||
// assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
|
||||
// }
|
||||
//}
|
Loading…
Reference in New Issue
Block a user