GameResultBuilder subflow
- looks for custodian and add it to the State participants - extra parameter to Commit subflow to initiate exchange session with Custodian as well
This commit is contained in:
parent
e1cc9bd9fd
commit
8971462c74
@ -8,6 +8,9 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState;
|
||||
import djmil.cordacheckers.cordaclient.dao.GameState.Status;
|
||||
import djmil.cordacheckers.cordaclient.dao.Piece;
|
||||
import djmil.cordacheckers.cordaclient.dao.Rank;
|
||||
import djmil.cordacheckers.user.HoldingIdentityResolver;
|
||||
|
||||
@ -22,12 +25,28 @@ public class RankingTests {
|
||||
@Test
|
||||
void testGlobalRanking() {
|
||||
final var hiCustodian = holdingIdentityResolver.getCustodian();
|
||||
final List<Rank> liderboard1 = cordaClient.fetchRanking(hiCustodian);
|
||||
|
||||
final var hiWinner = holdingIdentityResolver.getByUsername("Charlie");
|
||||
final var hiLooser = holdingIdentityResolver.getByUsername("Bob");
|
||||
|
||||
final GameState game = cordaClient.gameProposalCreate(
|
||||
hiWinner, hiLooser, Piece.Color.WHITE, "GameBoard GLOBAL_RANKING test");
|
||||
|
||||
cordaClient.gameProposalAccept(hiLooser, game.uuid());
|
||||
cordaClient.gameBoardSurrender(hiLooser, game.uuid());
|
||||
|
||||
final List<Rank> liderboard2 = cordaClient.fetchRanking(hiCustodian);
|
||||
|
||||
System.out.println(liderboard1);
|
||||
System.out.println(liderboard2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIndividualRanking() {
|
||||
final var hiCustodian = holdingIdentityResolver.getByUsername("Bob");
|
||||
final List<Rank> liderboard = cordaClient.fetchRanking(hiCustodian);
|
||||
|
||||
System.out.println("Liderboard " +liderboard);
|
||||
|
||||
|
||||
System.out.println(liderboard);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ public class GameResultState extends GameState {
|
||||
this.winnerName = winnerName;
|
||||
}
|
||||
|
||||
public GameResultState(GameBoardState gameBoardState, MemberX500Name winnerName) {
|
||||
super(gameBoardState.whitePlayer, gameBoardState.blackPlayer, gameBoardState.gameUuid, null, gameBoardState.participants);
|
||||
public GameResultState(GameBoardState gameBoardState, MemberX500Name winnerName, PublicKey custodianPubicKey) {
|
||||
super(gameBoardState.whitePlayer, gameBoardState.blackPlayer, gameBoardState.gameUuid, null, gameBoardState.participants, custodianPubicKey);
|
||||
this.winnerName = winnerName;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package djmil.cordacheckers.states;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -25,6 +26,18 @@ public abstract class GameState implements ContractState {
|
||||
this.participants = participants;
|
||||
}
|
||||
|
||||
GameState(MemberX500Name whitePlayer, MemberX500Name blackPlayer, UUID gameUuid,
|
||||
String message, List<PublicKey> participants, PublicKey additionalParticipand) {
|
||||
this.whitePlayer = whitePlayer;
|
||||
this.blackPlayer = blackPlayer;
|
||||
this.gameUuid = gameUuid;
|
||||
this.message = message;
|
||||
|
||||
var deepCopy = new LinkedList<>(participants);
|
||||
deepCopy.add(additionalParticipand);
|
||||
this.participants = deepCopy;
|
||||
}
|
||||
|
||||
public MemberX500Name getWhitePlayer() {
|
||||
return whitePlayer;
|
||||
}
|
||||
|
@ -8,13 +8,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import djmil.cordacheckers.contracts.GameCommand;
|
||||
import djmil.cordacheckers.gameresult.GameResultBuilder;
|
||||
import djmil.cordacheckers.gameresult.GameResultBuilder.Reason;
|
||||
import djmil.cordacheckers.gamestate.CommitSubFlow;
|
||||
import djmil.cordacheckers.gamestate.FlowResponce;
|
||||
import djmil.cordacheckers.gamestate.GetFlow;
|
||||
import djmil.cordacheckers.gamestate.View;
|
||||
import djmil.cordacheckers.gamestate.ViewBuilder;
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameResultState;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.application.flows.ClientRequestBody;
|
||||
import net.corda.v5.application.flows.ClientStartableFlow;
|
||||
@ -23,7 +23,6 @@ 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.common.NotaryLookup;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
@ -57,24 +56,27 @@ public class SurrenderFlow implements ClientStartableFlow{
|
||||
try {
|
||||
final GameCommand command = new GameCommand(GameCommand.Action.GAME_BOARD_SURRENDER);
|
||||
|
||||
final UUID gameStateUuid = UUID.fromString(requestBody.getRequestBody());
|
||||
final UUID gameUuid = UUID.fromString(requestBody.getRequestBody());
|
||||
|
||||
final StateAndRef<GameState> inputStateSar = this.flowEngine
|
||||
.subFlow(new GetFlow(gameStateUuid));
|
||||
final StateAndRef<GameState> gameBoardSar = this.flowEngine
|
||||
.subFlow(new GetFlow(gameUuid));
|
||||
|
||||
final GameResultState outputState = prepareGameResultState(inputStateSar);
|
||||
final GameResultBuilder.Result out = this.flowEngine
|
||||
.subFlow(new GameResultBuilder(gameBoardSar, Reason.SURRENDER));
|
||||
|
||||
final UtxoSignedTransaction gameBoardSurrenderTrx = utxoLedgerService.createTransactionBuilder()
|
||||
.addCommand(command)
|
||||
.addInputState(inputStateSar.getRef())
|
||||
.addOutputState(outputState)
|
||||
.addSignatories(outputState.getParticipants())
|
||||
.setNotary(inputStateSar.getState().getNotaryName())
|
||||
.addInputState(gameBoardSar.getRef())
|
||||
.addOutputState(out.gameResult)
|
||||
.addSignatories(out.gameResult.getParticipants())
|
||||
.setNotary(gameBoardSar.getState().getNotaryName())
|
||||
.setTimeWindowUntil(Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.toSignedTransaction();
|
||||
|
||||
utxoTrxId = this.flowEngine
|
||||
.subFlow(new CommitSubFlow(gameBoardSurrenderTrx, command.getCounterparty(inputStateSar)));
|
||||
.subFlow(new CommitSubFlow(gameBoardSurrenderTrx,
|
||||
command.getCounterparty(gameBoardSar),
|
||||
out.custodyName));
|
||||
|
||||
final View gameStateView = this.flowEngine
|
||||
.subFlow(new ViewBuilder(utxoTrxId));
|
||||
@ -90,15 +92,4 @@ public class SurrenderFlow implements ClientStartableFlow{
|
||||
}
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
GameResultState prepareGameResultState(StateAndRef<GameState> gameStateSar) {
|
||||
final GameState gameState = gameStateSar.getState().getContractState();
|
||||
final GameBoardState gameBoard = (GameBoardState) gameState;
|
||||
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
final MemberX500Name winnerName = gameBoard.getOpponentName(myName);
|
||||
|
||||
return new GameResultState(gameBoard, winnerName);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
package djmil.cordacheckers.gameresult;
|
||||
|
||||
import djmil.cordacheckers.states.GameBoardState;
|
||||
import djmil.cordacheckers.states.GameResultState;
|
||||
import djmil.cordacheckers.states.GameState;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.SubFlow;
|
||||
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.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.membership.MemberInfo;
|
||||
|
||||
public class GameResultBuilder implements SubFlow<GameResultBuilder.Result> {
|
||||
public class Result {
|
||||
final public GameResultState gameResult;
|
||||
final public MemberX500Name custodyName;
|
||||
|
||||
public Result(GameResultState gameResult, MemberX500Name custodyName){
|
||||
this.gameResult = gameResult;
|
||||
this.custodyName = custodyName;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Reason {
|
||||
VICTORY,
|
||||
SURRENDER
|
||||
}
|
||||
|
||||
private final GameBoardState gameBoard;
|
||||
private final Reason reason;
|
||||
|
||||
public GameResultBuilder(StateAndRef<GameState> gameBoardSar, Reason reason) {
|
||||
this.gameBoard = (GameBoardState)gameBoardSar.getState().getContractState();
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public Result call() {
|
||||
final MemberInfo custodiaInfo = findCustodian();
|
||||
|
||||
return new Result(
|
||||
new GameResultState(gameBoard, winnerName(), custodiaInfo.getLedgerKeys().get(0)),
|
||||
custodiaInfo.getName());
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
MemberInfo findCustodian() {
|
||||
return memberLookup.lookup()
|
||||
.stream()
|
||||
.filter(m -> m.getName().getOrganizationUnit().equals("Custodian") )
|
||||
.reduce((a,b) -> {throw new IllegalStateException("Multiple Custodian VNodes");})
|
||||
.orElseThrow( () -> new IllegalStateException("No Custodian VNode found"));
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
MemberX500Name winnerName() {
|
||||
final MemberX500Name myName = memberLookup.myInfo().getName();
|
||||
|
||||
switch(reason) {
|
||||
case VICTORY:
|
||||
return myName;
|
||||
case SURRENDER:
|
||||
return gameBoard.getOpponentName(myName);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Bad reason");
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package djmil.cordacheckers.gamestate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -9,6 +10,7 @@ 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.membership.MemberLookup;
|
||||
import net.corda.v5.application.messaging.FlowMessaging;
|
||||
import net.corda.v5.application.messaging.FlowSession;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
@ -23,10 +25,18 @@ public class CommitSubFlow implements SubFlow<SecureHash> {
|
||||
private final static Logger log = LoggerFactory.getLogger(CommitSubFlow.class);
|
||||
private final UtxoSignedTransaction utxTrxCandidate;
|
||||
private final MemberX500Name counterpartyName;
|
||||
private final MemberX500Name custodyName;
|
||||
|
||||
public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name counterpartyName) {
|
||||
this.utxTrxCandidate = signedTransaction;
|
||||
this.counterpartyName = counterpartyName;
|
||||
this.custodyName = null;
|
||||
}
|
||||
|
||||
public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name counterpartyName, MemberX500Name custodyName) {
|
||||
this.utxTrxCandidate = signedTransaction;
|
||||
this.counterpartyName = counterpartyName;
|
||||
this.custodyName = custodyName;
|
||||
}
|
||||
|
||||
@CordaInject
|
||||
@ -35,19 +45,25 @@ public class CommitSubFlow implements SubFlow<SecureHash> {
|
||||
@CordaInject
|
||||
public FlowMessaging flowMessaging;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public SecureHash call() {
|
||||
log.info("GameState commit started");
|
||||
|
||||
final FlowSession session = flowMessaging.initiateFlow(this.counterpartyName);
|
||||
|
||||
/*
|
||||
* 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 FlowSession session = flowMessaging.initiateFlow(this.counterpartyName);
|
||||
List<FlowSession> sessionsList = new LinkedList<FlowSession>(Arrays.asList(session));
|
||||
|
||||
if (custodyName != null) {
|
||||
sessionsList.add(flowMessaging.initiateFlow(custodyName));
|
||||
}
|
||||
|
||||
final SecureHash trxId = ledgerService
|
||||
.finalize(this.utxTrxCandidate, sessionsList)
|
||||
|
@ -100,16 +100,18 @@ public class CommitSubFlowResponder implements ResponderFlow {
|
||||
|
||||
@Suspendable
|
||||
void checkParticipants(FlowSession session, GameCommand gameCommand, GameState gameState) throws ParticipantException {
|
||||
final var myName = memberLookup.myInfo().getName();
|
||||
final var opponentName = gameState.getOpponentName(myName); // throws NotInvolved
|
||||
final var conterpartyName = session.getCounterparty();
|
||||
final var actorName = gameCommand.getInitiator(gameState);
|
||||
|
||||
if (conterpartyName.compareTo(opponentName) != 0)
|
||||
throw new ParticipantException("Counterparty", conterpartyName, opponentName);
|
||||
|
||||
if (actorName.compareTo(conterpartyName) != 0)
|
||||
throw new ParticipantException("Actor", conterpartyName, actorName);
|
||||
|
||||
final var myName = memberLookup.myInfo().getName();
|
||||
if (myName.getOrganizationUnit().equals("Custodian"))
|
||||
return; // Custodian shall not validate state's counterparty
|
||||
|
||||
final var opponentName = gameState.getOpponentName(myName); // throws NotInvolved
|
||||
if (conterpartyName.compareTo(opponentName) != 0)
|
||||
throw new ParticipantException("Counterparty", conterpartyName, opponentName);
|
||||
}
|
||||
|
||||
public static class ParticipantException extends Exception {
|
||||
|
Loading…
Reference in New Issue
Block a user