Compare commits
	
		
			No commits in common. "e235ecb942a7e53cc1f32648974c0c76a2a48287" and "7df57cb4d291ccc5ad2842ef20363d8096dc0bf0" have entirely different histories.
		
	
	
		
			e235ecb942
			...
			7df57cb4d2
		
	
		
| @ -14,23 +14,17 @@ import org.springframework.http.ResponseEntity; | |||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| import org.springframework.web.client.RestTemplate; | import org.springframework.web.client.RestTemplate; | ||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.core.JsonProcessingException; |  | ||||||
| import com.fasterxml.jackson.databind.JsonMappingException; |  | ||||||
| import com.fasterxml.jackson.databind.ObjectMapper; | import com.fasterxml.jackson.databind.ObjectMapper; | ||||||
| 
 | 
 | ||||||
| import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; | import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; | ||||||
| import djmil.cordacheckers.cordaclient.dao.Color; | import djmil.cordacheckers.cordaclient.dao.Color; | ||||||
| import djmil.cordacheckers.cordaclient.dao.GameProposal; |  | ||||||
| import djmil.cordacheckers.cordaclient.dao.VirtualNode; | import djmil.cordacheckers.cordaclient.dao.VirtualNode; | ||||||
| import djmil.cordacheckers.cordaclient.dao.VirtualNodeList; | import djmil.cordacheckers.cordaclient.dao.VirtualNodeList; | ||||||
| import djmil.cordacheckers.cordaclient.dao.flow.RequestBody; | import djmil.cordacheckers.cordaclient.dao.flow.RequestBody; | ||||||
| import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody; | import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody; | ||||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq; | import djmil.cordacheckers.cordaclient.dao.flow.arguments.CreateGameProposal; | ||||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateRes; |  | ||||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalListRes; |  | ||||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty; | import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty; | ||||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq; | import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalAction; | ||||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionRes; |  | ||||||
| 
 | 
 | ||||||
| @Service | @Service | ||||||
| public class CordaClient { | public class CordaClient { | ||||||
| @ -65,7 +59,7 @@ public class CordaClient { | |||||||
|      * @param holdingIdentity |      * @param holdingIdentity | ||||||
|      * @return GameProposals list in JSON form |      * @return GameProposals list in JSON form | ||||||
|      */ |      */ | ||||||
|     public List<GameProposal> gameProposalList(HoldingIdentity holdingIdentity) { |     public String listGameProposals(HoldingIdentity holdingIdentity) { | ||||||
| 
 | 
 | ||||||
|         final RequestBody requestBody = new RequestBody( |         final RequestBody requestBody = new RequestBody( | ||||||
|             "list-" + UUID.randomUUID(), |             "list-" + UUID.randomUUID(), | ||||||
| @ -73,27 +67,21 @@ public class CordaClient { | |||||||
|             new Empty() |             new Empty() | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|         final GameProposalListRes listFlowResult = cordaFlowExecute( |         final String gameProposalsJsonString = cordaFlowExecute( | ||||||
|             holdingIdentity, |             holdingIdentity, | ||||||
|             requestBody, |             requestBody | ||||||
|             GameProposalListRes.class |  | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|         if (listFlowResult.failureStatus() != null) { |         return gameProposalsJsonString; | ||||||
|             System.out.println("GameProposalCreateFlow failed: " + listFlowResult.failureStatus()); |  | ||||||
|             throw new RuntimeException("GameProsal: CreateFlow execution has failed"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return listFlowResult.successStatus(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public UUID gameProposalCreate( |     public String createGameProposal( | ||||||
|         HoldingIdentity sender, |         HoldingIdentity sender, | ||||||
|         HoldingIdentity receiver, |         HoldingIdentity receiver, | ||||||
|         Color receiverColor, |         Color receiverColor, | ||||||
|         String message |         String message | ||||||
|     ) throws JsonMappingException, JsonProcessingException { |     ) { | ||||||
|         final GameProposalCreateReq createGameProposal = new GameProposalCreateReq( |         final CreateGameProposal createGameProposal = new CreateGameProposal( | ||||||
|             receiver.x500Name(), |             receiver.x500Name(), | ||||||
|             receiverColor, |             receiverColor, | ||||||
|             message |             message | ||||||
| @ -105,28 +93,21 @@ public class CordaClient { | |||||||
|             createGameProposal |             createGameProposal | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|         final GameProposalCreateRes createResult = cordaFlowExecute( |         final String createdGameProposalUuid = cordaFlowExecute( | ||||||
|             sender, |             sender, | ||||||
|             requestBody, |             requestBody | ||||||
|             GameProposalCreateRes.class |  | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|         if (createResult.failureStatus() != null) { |         return createdGameProposalUuid; | ||||||
|             System.out.println("GameProposalCreateFlow failed: " + createResult.failureStatus()); |  | ||||||
|             throw new RuntimeException("GameProsal: CreateFlow execution has failed"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return createResult.successStatus(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public String gameProposalAction( |     public String rejectGameProposal( | ||||||
|         HoldingIdentity self, |         HoldingIdentity self, | ||||||
|         UUID gameProposalUuid, |         String gameProposalUuid | ||||||
|         GameProposalActionReq.Action action |  | ||||||
|     ) { |     ) { | ||||||
|         final GameProposalActionReq rejectGameProposal = new GameProposalActionReq( |         final GameProposalAction rejectGameProposal = new GameProposalAction( | ||||||
|             gameProposalUuid.toString(), |             gameProposalUuid, | ||||||
|             action |             GameProposalAction.Action.REJECT | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|         final RequestBody requestBody = new RequestBody( |         final RequestBody requestBody = new RequestBody( | ||||||
| @ -135,21 +116,15 @@ public class CordaClient { | |||||||
|             rejectGameProposal |             rejectGameProposal | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|         final GameProposalActionRes actionResult = cordaFlowExecute( |         final String createdGameProposalUuid = cordaFlowExecute( | ||||||
|             self, |             self, | ||||||
|             requestBody, |             requestBody | ||||||
|             GameProposalActionRes.class |  | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|         if (actionResult.failureStatus() != null) { |         return createdGameProposalUuid; | ||||||
|             System.out.println("GameProposalActionFlow failed: " + actionResult.failureStatus()); |  | ||||||
|             throw new RuntimeException("GameProsal: ActionFlow execution has failed"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return actionResult.successStatus(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private <T> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowResultType) { |     private String cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody) { | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             final String requestBodyJson = this.jsonMapper.writeValueAsString(requestBody); |             final String requestBodyJson = this.jsonMapper.writeValueAsString(requestBody); | ||||||
| @ -159,9 +134,15 @@ public class CordaClient { | |||||||
|                 requestBodyJson |                 requestBodyJson | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|             final String flowResult = cordaFlowPoll(startedFlow); |             final String flowExecutionResult = cordaFlowPoll(startedFlow); | ||||||
| 
 | 
 | ||||||
|             return this.jsonMapper.readValue(flowResult, flowResultType); |             // NOTE: | ||||||
|  |             // At this point, real production code, probably should convert data between CordaFlow | ||||||
|  |             // abstarction into ReactApp abstraction. Instead, to limit boring json shuffling, all | ||||||
|  |             // family of Corda.List flows were deliberatly designed to return frontend frendly JSONs. | ||||||
|  |             // At the same time, all other Corda flows, simply return plain text string with | ||||||
|  |             // operation result. | ||||||
|  |             return flowExecutionResult; | ||||||
|         }  |         }  | ||||||
|         catch (Exception e) { |         catch (Exception e) { | ||||||
|             throw new RuntimeException("Unable to perform "+requestBody.flowClassName() |             throw new RuntimeException("Unable to perform "+requestBody.flowClassName() | ||||||
| @ -202,7 +183,7 @@ public class CordaClient { | |||||||
| 
 | 
 | ||||||
|         for (int retry = 0; retry < 6; retry++) { |         for (int retry = 0; retry < 6; retry++) { | ||||||
|             // Give Corda cluster some time to process our request |             // Give Corda cluster some time to process our request | ||||||
|             TimeUnit.SECONDS.sleep(retry*retry +2); // 2 3 6 10 18 27 sec |             TimeUnit.SECONDS.sleep(retry*retry +1); // 1 2 5 8 17 33 sec | ||||||
| 
 | 
 | ||||||
|             final ResponseEntity<ResponseBody> responce = this.restTemplate.exchange( |             final ResponseEntity<ResponseBody> responce = this.restTemplate.exchange( | ||||||
|                 "/flow/"  |                 "/flow/"  | ||||||
| @ -223,16 +204,15 @@ public class CordaClient { | |||||||
|                 "CordaClient.cordaFlowPoll: empty getBody()" |                 "CordaClient.cordaFlowPoll: empty getBody()" | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|             if (responseBody.flowError() != null) { |  | ||||||
|                 throw new RuntimeException("CordaClient.cordaFlowPoll: flow execution error: "  |  | ||||||
|                                             +responseBody.flowError()); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) { |             if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) { | ||||||
|  |                 System.out.println("Completed "+responseBody.flowResult()); | ||||||
|                 return responseBody.flowResult(); |                 return responseBody.flowResult(); | ||||||
|  |             } else | ||||||
|  |             if (responseBody.flowError() != null) { | ||||||
|  |                 return "Flow execution error: " +responseBody.flowError(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         throw new RuntimeException ("CordaClient.cordaFlowPoll: retry limit"); |         return "CordaClient.cordaFlowPoll: retry limit"; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,15 +1,13 @@ | |||||||
| package djmil.cordacheckers.cordaclient.dao; | package djmil.cordacheckers.cordaclient.dao; | ||||||
| 
 | 
 | ||||||
| import java.util.UUID; |  | ||||||
| 
 |  | ||||||
| import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | ||||||
| 
 | 
 | ||||||
| @JsonDeserialize | @JsonDeserialize | ||||||
| public record GameProposal( | public record GameProposal( | ||||||
|     String issuer, |     String sender, | ||||||
|     String acquier, |     String recipient, | ||||||
|     Color acquierColor, |     Color recipientColor, | ||||||
|     String message, |     String message, | ||||||
|     UUID id) { |     String id) { | ||||||
|      |      | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,6 +2,6 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments; | |||||||
| 
 | 
 | ||||||
| import djmil.cordacheckers.cordaclient.dao.Color; | import djmil.cordacheckers.cordaclient.dao.Color; | ||||||
| 
 | 
 | ||||||
| public record GameProposalCreateReq(String opponentName, Color opponentColor, String message) { | public record CreateGameProposal(String opponentName, Color opponentColor, String message) { | ||||||
|      |      | ||||||
| } | } | ||||||
| @ -1,6 +1,6 @@ | |||||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; | package djmil.cordacheckers.cordaclient.dao.flow.arguments; | ||||||
| 
 | 
 | ||||||
| public record GameProposalActionReq(String gameProposalUuid, Action action) { | public record GameProposalAction(String gameProposalUuid, Action action) { | ||||||
|     public enum Action { |     public enum Action { | ||||||
|         ACCEPT, |         ACCEPT, | ||||||
|         REJECT, |         REJECT, | ||||||
| @ -1,5 +0,0 @@ | |||||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; |  | ||||||
| 
 |  | ||||||
| public record GameProposalActionRes(String successStatus, String transactionId, String failureStatus) { |  | ||||||
|      |  | ||||||
| } |  | ||||||
| @ -1,10 +0,0 @@ | |||||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; |  | ||||||
| 
 |  | ||||||
| import java.util.UUID; |  | ||||||
| 
 |  | ||||||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |  | ||||||
| 
 |  | ||||||
| @JsonIgnoreProperties(ignoreUnknown = true) |  | ||||||
| public record GameProposalCreateRes(UUID successStatus, String failureStatus) { |  | ||||||
|      |  | ||||||
| } |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| package djmil.cordacheckers.cordaclient.dao.flow.arguments; |  | ||||||
| 
 |  | ||||||
| import java.util.List; |  | ||||||
| 
 |  | ||||||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |  | ||||||
| 
 |  | ||||||
| import djmil.cordacheckers.cordaclient.dao.GameProposal; |  | ||||||
| 
 |  | ||||||
| @JsonIgnoreProperties(ignoreUnknown = true) |  | ||||||
| public record GameProposalListRes(List<GameProposal> successStatus, String failureStatus) { |  | ||||||
|      |  | ||||||
| } |  | ||||||
| @ -1,8 +1,6 @@ | |||||||
| package djmil.cordacheckers.gameproposal; | package djmil.cordacheckers.gameproposal; | ||||||
| 
 | 
 | ||||||
| import java.net.URI; | import java.net.URI; | ||||||
| import java.util.List; |  | ||||||
| import java.util.UUID; |  | ||||||
| 
 | 
 | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.http.ResponseEntity; | import org.springframework.http.ResponseEntity; | ||||||
| @ -11,12 +9,9 @@ import org.springframework.web.bind.annotation.GetMapping; | |||||||
| import org.springframework.web.bind.annotation.RestController; | import org.springframework.web.bind.annotation.RestController; | ||||||
| import org.springframework.web.util.UriComponentsBuilder; | import org.springframework.web.util.UriComponentsBuilder; | ||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.core.JsonProcessingException; |  | ||||||
| import com.fasterxml.jackson.databind.JsonMappingException; |  | ||||||
| 
 |  | ||||||
| import djmil.cordacheckers.cordaclient.CordaClient; | import djmil.cordacheckers.cordaclient.CordaClient; | ||||||
| import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; | import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; | ||||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq; | import djmil.cordacheckers.cordaclient.dao.flow.arguments.CreateGameProposal; | ||||||
| import djmil.cordacheckers.cordaclient.dao.Color; | import djmil.cordacheckers.cordaclient.dao.Color; | ||||||
| import djmil.cordacheckers.cordaclient.dao.GameProposal; | import djmil.cordacheckers.cordaclient.dao.GameProposal; | ||||||
| import djmil.cordacheckers.user.HoldingIdentityResolver; | import djmil.cordacheckers.user.HoldingIdentityResolver; | ||||||
| @ -41,9 +36,9 @@ public class GameProposalController { | |||||||
|     public ResponseEntity<String> findAllUnconsumed( |     public ResponseEntity<String> findAllUnconsumed( | ||||||
|         @AuthenticationPrincipal User player |         @AuthenticationPrincipal User player | ||||||
|     ) { |     ) { | ||||||
|         List<GameProposal> gpList = cordaClient.gameProposalList(player.getHoldingIdentity()); |         String gpList = cordaClient.listGameProposals(player.getHoldingIdentity()); | ||||||
| 
 | 
 | ||||||
|         return ResponseEntity.ok(gpList.toString()); |         return ResponseEntity.ok(gpList); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // @PostMapping() |     // @PostMapping() | ||||||
| @ -55,16 +50,15 @@ public class GameProposalController { | |||||||
|     @PostMapping() |     @PostMapping() | ||||||
|     public ResponseEntity<Void> createGameProposal( |     public ResponseEntity<Void> createGameProposal( | ||||||
|         @AuthenticationPrincipal User sender, |         @AuthenticationPrincipal User sender, | ||||||
|         @RequestBody GameProposalCreateReq gpRequest, |         @RequestBody CreateGameProposal gpRequest, | ||||||
|         UriComponentsBuilder ucb |         UriComponentsBuilder ucb | ||||||
|     ) throws JsonMappingException, JsonProcessingException { |     ) { | ||||||
|         final HoldingIdentity gpSender = sender.getHoldingIdentity(); |         final HoldingIdentity gpSender = sender.getHoldingIdentity(); | ||||||
|         // TODO: throw execption with custom type |         // TODO: throw execption with custom type | ||||||
|         final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName()); |         final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName()); | ||||||
|         final Color gpReceiverColor = gpRequest.opponentColor(); |         final Color gpReceiverColor = gpRequest.opponentColor(); | ||||||
| 
 | 
 | ||||||
|         // TODO handle expectionns here |         String newGameProposalUuid = cordaClient.createGameProposal( | ||||||
|         UUID newGameProposalUuid = cordaClient.gameProposalCreate( |  | ||||||
|             gpSender, |             gpSender, | ||||||
|             gpReceiver, |             gpReceiver, | ||||||
|             gpReceiverColor, |             gpReceiverColor, | ||||||
|  | |||||||
| @ -6,19 +6,14 @@ import org.springframework.beans.factory.annotation.Autowired; | |||||||
| import org.springframework.boot.test.context.SpringBootTest; | import org.springframework.boot.test.context.SpringBootTest; | ||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.core.JsonProcessingException; | import com.fasterxml.jackson.core.JsonProcessingException; | ||||||
| import com.fasterxml.jackson.databind.JsonMappingException; |  | ||||||
| 
 | 
 | ||||||
| import djmil.cordacheckers.cordaclient.dao.Color; | import djmil.cordacheckers.cordaclient.dao.Color; | ||||||
| import djmil.cordacheckers.cordaclient.dao.GameProposal; |  | ||||||
| import djmil.cordacheckers.cordaclient.dao.VirtualNode; | import djmil.cordacheckers.cordaclient.dao.VirtualNode; | ||||||
| import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq.Action; |  | ||||||
| import djmil.cordacheckers.user.HoldingIdentityResolver; | import djmil.cordacheckers.user.HoldingIdentityResolver; | ||||||
| 
 | 
 | ||||||
| import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; |  | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.UUID; |  | ||||||
| 
 | 
 | ||||||
| import javax.naming.InvalidNameException; | import javax.naming.InvalidNameException; | ||||||
| 
 | 
 | ||||||
| @ -39,51 +34,44 @@ public class CordaClientTest { | |||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     void testGameProposalList() throws JsonProcessingException { |     void testGameProposalList() throws JsonProcessingException { | ||||||
|         List<GameProposal> gpList = cordaClient.gameProposalList( |         String resp = cordaClient.listGameProposals( | ||||||
|             holdingIdentityResolver.getByUsername("Alice")); |             holdingIdentityResolver.getByUsername("alice")); | ||||||
| 
 | 
 | ||||||
|         System.out.println("testListGameProposals\n"+ gpList); |         System.out.println("testListGameProposals "+ resp); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     void testGemeProposalCreate() throws JsonMappingException, JsonProcessingException { |     void testGemeProposalCreate() { | ||||||
|         final String gpIssuer = "alice"; |  | ||||||
|         final String gpAcquier = "bob"; |  | ||||||
|         final Color gpAcquierColor = Color.WHITE; |  | ||||||
|         final String gpMessage = "GameProposal create test"; |  | ||||||
| 
 |  | ||||||
|         final UUID createdGpUuid = cordaClient.gameProposalCreate( |  | ||||||
|             holdingIdentityResolver.getByUsername(gpIssuer), |  | ||||||
|             holdingIdentityResolver.getByUsername(gpAcquier), |  | ||||||
|             gpAcquierColor, |  | ||||||
|             gpMessage |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         List<GameProposal> gpListSender = cordaClient.gameProposalList( |  | ||||||
|             holdingIdentityResolver.getByUsername(gpIssuer)); |  | ||||||
| 
 |  | ||||||
|         assertThat(findByUuid(gpListSender, createdGpUuid)).isNotNull(); |  | ||||||
| 
 |  | ||||||
|         List<GameProposal> gpListReceiver = cordaClient.gameProposalList( |  | ||||||
|             holdingIdentityResolver.getByUsername(gpAcquier)); |  | ||||||
| 
 |  | ||||||
|         GameProposal gp; |  | ||||||
|         assertThat(gp = findByUuid(gpListReceiver, createdGpUuid)).isNotNull(); |  | ||||||
| 
 |  | ||||||
|         assertThat(gp.acquier()).isEqualToIgnoringCase(gpAcquier); |  | ||||||
|         assertThat(gp.issuer()).isEqualToIgnoringCase(gpIssuer); |  | ||||||
|         assertThat(gp.acquierColor()).isEqualByComparingTo(gpAcquierColor); |  | ||||||
|         assertThat(gp.message()).isEqualTo(gpMessage); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Test |  | ||||||
|     void testGemeProposalReject() throws JsonMappingException, JsonProcessingException { |  | ||||||
|         final String gpSender = "alice"; |         final String gpSender = "alice"; | ||||||
|         final String gpReceiver = "bob"; |         final String gpReceiver = "bob"; | ||||||
|         final Color gpReceiverColor = Color.WHITE; |         final Color gpReceiverColor = Color.WHITE; | ||||||
|         final String gpMessage = "GameProposal REJECT test"; |         final String gpMessage = "GameProposal create test"; | ||||||
| 
 | 
 | ||||||
|         final UUID gpUuid = cordaClient.gameProposalCreate( |         final String gpUuid = cordaClient.createGameProposal( | ||||||
|  |             holdingIdentityResolver.getByUsername(gpSender), | ||||||
|  |             holdingIdentityResolver.getByUsername(gpReceiver), | ||||||
|  |             gpReceiverColor, | ||||||
|  |             gpMessage | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         String listResSender = cordaClient.listGameProposals( | ||||||
|  |             holdingIdentityResolver.getByUsername(gpSender)); | ||||||
|  | 
 | ||||||
|  |         String listResReceiver = cordaClient.listGameProposals( | ||||||
|  |             holdingIdentityResolver.getByUsername(gpReceiver)); | ||||||
|  | 
 | ||||||
|  |         assertThat(listResSender).contains(gpUuid); | ||||||
|  |         assertThat(listResReceiver).contains(gpUuid); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void testGemeProposalReject() { | ||||||
|  |         final String gpSender = "alice"; | ||||||
|  |         final String gpReceiver = "bob"; | ||||||
|  |         final Color gpReceiverColor = Color.WHITE; | ||||||
|  |         final String gpMessage = "GameProposal create test"; | ||||||
|  | 
 | ||||||
|  |         final String gpUuid = cordaClient.createGameProposal( | ||||||
|             holdingIdentityResolver.getByUsername(gpSender), |             holdingIdentityResolver.getByUsername(gpSender), | ||||||
|             holdingIdentityResolver.getByUsername(gpReceiver), |             holdingIdentityResolver.getByUsername(gpReceiver), | ||||||
|             gpReceiverColor, |             gpReceiverColor, | ||||||
| @ -92,45 +80,20 @@ public class CordaClientTest { | |||||||
| 
 | 
 | ||||||
|         System.out.println("Create GP UUID "+ gpUuid); |         System.out.println("Create GP UUID "+ gpUuid); | ||||||
| 
 | 
 | ||||||
|         assertThatThrownBy(() -> { |         String listResSender = cordaClient.listGameProposals( | ||||||
|             cordaClient.gameProposalAction( |             holdingIdentityResolver.getByUsername(gpSender)); | ||||||
|                 holdingIdentityResolver.getByUsername(gpSender), |  | ||||||
|                 gpUuid, |  | ||||||
|                 Action.REJECT); |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         final String rejectRes = cordaClient.gameProposalAction( |         String listResReceiver = cordaClient.listGameProposals( | ||||||
|  |             holdingIdentityResolver.getByUsername(gpReceiver)); | ||||||
|  | 
 | ||||||
|  |         assertThat(listResSender).contains(gpUuid); | ||||||
|  |         assertThat(listResReceiver).contains(gpUuid); | ||||||
|  | 
 | ||||||
|  |         final String rejectRes = cordaClient.rejectGameProposal( | ||||||
|             holdingIdentityResolver.getByUsername(gpReceiver), |             holdingIdentityResolver.getByUsername(gpReceiver), | ||||||
|             gpUuid, |             "1ed70601-c79a-486d-b907-8537f317083a" | ||||||
|             Action.REJECT |  | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         assertThat(rejectRes).isEqualToIgnoringCase("REJECTED"); |         assertThat(rejectRes).isEqualToIgnoringCase("REJECTED"); | ||||||
| 
 |  | ||||||
|         List<GameProposal> gpListSender = cordaClient.gameProposalList( |  | ||||||
|             holdingIdentityResolver.getByUsername(gpSender)); |  | ||||||
| 
 |  | ||||||
|         assertThat(findByUuid(gpListSender, gpUuid)).isNull(); |  | ||||||
| 
 |  | ||||||
|         List<GameProposal> gpListReceiver = cordaClient.gameProposalList( |  | ||||||
|             holdingIdentityResolver.getByUsername(gpReceiver)); |  | ||||||
| 
 |  | ||||||
|         assertThat(findByUuid(gpListReceiver, gpUuid)).isNull(); |  | ||||||
| 
 |  | ||||||
|         // GameProposal can not be rejected twice  |  | ||||||
|         assertThatThrownBy(() -> { |  | ||||||
|             cordaClient.gameProposalAction( |  | ||||||
|                 holdingIdentityResolver.getByUsername(gpSender), |  | ||||||
|                 gpUuid, |  | ||||||
|                 Action.REJECT); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private GameProposal findByUuid(List<GameProposal> gpList, UUID uuid) { |  | ||||||
|         for (GameProposal gameProposal : gpList) { |  | ||||||
|             if (gameProposal.id().compareTo(uuid) == 0) |  | ||||||
|                 return gameProposal; |  | ||||||
|         }; |  | ||||||
|         return null; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,96 +3,66 @@ package djmil.cordacheckers.contracts; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| //import djmil.cordacheckers.states.GameProposalResolutionState; | import djmil.cordacheckers.states.GameProposalResolutionState; | ||||||
| import djmil.cordacheckers.states.GameProposalState; | import djmil.cordacheckers.states.GameProposalState; | ||||||
| import net.corda.v5.base.exceptions.CordaRuntimeException; | import net.corda.v5.base.exceptions.CordaRuntimeException; | ||||||
| import net.corda.v5.base.types.MemberX500Name; |  | ||||||
| import net.corda.v5.ledger.utxo.Command; | import net.corda.v5.ledger.utxo.Command; | ||||||
| import net.corda.v5.ledger.utxo.StateAndRef; |  | ||||||
| import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; | ||||||
| 
 | 
 | ||||||
| public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { | public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { | ||||||
| 
 | 
 | ||||||
|     private final static Logger log = LoggerFactory.getLogger(GameProposalContract.class); |     private final static Logger log = LoggerFactory.getLogger(GameProposalContract.class); | ||||||
| 
 | 
 | ||||||
|     public static enum Action implements Command {  |     public static class Create implements Command { } | ||||||
|         CREATE, |     public static class Accept implements Command { } | ||||||
|         ACCEPT, |     public static class Reject implements Command { } | ||||||
|         REJECT, |     public static class Cancel implements Command { } | ||||||
|         CANCEL; |  | ||||||
| 
 |  | ||||||
|         public MemberX500Name getInitiator(GameProposalState gameProposalState) { |  | ||||||
|             switch (this) { |  | ||||||
|                 case CREATE: |  | ||||||
|                 case CANCEL: |  | ||||||
|                     return gameProposalState.getIssuer(); |  | ||||||
|                 case ACCEPT: |  | ||||||
|                 case REJECT: |  | ||||||
|                     return gameProposalState.getAcquier(); |  | ||||||
|                 default: |  | ||||||
|                     throw new RuntimeException(UNSUPPORTED_ACTION_VALUE_OF + this.name()); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public MemberX500Name getRespondent(GameProposalState gameProposalState) { |  | ||||||
|             switch (this) { |  | ||||||
|                 case CREATE: |  | ||||||
|                 case CANCEL: |  | ||||||
|                     return gameProposalState.getAcquier(); |  | ||||||
|                 case ACCEPT: |  | ||||||
|                 case REJECT: |  | ||||||
|                     return gameProposalState.getIssuer(); |  | ||||||
|                 default: |  | ||||||
|                     throw new RuntimeException(UNSUPPORTED_ACTION_VALUE_OF + this.name()); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public MemberX500Name getSender(StateAndRef<GameProposalState> utxoGameProposal) { |  | ||||||
|             return getInitiator(utxoGameProposal.getState().getContractState()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public MemberX500Name getReceiver(StateAndRef<GameProposalState> utxoGameProposal) { |  | ||||||
|             return getRespondent(utxoGameProposal.getState().getContractState()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static final String UNSUPPORTED_ACTION_VALUE_OF = "Unsupported Action value: "; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void verify(UtxoLedgerTransaction trx) { |     public void verify(UtxoLedgerTransaction trx) { | ||||||
|         log.info("GameProposalContract.verify() called"); |         log.info("GameProposalContract.verify() called"); | ||||||
| 
 | 
 | ||||||
|         requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); |         requireThat(trx.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); | ||||||
|  |         Command command = trx.getCommands().get(0); | ||||||
| 
 | 
 | ||||||
|         switch ((Action)(trx.getCommands().get(0))) { |         if (command instanceof Create) { | ||||||
|             case CREATE: { |             requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); | ||||||
|                 requireThat(trx.getInputContractStates().isEmpty(), CREATE_INPUT_STATE); |             requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE); | ||||||
|                 requireThat(trx.getOutputContractStates().size() == 1, CREATE_OUTPUT_STATE); |  | ||||||
|              |              | ||||||
|                 GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0); |             GameProposalState outputState = trx.getOutputStates(GameProposalState.class).get(0); | ||||||
|                 requireThat(outputState != null, CREATE_OUTPUT_STATE); |             requireThat(outputState != null, CREATE_OUTPUT_STATE); | ||||||
| 
 | 
 | ||||||
|                 requireThat(outputState.getRecipientColor() != null, NON_NULL_RECIPIENT_COLOR); |             requireThat(outputState.getRecipientColor() != null, NON_NULL_RECIPIENT_COLOR); | ||||||
|                 break; } |         } else  | ||||||
|  |         if (command instanceof Accept) { | ||||||
|  |             // TODO outputState -> Game | ||||||
|  |             throw new CordaRuntimeException("Unimplemented!"); | ||||||
|  |         } else  | ||||||
|  |         if (command instanceof Reject) { | ||||||
|  |             requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE); | ||||||
|  |             requireThat(trx.getOutputContractStates().size() == 1, REJECT_OUTPUT_STATE); | ||||||
| 
 | 
 | ||||||
|             case ACCEPT: |             GameProposalState inputState = trx.getInputStates(GameProposalState.class).get(0); | ||||||
|                 throw new CordaRuntimeException("Unimplemented!"); |             requireThat(inputState != null, REJECT_INPUT_STATE); | ||||||
|                 //break;  |  | ||||||
|              |              | ||||||
|             case REJECT: |             GameProposalResolutionState outputState = trx.getOutputStates(GameProposalResolutionState.class).get(0); | ||||||
|                 requireThat(trx.getInputContractStates().size() == 1, REJECT_INPUT_STATE); |             requireThat(outputState != null, REJECT_OUTPUT_STATE); | ||||||
|                 requireThat(trx.getOutputContractStates().isEmpty(), REJECT_OUTPUT_STATE); |  | ||||||
|                 requireThat(trx.getInputStates(GameProposalState.class).get(0) != null, REJECT_INPUT_STATE); |  | ||||||
|                 break; |  | ||||||
| 
 | 
 | ||||||
|             case CANCEL: |             requireThat(outputState.outcome == GameProposalResolutionState.Resolution.REJECT, REJECT_OUTPUT_OUTCOME); | ||||||
|                 requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE); |         } else  | ||||||
|                 requireThat(trx.getOutputContractStates().isEmpty(), CANCEL_OUTPUT_STATE); |         if (command instanceof Cancel) { | ||||||
|                 requireThat(trx.getInputStates(GameProposalState.class).get(0) != null, CANCEL_INPUT_STATE); |             requireThat(trx.getInputContractStates().size() == 1, CANCEL_INPUT_STATE); | ||||||
|                 break; |             requireThat(trx.getOutputContractStates().size() == 1, CANCEL_OUTPUT_STATE); | ||||||
| 
 | 
 | ||||||
|             default: |             GameProposalState inputState = trx.getInputStates(GameProposalState.class).get(0); | ||||||
|                 throw new CordaRuntimeException(UNKNOWN_COMMAND); |             requireThat(inputState != null, CANCEL_INPUT_STATE); | ||||||
|  |              | ||||||
|  |             GameProposalResolutionState outputState = trx.getOutputStates(GameProposalResolutionState.class).get(0); | ||||||
|  |             requireThat(outputState != null, CANCEL_OUTPUT_STATE); | ||||||
|  | 
 | ||||||
|  |             requireThat(outputState.outcome == GameProposalResolutionState.Resolution.CANCEL, CANCEL_OUTPUT_OUTCOME); | ||||||
|  |         } else { | ||||||
|  |             throw new CordaRuntimeException(UNKNOWN_COMMAND); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
| @ -110,8 +80,10 @@ public class GameProposalContract implements net.corda.v5.ledger.utxo.Contract { | |||||||
|     static final String NON_NULL_RECIPIENT_COLOR = "GameProposal.recipientColor field can not be null"; |     static final String NON_NULL_RECIPIENT_COLOR = "GameProposal.recipientColor field can not be null"; | ||||||
| 
 | 
 | ||||||
|     static final String REJECT_INPUT_STATE  = "Reject command should have exactly one GameProposal input state"; |     static final String REJECT_INPUT_STATE  = "Reject command should have exactly one GameProposal input state"; | ||||||
|     static final String REJECT_OUTPUT_STATE = "Reject command should have no output states"; |     static final String REJECT_OUTPUT_STATE = "Reject command should have exactly one GameProposalResolution output states"; | ||||||
|  |     static final String REJECT_OUTPUT_OUTCOME = "Reject output state should have Resolution value set to REJECT"; | ||||||
| 
 | 
 | ||||||
|     static final String CANCEL_INPUT_STATE  = "Cancel command should have exactly one GameProposal input state"; |     static final String CANCEL_INPUT_STATE  = "Cancel command should have exactly one GameProposal input state"; | ||||||
|     static final String CANCEL_OUTPUT_STATE = "Cancel command should have no output states"; |     static final String CANCEL_OUTPUT_STATE = "Cancel command should have exactly one GameProposalResolution output states"; | ||||||
|  |     static final String CANCEL_OUTPUT_OUTCOME = "Cancel output state should have Resolution value set to CANCEL"; | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,63 @@ | |||||||
|  | package djmil.cordacheckers.states; | ||||||
|  | 
 | ||||||
|  | import java.security.PublicKey; | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
|  | import djmil.cordacheckers.contracts.GameProposalContract; | ||||||
|  | import net.corda.v5.base.annotations.ConstructorForDeserialization; | ||||||
|  | import net.corda.v5.base.annotations.CordaSerializable; | ||||||
|  | import net.corda.v5.base.types.MemberX500Name; | ||||||
|  | import net.corda.v5.ledger.utxo.BelongsToContract; | ||||||
|  | import net.corda.v5.ledger.utxo.ContractState; | ||||||
|  | 
 | ||||||
|  | @BelongsToContract(GameProposalContract.class) | ||||||
|  | public class GameProposalResolutionState implements ContractState { | ||||||
|  | 
 | ||||||
|  |     @CordaSerializable | ||||||
|  |     public enum Resolution { | ||||||
|  |         ACCEPT, | ||||||
|  |         REJECT, | ||||||
|  |         CANCEL | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public final Resolution outcome; | ||||||
|  |     public final List<PublicKey> participants; | ||||||
|  | 
 | ||||||
|  |     public GameProposalResolutionState( | ||||||
|  |         Resolution outcome, | ||||||
|  |         GameProposalState gameProposal | ||||||
|  |     ) { | ||||||
|  |         this.outcome = outcome; | ||||||
|  |         this.participants = gameProposal.getParticipants(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @ConstructorForDeserialization | ||||||
|  |     public GameProposalResolutionState( | ||||||
|  |         Resolution outcome, | ||||||
|  |         List<PublicKey> participants | ||||||
|  |     ) { | ||||||
|  |         this.outcome = outcome; | ||||||
|  |         this.participants = participants; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public Resolution getOutcome() { | ||||||
|  |         return outcome; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public List<PublicKey> getParticipants() { | ||||||
|  |         return this.participants; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public MemberX500Name getRecipient(GameProposalState gameProposal) { | ||||||
|  |         switch (outcome) { | ||||||
|  |             case ACCEPT: | ||||||
|  |             case REJECT: | ||||||
|  |                 return gameProposal.getSender(); | ||||||
|  |             case CANCEL: | ||||||
|  |                 return gameProposal.getRecipient(); | ||||||
|  |             default: | ||||||
|  |                 throw new RuntimeException("Unknown Resolution value: "+outcome.toString()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @ -6,6 +6,7 @@ import java.util.UUID; | |||||||
| 
 | 
 | ||||||
| import djmil.cordacheckers.contracts.GameProposalContract; | import djmil.cordacheckers.contracts.GameProposalContract; | ||||||
| import net.corda.v5.base.annotations.ConstructorForDeserialization; | import net.corda.v5.base.annotations.ConstructorForDeserialization; | ||||||
|  | import net.corda.v5.base.annotations.CordaSerializable; | ||||||
| import net.corda.v5.base.types.MemberX500Name; | import net.corda.v5.base.types.MemberX500Name; | ||||||
| import net.corda.v5.ledger.utxo.BelongsToContract; | import net.corda.v5.ledger.utxo.BelongsToContract; | ||||||
| import net.corda.v5.ledger.utxo.ContractState; | import net.corda.v5.ledger.utxo.ContractState; | ||||||
| @ -13,40 +14,46 @@ import net.corda.v5.ledger.utxo.ContractState; | |||||||
| @BelongsToContract(GameProposalContract.class) | @BelongsToContract(GameProposalContract.class) | ||||||
| public class GameProposalState implements ContractState { | public class GameProposalState implements ContractState { | ||||||
| 
 | 
 | ||||||
|     MemberX500Name issuer; |     @CordaSerializable | ||||||
|     MemberX500Name acquier; |     public enum Color { | ||||||
|     Piece.Color recipientColor; |         WHITE, | ||||||
|     String message; |         BLACK, | ||||||
|     UUID id; |     } | ||||||
|     List<PublicKey> participants; |        | ||||||
|  |     public final MemberX500Name sender; | ||||||
|  |     public final MemberX500Name recipient; | ||||||
|  |     public final Color recipientColor; | ||||||
|  |     public final String message; | ||||||
|  |     public final UUID id; | ||||||
|  |     public final List<PublicKey> participants; | ||||||
| 
 | 
 | ||||||
|     // Allows serialisation and to use a specified UUID |     // Allows serialisation and to use a specified UUID | ||||||
|     @ConstructorForDeserialization |     @ConstructorForDeserialization | ||||||
|     public GameProposalState( |     public GameProposalState( | ||||||
|         MemberX500Name issuer, |         MemberX500Name sender, | ||||||
|         MemberX500Name acquier, |         MemberX500Name recipient, | ||||||
|         Piece.Color recipientColor, |         Color recipientColor, | ||||||
|         String message, |         String message, | ||||||
|         UUID id, |         UUID id, | ||||||
|         List<PublicKey> participants |         List<PublicKey> participants | ||||||
|     ) { |     ) { | ||||||
|         this.issuer = issuer; |         this.sender = sender; | ||||||
|         this.acquier = acquier; |         this.recipient = recipient; | ||||||
|         this.recipientColor = recipientColor; |         this.recipientColor = recipientColor; | ||||||
|         this.message = message; |         this.message = message; | ||||||
|         this.id = id; |         this.id = id; | ||||||
|         this.participants = participants; |         this.participants = participants; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public MemberX500Name getIssuer() { |     public MemberX500Name getSender() { | ||||||
|         return issuer; |         return sender; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public MemberX500Name getAcquier() { |     public MemberX500Name getRecipient() { | ||||||
|         return acquier; |         return recipient; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Piece.Color getRecipientColor() { |     public Color getRecipientColor() { | ||||||
|         return recipientColor; |         return recipientColor; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,24 +0,0 @@ | |||||||
| package djmil.cordacheckers.states; |  | ||||||
| 
 |  | ||||||
| import net.corda.v5.base.annotations.CordaSerializable; |  | ||||||
| 
 |  | ||||||
| @CordaSerializable |  | ||||||
| public class Piece { |  | ||||||
|      |  | ||||||
|     @CordaSerializable |  | ||||||
|     public enum Type { |  | ||||||
|         MAN, |  | ||||||
|         KING, |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @CordaSerializable |  | ||||||
|     public enum Color { |  | ||||||
|         WHITE, |  | ||||||
|         BLACK, |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Color color; |  | ||||||
|     Type type; |  | ||||||
| 
 |  | ||||||
|      |  | ||||||
| } |  | ||||||
| @ -1,28 +1,18 @@ | |||||||
| package djmil.cordacheckers; | package djmil.cordacheckers; | ||||||
| 
 | 
 | ||||||
| import net.corda.v5.application.marshalling.JsonMarshallingService; | import net.corda.v5.application.marshalling.JsonMarshallingService; | ||||||
| import net.corda.v5.crypto.SecureHash; |  | ||||||
| 
 | 
 | ||||||
| public class FlowResult { | public class FlowResult { | ||||||
|     public final Object successStatus; |     public final Object successStatus; | ||||||
|     public final String transactionId; |  | ||||||
|     public final String failureStatus; |     public final String failureStatus; | ||||||
| 
 | 
 | ||||||
|     public FlowResult(Object success) { |     public FlowResult(Object success) { | ||||||
|         this.successStatus = success; |         this.successStatus = success; | ||||||
|         this.transactionId = null; |  | ||||||
|         this.failureStatus = null; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public FlowResult(Object success, SecureHash transactionId) { |  | ||||||
|         this.successStatus = success; |  | ||||||
|         this.transactionId = transactionId.toString(); |  | ||||||
|         this.failureStatus = null; |         this.failureStatus = null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public FlowResult(Exception exception) { |     public FlowResult(Exception exception) { | ||||||
|         this.successStatus = null; |         this.successStatus = null; | ||||||
|         this.transactionId = null; |  | ||||||
|         this.failureStatus = exception.getMessage(); |         this.failureStatus = exception.getMessage(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| package djmil.cordacheckers.gameproposal; | package djmil.cordacheckers.gameproposal; | ||||||
| 
 | 
 | ||||||
|  | import java.util.Arrays; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| 
 | 
 | ||||||
| @ -8,15 +10,21 @@ import org.slf4j.Logger; | |||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import djmil.cordacheckers.FlowResult; | import djmil.cordacheckers.FlowResult; | ||||||
|  | import djmil.cordacheckers.contracts.GameProposalContract; | ||||||
|  | import djmil.cordacheckers.states.GameProposalResolutionState; | ||||||
| import djmil.cordacheckers.states.GameProposalState; | import djmil.cordacheckers.states.GameProposalState; | ||||||
| import net.corda.v5.application.flows.ClientRequestBody; | import net.corda.v5.application.flows.ClientRequestBody; | ||||||
| import net.corda.v5.application.flows.ClientStartableFlow; | import net.corda.v5.application.flows.ClientStartableFlow; | ||||||
| import net.corda.v5.application.flows.CordaInject; | import net.corda.v5.application.flows.CordaInject; | ||||||
| import net.corda.v5.application.flows.FlowEngine; | import net.corda.v5.application.flows.InitiatingFlow; | ||||||
| import net.corda.v5.application.marshalling.JsonMarshallingService; | import net.corda.v5.application.marshalling.JsonMarshallingService; | ||||||
|  | import net.corda.v5.application.membership.MemberLookup; | ||||||
|  | import net.corda.v5.application.messaging.FlowMessaging; | ||||||
|  | import net.corda.v5.application.messaging.FlowSession; | ||||||
| import net.corda.v5.base.annotations.Suspendable; | import net.corda.v5.base.annotations.Suspendable; | ||||||
| import net.corda.v5.base.exceptions.CordaRuntimeException; | import net.corda.v5.base.exceptions.CordaRuntimeException; | ||||||
| import net.corda.v5.crypto.SecureHash; | import net.corda.v5.base.types.MemberX500Name; | ||||||
|  | import net.corda.v5.ledger.utxo.Command; | ||||||
| import net.corda.v5.ledger.utxo.StateAndRef; | import net.corda.v5.ledger.utxo.StateAndRef; | ||||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; | import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||||
| import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | ||||||
| @ -25,8 +33,7 @@ import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder; | |||||||
| import java.time.Duration; | import java.time.Duration; | ||||||
| import java.time.Instant; | import java.time.Instant; | ||||||
| 
 | 
 | ||||||
| import static djmil.cordacheckers.contracts.GameProposalContract.Action; | @InitiatingFlow(protocol = "game-proposal-action") | ||||||
| 
 |  | ||||||
| public class ActionFlow implements ClientStartableFlow { | public class ActionFlow implements ClientStartableFlow { | ||||||
| 
 | 
 | ||||||
|     private final static Logger log = LoggerFactory.getLogger(CreateFlow.class); |     private final static Logger log = LoggerFactory.getLogger(CreateFlow.class); | ||||||
| @ -38,24 +45,33 @@ public class ActionFlow implements ClientStartableFlow { | |||||||
|     public UtxoLedgerService ledgerService; |     public UtxoLedgerService ledgerService; | ||||||
| 
 | 
 | ||||||
|     @CordaInject |     @CordaInject | ||||||
|     public FlowEngine flowEngine; |     public FlowMessaging flowMessaging; | ||||||
|  | 
 | ||||||
|  |     @CordaInject | ||||||
|  |     public MemberLookup memberLookup; | ||||||
|  | 
 | ||||||
|  |     private final static Map<GameProposalResolutionState.Resolution, Command> resoultion2command = Map.ofEntries( | ||||||
|  |         Map.entry(GameProposalResolutionState.Resolution.CANCEL, new GameProposalContract.Cancel()), | ||||||
|  |         Map.entry(GameProposalResolutionState.Resolution.REJECT, new GameProposalContract.Reject()), | ||||||
|  |         Map.entry(GameProposalResolutionState.Resolution.ACCEPT, new GameProposalContract.Accept()) | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     @Suspendable |     @Suspendable | ||||||
|     public String call(ClientRequestBody requestBody) { |     public String call(ClientRequestBody requestBody) { | ||||||
|         try { |         try { | ||||||
|             final ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class); |             ActionFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, ActionFlowArgs.class); | ||||||
|             final Action action = args.getAction(); |  | ||||||
| 
 | 
 | ||||||
|             final StateAndRef<GameProposalState> inputState = findUnconsumedGameProposalState(args.getGameProposalUuid()); |             StateAndRef<GameProposalState> inputState = findUnconsumedGameProposalState(args.getGameProposalUuid()); | ||||||
|          |          | ||||||
|             final UtxoSignedTransaction trx = prepareSignedTransaction(action, inputState); |             GameProposalResolutionState outputState = new GameProposalResolutionState( | ||||||
|  |                 args.getAction(), | ||||||
|  |                 inputState.getState().getContractState() | ||||||
|  |                 ); | ||||||
| 
 | 
 | ||||||
|             final SecureHash trxId = this.flowEngine |             String trxResult = doTrunsaction(inputState, outputState); | ||||||
|                 .subFlow( new CommitSubFlow(trx, action.getReceiver(inputState)) ); |  | ||||||
| 
 | 
 | ||||||
|             return new FlowResult(action+"ED", trxId)  // REJECT+ED |             return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService); | ||||||
|                 .toJsonEncodedString(jsonMarshallingService); |  | ||||||
|         }  |         }  | ||||||
|         catch (Exception e) { |         catch (Exception e) { | ||||||
|             log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +  |             log.warn("GameProposalAction flow failed to process utxo request body " + requestBody +  | ||||||
| @ -67,7 +83,7 @@ public class ActionFlow implements ClientStartableFlow { | |||||||
|     @Suspendable |     @Suspendable | ||||||
|     private StateAndRef<GameProposalState> findUnconsumedGameProposalState (UUID gameProposalUuid) { |     private StateAndRef<GameProposalState> findUnconsumedGameProposalState (UUID gameProposalUuid) { | ||||||
|         /* |         /* | ||||||
|          * Get list of all unconsumed aka 'active' GameProposalStates, then filter by UUID. |          * Get list of all unconsumed aka 'actuve' GameProposalStates, then filter by UUID. | ||||||
|          * Note, this is an inefficient way to perform this operation if there are a large |          * Note, this is an inefficient way to perform this operation if there are a large | ||||||
|          * number of 'active' GameProposals exists in storage. |          * number of 'active' GameProposals exists in storage. | ||||||
|          */ |          */ | ||||||
| @ -86,24 +102,26 @@ public class ActionFlow implements ClientStartableFlow { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Suspendable |     @Suspendable | ||||||
|     private UtxoSignedTransaction prepareSignedTransaction( |     private String doTrunsaction(StateAndRef<GameProposalState> inputState, GameProposalResolutionState outputState) { | ||||||
|         Action action, |         UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder() | ||||||
|         StateAndRef<GameProposalState> inputState |  | ||||||
|     ) { |  | ||||||
|         UtxoTransactionBuilder trxBuilder = ledgerService.createTransactionBuilder() |  | ||||||
|             .setNotary(inputState.getState().getNotaryName()) |             .setNotary(inputState.getState().getNotaryName()) | ||||||
|             .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) |             .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) | ||||||
|             .addInputState(inputState.getRef()) |             .addInputState(inputState.getRef()) | ||||||
|             .addCommand(action) |             .addOutputState(outputState) | ||||||
|             .addSignatories(inputState.getState().getContractState().getParticipants()); |             .addCommand(resoultion2command.get(outputState.outcome)) | ||||||
|  |             .addSignatories(outputState.getParticipants()); | ||||||
| 
 | 
 | ||||||
|         if (action == Action.ACCEPT) { |         UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(); | ||||||
|             // TODO |  | ||||||
|             // .addOutputState(outputState) |  | ||||||
|             throw new RuntimeException(action +" unimplemented"); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         return trxBuilder.toSignedTransaction(); |         FlowSession session = flowMessaging.initiateFlow( | ||||||
|  |             outputState.getRecipient(inputState.getState().getContractState()) | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |         List<FlowSession> sessionsList = Arrays.asList(session); | ||||||
|  | 
 | ||||||
|  |         ledgerService.finalize(signedTransaction, sessionsList); | ||||||
|  | 
 | ||||||
|  |         return outputState.getOutcome()+"ED"; // REJECT+ED | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,7 +2,8 @@ package djmil.cordacheckers.gameproposal; | |||||||
| 
 | 
 | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
| 
 | 
 | ||||||
| import djmil.cordacheckers.contracts.GameProposalContract; | import djmil.cordacheckers.states.GameProposalResolutionState; | ||||||
|  | import djmil.cordacheckers.states.GameProposalResolutionState.Resolution; | ||||||
| 
 | 
 | ||||||
| public class ActionFlowArgs { | public class ActionFlowArgs { | ||||||
|     private UUID gameProposalUuid; |     private UUID gameProposalUuid; | ||||||
| @ -14,8 +15,8 @@ public class ActionFlowArgs { | |||||||
|         this.action = null; |         this.action = null; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     public GameProposalContract.Action getAction() { |     public Resolution getAction() { | ||||||
|         return GameProposalContract.Action.valueOf(this.action); |         return GameProposalResolutionState.Resolution.valueOf(this.action); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public UUID getGameProposalUuid() { |     public UUID getGameProposalUuid() { | ||||||
|  | |||||||
| @ -0,0 +1,83 @@ | |||||||
|  | package djmil.cordacheckers.gameproposal; | ||||||
|  | 
 | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | 
 | ||||||
|  | import djmil.cordacheckers.contracts.GameProposalContract.Accept; | ||||||
|  | import djmil.cordacheckers.contracts.GameProposalContract.Cancel; | ||||||
|  | import djmil.cordacheckers.contracts.GameProposalContract.Reject; | ||||||
|  | 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.Command; | ||||||
|  | import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||||
|  | import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | ||||||
|  | import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; | ||||||
|  | 
 | ||||||
|  | @InitiatedBy(protocol = "game-proposal-action") | ||||||
|  | public class ActionResponder implements ResponderFlow { | ||||||
|  |     private final static Logger log = LoggerFactory.getLogger(CreateResponder.class); | ||||||
|  | 
 | ||||||
|  |     @CordaInject | ||||||
|  |     public MemberLookup memberLookup; | ||||||
|  | 
 | ||||||
|  |     @CordaInject | ||||||
|  |     public UtxoLedgerService utxoLedgerService; | ||||||
|  |      | ||||||
|  |     @Suspendable | ||||||
|  |     @Override | ||||||
|  |     public void call(FlowSession session) { | ||||||
|  |         try { | ||||||
|  |             UtxoTransactionValidator txValidator = ledgerTransaction -> { | ||||||
|  |                 GameProposalState gameProposal = (GameProposalState) ledgerTransaction.getInputContractStates().get(0); | ||||||
|  | 
 | ||||||
|  |                 Command command = ledgerTransaction.getCommands(Command.class).get(0); | ||||||
|  | 
 | ||||||
|  |                 if (!checkParticipants(gameProposal, session.getCounterparty(), command)) { | ||||||
|  |                     throw new CordaRuntimeException("Failed verification"); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 log.info("Verified the transaction - " + ledgerTransaction.getId()); | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             // Calls receiveFinality() function which provides the responder to the finalise() function | ||||||
|  |             // in the Initiating Flow. Accepts a lambda validator containing the business logic to decide whether | ||||||
|  |             // responder should sign the Transaction. | ||||||
|  |             UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService | ||||||
|  |                     .receiveFinality(session, txValidator) | ||||||
|  |                     .getTransaction(); | ||||||
|  | 
 | ||||||
|  |             log.info("Finished responder flow - " + finalizedSignedTransaction.getId()); | ||||||
|  |         } | ||||||
|  |         // Soft fails the flow and log the exception. | ||||||
|  |         catch(Exception e) | ||||||
|  |         { | ||||||
|  |             log.warn("Exceptionally finished responder flow", e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Suspendable | ||||||
|  |     Boolean checkParticipants(GameProposalState gameProposal, MemberX500Name counterpartyName, Command command) { | ||||||
|  |         MemberX500Name myName = memberLookup.myInfo().getName(); | ||||||
|  | 
 | ||||||
|  |         if (command instanceof Reject || command instanceof Accept) { | ||||||
|  |             if (gameProposal.getRecipient().compareTo(counterpartyName) == 0 &&  | ||||||
|  |                 gameProposal.getSender().compareTo(myName) == 0) | ||||||
|  |                 return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (command instanceof Cancel) { | ||||||
|  |             if (gameProposal.getRecipient().compareTo(myName) == 0 &&  | ||||||
|  |                 gameProposal.getSender().compareTo(counterpartyName) == 0) | ||||||
|  |                 return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,95 +0,0 @@ | |||||||
| package djmil.cordacheckers.gameproposal; |  | ||||||
| 
 |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
| 
 |  | ||||||
| import djmil.cordacheckers.states.GameProposalState; |  | ||||||
| import net.corda.v5.application.flows.CordaInject; |  | ||||||
| import net.corda.v5.application.flows.InitiatedBy; |  | ||||||
| import net.corda.v5.application.flows.ResponderFlow; |  | ||||||
| import net.corda.v5.application.membership.MemberLookup; |  | ||||||
| import net.corda.v5.application.messaging.FlowSession; |  | ||||||
| import net.corda.v5.base.annotations.Suspendable; |  | ||||||
| import net.corda.v5.base.exceptions.CordaRuntimeException; |  | ||||||
| import net.corda.v5.base.types.MemberX500Name; |  | ||||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; |  | ||||||
| import net.corda.v5.ledger.utxo.transaction.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 { |  | ||||||
|     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 Action action = ledgerTransaction.getCommands(Action.class).get(0); |  | ||||||
| 
 |  | ||||||
|                 final GameProposalState gameProposal = getGameProposal(ledgerTransaction);  |  | ||||||
| 
 |  | ||||||
|                 checkSessionParticipants(session, gameProposal, action); |  | ||||||
| 
 |  | ||||||
|                 /* |  | ||||||
|                  * 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 checkSessionParticipants( |  | ||||||
|         FlowSession session, |  | ||||||
|         GameProposalState gameProposal, |  | ||||||
|         Action action |  | ||||||
|     ) { |  | ||||||
|         final MemberX500Name myName = memberLookup.myInfo().getName(); |  | ||||||
|         final MemberX500Name otherName = session.getCounterparty(); |  | ||||||
| 
 |  | ||||||
|         if (action.getRespondent(gameProposal).compareTo(myName) != 0) |  | ||||||
|             throw new CordaRuntimeException("Bad GameProposal acquirer: expected '"+myName+"', actual '" +gameProposal.getAcquier() +"'"); |  | ||||||
|          |  | ||||||
|         if (action.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); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,60 +0,0 @@ | |||||||
| package djmil.cordacheckers.gameproposal; |  | ||||||
| 
 |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.List; |  | ||||||
| 
 |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
| 
 |  | ||||||
| import net.corda.v5.application.flows.CordaInject; |  | ||||||
| import net.corda.v5.application.flows.InitiatingFlow; |  | ||||||
| import net.corda.v5.application.flows.SubFlow; |  | ||||||
| import net.corda.v5.application.messaging.FlowMessaging; |  | ||||||
| import net.corda.v5.application.messaging.FlowSession; |  | ||||||
| import net.corda.v5.base.annotations.Suspendable; |  | ||||||
| import net.corda.v5.base.types.MemberX500Name; |  | ||||||
| import net.corda.v5.crypto.SecureHash; |  | ||||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; |  | ||||||
| import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; |  | ||||||
| 
 |  | ||||||
| @InitiatingFlow(protocol = "game-proposal") |  | ||||||
| public class CommitSubFlow implements SubFlow<SecureHash> { |  | ||||||
| 
 |  | ||||||
|     private final static Logger log = LoggerFactory.getLogger(CommitSubFlow.class); |  | ||||||
|     private final UtxoSignedTransaction signedTransaction; |  | ||||||
|     private final MemberX500Name respondenName; |  | ||||||
| 
 |  | ||||||
|     public CommitSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name respondenName) { |  | ||||||
|         this.signedTransaction = signedTransaction; |  | ||||||
|         this.respondenName = respondenName; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @CordaInject |  | ||||||
|     public UtxoLedgerService ledgerService; |  | ||||||
| 
 |  | ||||||
|     @CordaInject |  | ||||||
|     public FlowMessaging flowMessaging; |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     @Suspendable |  | ||||||
|     public SecureHash call() { |  | ||||||
|         log.info("GamePropsal commit started"); |  | ||||||
| 
 |  | ||||||
|         final FlowSession session = flowMessaging.initiateFlow(this.respondenName); |  | ||||||
| 
 |  | ||||||
|         /* |  | ||||||
|          * Calls the Corda provided finalise() function which gather signatures from the counterparty, |  | ||||||
|          * notarises the transaction and persists the transaction to each party's vault. |  | ||||||
|          */ |  | ||||||
| 
 |  | ||||||
|         final List<FlowSession> sessionsList = Arrays.asList(session); |  | ||||||
| 
 |  | ||||||
|         final SecureHash trxId = ledgerService |  | ||||||
|             .finalize(this.signedTransaction, sessionsList) |  | ||||||
|             .getTransaction() |  | ||||||
|             .getId(); |  | ||||||
| 
 |  | ||||||
|         log.info("GamePropsal commit " +trxId); |  | ||||||
|         return trxId; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -4,8 +4,8 @@ import org.slf4j.Logger; | |||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import djmil.cordacheckers.FlowResult; | import djmil.cordacheckers.FlowResult; | ||||||
|  | import djmil.cordacheckers.contracts.GameProposalContract; | ||||||
| import djmil.cordacheckers.states.GameProposalState; | import djmil.cordacheckers.states.GameProposalState; | ||||||
| import djmil.cordacheckers.states.Piece; |  | ||||||
| import net.corda.v5.application.flows.ClientRequestBody; | import net.corda.v5.application.flows.ClientRequestBody; | ||||||
| import net.corda.v5.application.flows.ClientStartableFlow; | import net.corda.v5.application.flows.ClientStartableFlow; | ||||||
| import net.corda.v5.application.flows.CordaInject; | import net.corda.v5.application.flows.CordaInject; | ||||||
| @ -13,23 +13,26 @@ import net.corda.v5.application.flows.FlowEngine; | |||||||
| import net.corda.v5.application.flows.InitiatingFlow; | import net.corda.v5.application.flows.InitiatingFlow; | ||||||
| import net.corda.v5.application.marshalling.JsonMarshallingService; | import net.corda.v5.application.marshalling.JsonMarshallingService; | ||||||
| import net.corda.v5.application.membership.MemberLookup; | import net.corda.v5.application.membership.MemberLookup; | ||||||
|  | import net.corda.v5.application.messaging.FlowMessaging; | ||||||
|  | import net.corda.v5.application.messaging.FlowSession; | ||||||
| import net.corda.v5.base.annotations.Suspendable; | import net.corda.v5.base.annotations.Suspendable; | ||||||
| import net.corda.v5.base.types.MemberX500Name; | import net.corda.v5.base.types.MemberX500Name; | ||||||
| import net.corda.v5.crypto.SecureHash; |  | ||||||
| import net.corda.v5.ledger.common.NotaryLookup; | import net.corda.v5.ledger.common.NotaryLookup; | ||||||
| import net.corda.v5.ledger.utxo.UtxoLedgerService; | import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||||
| import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | ||||||
|  | import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder; | ||||||
| import net.corda.v5.membership.MemberInfo; | import net.corda.v5.membership.MemberInfo; | ||||||
| import net.corda.v5.membership.NotaryInfo; | import net.corda.v5.membership.NotaryInfo; | ||||||
| 
 | 
 | ||||||
| import static java.util.Objects.requireNonNull; | import static java.util.Objects.requireNonNull; | ||||||
| import static djmil.cordacheckers.contracts.GameProposalContract.Action; |  | ||||||
| 
 | 
 | ||||||
| import java.time.Duration; | import java.time.Duration; | ||||||
| import java.time.Instant; | import java.time.Instant; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
|  | import java.util.List; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
| 
 | 
 | ||||||
|  | @InitiatingFlow(protocol = "game-proposal-create") | ||||||
| public class CreateFlow implements ClientStartableFlow{ | public class CreateFlow implements ClientStartableFlow{ | ||||||
| 
 | 
 | ||||||
|      private final static Logger log = LoggerFactory.getLogger(CreateFlow.class); |      private final static Logger log = LoggerFactory.getLogger(CreateFlow.class); | ||||||
| @ -49,22 +52,19 @@ public class CreateFlow implements ClientStartableFlow{ | |||||||
|      @CordaInject |      @CordaInject | ||||||
|      public FlowEngine flowEngine; |      public FlowEngine flowEngine; | ||||||
| 
 | 
 | ||||||
|  |      @CordaInject | ||||||
|  |      public FlowMessaging flowMessaging; | ||||||
|  | 
 | ||||||
|      @Suspendable |      @Suspendable | ||||||
|      @Override |      @Override | ||||||
|      public String call(ClientRequestBody requestBody) { |      public String call(ClientRequestBody requestBody) { | ||||||
|           try { |           try { | ||||||
|                log.info("flow: Create Game Proposal"); |                log.info("flow: Create Game Proposal"); | ||||||
|                final Action actino = Action.CREATE; |  | ||||||
| 
 | 
 | ||||||
|                final GameProposalState newGameProposal = buildGameProposalStateFrom(requestBody); |                GameProposalState gameProposal = buildGameProposalStateFrom(requestBody); | ||||||
|  |                String trxResult = doTrunsaction(gameProposal); | ||||||
| 
 | 
 | ||||||
|                final UtxoSignedTransaction trx = prepareSignedTransaction(newGameProposal); |                return new FlowResult(trxResult).toJsonEncodedString(jsonMarshallingService); | ||||||
| 
 |  | ||||||
|                final SecureHash trxId = this.flowEngine |  | ||||||
|                     .subFlow(new CommitSubFlow(trx, actino.getRespondent(newGameProposal))); |  | ||||||
| 
 |  | ||||||
|                return new FlowResult(newGameProposal.getId(), trxId) |  | ||||||
|                     .toJsonEncodedString(jsonMarshallingService); |  | ||||||
|           }  |           }  | ||||||
|           catch (Exception e) { |           catch (Exception e) { | ||||||
|                log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody +  |                log.warn("CreateGameProposal flow failed to process utxo request body " + requestBody +  | ||||||
| @ -77,10 +77,10 @@ public class CreateFlow implements ClientStartableFlow{ | |||||||
|      private GameProposalState buildGameProposalStateFrom(ClientRequestBody requestBody) { |      private GameProposalState buildGameProposalStateFrom(ClientRequestBody requestBody) { | ||||||
|           CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class); |           CreateFlowArgs args = requestBody.getRequestBodyAs(jsonMarshallingService, CreateFlowArgs.class); | ||||||
| 
 | 
 | ||||||
|           Piece.Color opponentColor = Piece.Color.valueOf(args.opponentColor); |           GameProposalState.Color opponentColor = GameProposalState.Color.valueOf(args.opponentColor); | ||||||
|           if (opponentColor == null) { |           if (opponentColor == null) { | ||||||
|                throw new RuntimeException("Allowed values for opponentColor are: " |                throw new RuntimeException("Allowed values for opponentColor are: " | ||||||
|                     + Piece.Color.WHITE.name() +", "  + Piece.Color.BLACK.name()  |                     + GameProposalState.Color.WHITE.name() +", "  + GameProposalState.Color.BLACK.name()  | ||||||
|                     + ". Actual value was: " + args.opponentColor); |                     + ". Actual value was: " + args.opponentColor); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
| @ -101,15 +101,24 @@ public class CreateFlow implements ClientStartableFlow{ | |||||||
|      } |      } | ||||||
| 
 | 
 | ||||||
|      @Suspendable |      @Suspendable | ||||||
|      private UtxoSignedTransaction prepareSignedTransaction(GameProposalState outputGameProposalState) {      |      private String doTrunsaction(GameProposalState gameProposal) {      | ||||||
|           final NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next(); |           NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next(); | ||||||
| 
 | 
 | ||||||
|           return ledgerService.createTransactionBuilder() |           UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder() | ||||||
|                .setNotary(notary.getName()) |                .setNotary(notary.getName()) | ||||||
|                .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) |                .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) | ||||||
|                .addOutputState(outputGameProposalState) |                .addOutputState(gameProposal) | ||||||
|                .addCommand(Action.CREATE) |                .addCommand(new GameProposalContract.Create()) | ||||||
|                .addSignatories(outputGameProposalState.getParticipants()) |                .addSignatories(gameProposal.getParticipants()); | ||||||
|                .toSignedTransaction(); | 
 | ||||||
|  |           UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(); | ||||||
|  | 
 | ||||||
|  |           FlowSession session = flowMessaging.initiateFlow(gameProposal.getRecipient()); | ||||||
|  | 
 | ||||||
|  |           List<FlowSession> sessionsList = Arrays.asList(session); | ||||||
|  | 
 | ||||||
|  |           ledgerService.finalize(signedTransaction, sessionsList); | ||||||
|  | 
 | ||||||
|  |           return gameProposal.id.toString(); | ||||||
|      } |      } | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,72 @@ | |||||||
|  | package djmil.cordacheckers.gameproposal; | ||||||
|  | 
 | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | 
 | ||||||
|  | import djmil.cordacheckers.states.GameProposalState; | ||||||
|  | import net.corda.v5.application.flows.CordaInject; | ||||||
|  | import net.corda.v5.application.flows.InitiatedBy; | ||||||
|  | import net.corda.v5.application.flows.ResponderFlow; | ||||||
|  | import net.corda.v5.application.membership.MemberLookup; | ||||||
|  | import net.corda.v5.application.messaging.FlowSession; | ||||||
|  | import net.corda.v5.base.annotations.Suspendable; | ||||||
|  | import net.corda.v5.base.exceptions.CordaRuntimeException; | ||||||
|  | import net.corda.v5.base.types.MemberX500Name; | ||||||
|  | import net.corda.v5.ledger.utxo.UtxoLedgerService; | ||||||
|  | import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; | ||||||
|  | import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; | ||||||
|  | 
 | ||||||
|  | @InitiatedBy(protocol = "game-proposal-create") | ||||||
|  | public class CreateResponder implements ResponderFlow { | ||||||
|  |     private final static Logger log = LoggerFactory.getLogger(CreateResponder.class); | ||||||
|  | 
 | ||||||
|  |     @CordaInject | ||||||
|  |     public MemberLookup memberLookup; | ||||||
|  | 
 | ||||||
|  |     @CordaInject | ||||||
|  |     public UtxoLedgerService utxoLedgerService; | ||||||
|  |      | ||||||
|  |     @Suspendable | ||||||
|  |     @Override | ||||||
|  |     public void call(FlowSession session) { | ||||||
|  |         try { | ||||||
|  |             // Defines the lambda validator used in receiveFinality below. | ||||||
|  |             UtxoTransactionValidator txValidator = ledgerTransaction -> { | ||||||
|  |                 GameProposalState gameProposal = (GameProposalState) ledgerTransaction.getOutputContractStates().get(0); | ||||||
|  |                 // Uses checkForBannedWords() and checkMessageFromMatchesCounterparty() functions | ||||||
|  |                 // to check whether to sign the transaction. | ||||||
|  |                 if (!checkParticipants(gameProposal, session.getCounterparty())) { | ||||||
|  |                     throw new CordaRuntimeException("Failed verification"); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 log.info("Verified the transaction - " + ledgerTransaction.getId()); | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             // Calls receiveFinality() function which provides the responder to the finalise() function | ||||||
|  |             // in the Initiating Flow. Accepts a lambda validator containing the business logic to decide whether | ||||||
|  |             // responder should sign the Transaction. | ||||||
|  |             UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService | ||||||
|  |                     .receiveFinality(session, txValidator) | ||||||
|  |                     .getTransaction(); | ||||||
|  | 
 | ||||||
|  |             log.info("Finished responder flow - " + finalizedSignedTransaction.getId()); | ||||||
|  |         } | ||||||
|  |         // Soft fails the flow and log the exception. | ||||||
|  |         catch(Exception e) | ||||||
|  |         { | ||||||
|  |             log.warn("Exceptionally finished responder flow", e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Suspendable | ||||||
|  |     Boolean checkParticipants(GameProposalState gameProposal, MemberX500Name counterpartyName) { | ||||||
|  |         MemberX500Name myName = memberLookup.myInfo().getName(); | ||||||
|  | 
 | ||||||
|  |         if (gameProposal.getRecipient().compareTo(myName) == 0 &&  | ||||||
|  |             gameProposal.getSender().compareTo(counterpartyName) == 0) | ||||||
|  |             return true; | ||||||
|  |      | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| @ -10,26 +10,26 @@ import djmil.cordacheckers.states.GameProposalState; | |||||||
| // and a UUID. It is possible to create custom serializers for the JsonMarshallingService in the future. | // and a UUID. It is possible to create custom serializers for the JsonMarshallingService in the future. | ||||||
| 
 | 
 | ||||||
| public class ListItem { | public class ListItem { | ||||||
|     public final String issuer; |     public final String sender; | ||||||
|     public final String acquier; |     public final String recipient; | ||||||
|     public final String acquierColor; |     public final String recipientColor; | ||||||
|     public final String message; |     public final String message; | ||||||
|     public final UUID id; |     public final UUID id; | ||||||
| 
 | 
 | ||||||
|     // Serialisation service requires a default constructor |     // Serialisation service requires a default constructor | ||||||
|     public ListItem() {  |     public ListItem() {  | ||||||
|         this.issuer = null; |         this.sender = null; | ||||||
|         this.acquier = null; |         this.recipient = null; | ||||||
|         this.acquierColor = null; |         this.recipientColor = null; | ||||||
|         this.message = null; |         this.message = null; | ||||||
|         this.id = null; |         this.id = null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public ListItem(GameProposalState state) { |     public ListItem(GameProposalState state) { | ||||||
|         this.issuer  = state.getIssuer().getCommonName(); |         this.sender    = state.getSender().getCommonName(); | ||||||
|         this.acquier = state.getAcquier().getCommonName(); |         this.recipient = state.getRecipient().getCommonName(); | ||||||
|         this.acquierColor = state.getRecipientColor().name(); |         this.recipientColor = state.getRecipientColor().name(); | ||||||
|         this.message = state.getMessage(); |         this.message   = state.getMessage(); | ||||||
|         this.id      = state.getId(); |         this.id        = state.getId(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user