diff --git a/corda/contracts/src/main/java/com/r3/developers/csdetemplate/utxoexample/contracts/ChatContract.java b/corda/contracts/src/main/java/com/r3/developers/csdetemplate/utxoexample/contracts/ChatContract.java deleted file mode 100644 index 846b286..0000000 --- a/corda/contracts/src/main/java/com/r3/developers/csdetemplate/utxoexample/contracts/ChatContract.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.contracts; - -import com.r3.developers.csdetemplate.utxoexample.states.ChatState; -import net.corda.v5.base.exceptions.CordaRuntimeException; -import net.corda.v5.ledger.utxo.Command; -import net.corda.v5.ledger.utxo.Contract; -import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -public class ChatContract implements Contract { - - private final static Logger log = LoggerFactory.getLogger(ChatContract.class); - - // Use constants to hold the error messages - // This allows the tests to use them, meaning if they are updated you won't need to fix tests just because the wording was updated - static final String REQUIRE_SINGLE_COMMAND = "Require a single command."; - static final String UNKNOWN_COMMAND = "Unsupported command"; - static final String OUTPUT_STATE_SHOULD_ONLY_HAVE_TWO_PARTICIPANTS = "The output state should have two and only two participants."; - static final String TRANSACTION_SHOULD_BE_SIGNED_BY_ALL_PARTICIPANTS = "The transaction should have been signed by both participants."; - - static final String CREATE_COMMAND_SHOULD_HAVE_NO_INPUT_STATES = "When command is Create there should be no input states."; - static final String CREATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE = "When command is Create there should be one and only one output state."; - - static final String UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_INPUT_STATE = "When command is Update there should be one and only one input state."; - static final String UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE = "When command is Update there should be one and only one output state."; - static final String UPDATE_COMMAND_ID_SHOULD_NOT_CHANGE = "When command is Update id must not change."; - static final String UPDATE_COMMAND_CHATNAME_SHOULD_NOT_CHANGE = "When command is Update chatName must not change."; - static final String UPDATE_COMMAND_PARTICIPANTS_SHOULD_NOT_CHANGE = "When command is Update participants must not change."; - - public static class Create implements Command { } - public static class Update implements Command { } - - @Override - public void verify(UtxoLedgerTransaction transaction) { - - requireThat( transaction.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND); - Command command = transaction.getCommands().get(0); - - ChatState output = transaction.getOutputStates(ChatState.class).get(0); - - requireThat(output.getParticipants().size() == 2, OUTPUT_STATE_SHOULD_ONLY_HAVE_TWO_PARTICIPANTS); - requireThat(transaction.getSignatories().containsAll(output.getParticipants()), TRANSACTION_SHOULD_BE_SIGNED_BY_ALL_PARTICIPANTS); - - if(command.getClass() == Create.class) { - requireThat(transaction.getInputContractStates().isEmpty(), CREATE_COMMAND_SHOULD_HAVE_NO_INPUT_STATES); - requireThat(transaction.getOutputContractStates().size() == 1, CREATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE); - } - else if(command.getClass() == Update.class) { - requireThat(transaction.getInputContractStates().size() == 1, UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_INPUT_STATE); - requireThat(transaction.getOutputContractStates().size() == 1, UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE); - - ChatState input = transaction.getInputStates(ChatState.class).get(0); - requireThat(input.getId().equals(output.getId()), UPDATE_COMMAND_ID_SHOULD_NOT_CHANGE); - requireThat(input.getChatName().equals(output.getChatName()), UPDATE_COMMAND_CHATNAME_SHOULD_NOT_CHANGE); - requireThat( - input.getParticipants().containsAll(output.getParticipants()) && - output.getParticipants().containsAll(input.getParticipants()), - UPDATE_COMMAND_PARTICIPANTS_SHOULD_NOT_CHANGE); - } - else { - throw new CordaRuntimeException(UNKNOWN_COMMAND); - } - } - - private void requireThat(boolean asserted, String errorMessage) { - if(!asserted) { - throw new CordaRuntimeException("Failed requirement: " + errorMessage); - } - } -} diff --git a/corda/contracts/src/main/java/com/r3/developers/csdetemplate/utxoexample/states/ChatState.java b/corda/contracts/src/main/java/com/r3/developers/csdetemplate/utxoexample/states/ChatState.java deleted file mode 100644 index bc85e7f..0000000 --- a/corda/contracts/src/main/java/com/r3/developers/csdetemplate/utxoexample/states/ChatState.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.states; - -import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract; -import net.corda.v5.base.annotations.ConstructorForDeserialization; -import net.corda.v5.base.types.MemberX500Name; -import net.corda.v5.ledger.utxo.BelongsToContract; -import net.corda.v5.ledger.utxo.ContractState; - -import java.security.PublicKey; -import java.util.*; - -@BelongsToContract(ChatContract.class) -public class ChatState implements ContractState { - - private UUID id; - private String chatName; - private MemberX500Name messageFrom; - private String message; - public List participants; - - // Allows serialisation and to use a specified UUID. - @ConstructorForDeserialization - public ChatState(UUID id, - String chatName, - MemberX500Name messageFrom, - String message, - List participants) { - this.id = id; - this.chatName = chatName; - this.messageFrom = messageFrom; - this.message = message; - this.participants = participants; - } - - public UUID getId() { - return id; - } - public String getChatName() { - return chatName; - } - public MemberX500Name getMessageFrom() { - return messageFrom; - } - public String getMessage() { - return message; - } - - public List getParticipants() { - return participants; - } - - public ChatState updateMessage(MemberX500Name name, String message) { - return new ChatState(id, chatName, name, message, participants); - } -} \ No newline at end of file diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/Message.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/Message.java deleted file mode 100644 index b20791e..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/Message.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.r3.developers.csdetemplate.flowexample.workflows; - -import net.corda.v5.base.annotations.CordaSerializable; -import net.corda.v5.base.types.MemberX500Name; - -// Where a class contains a message, mark it with @CordaSerializable to enable Corda to -// send it from one virtual node to another. -@CordaSerializable -public class Message { - - private MemberX500Name sender; - private String message; - - public Message(MemberX500Name sender, String message) { - this.sender = sender; - this.message = message; - } - - public MemberX500Name getSender() { - return sender; - } - - public String getMessage() { - return message; - } - - -} diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlow.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlow.java deleted file mode 100644 index 4e7b368..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlow.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.r3.developers.csdetemplate.flowexample.workflows; - -import net.corda.v5.application.flows.*; -import net.corda.v5.application.marshalling.JsonMarshallingService; -import net.corda.v5.application.membership.MemberLookup; -import net.corda.v5.application.messaging.FlowMessaging; -import net.corda.v5.application.messaging.FlowSession; -import net.corda.v5.base.annotations.Suspendable; -import net.corda.v5.base.types.MemberX500Name; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -// MyFirstFlow is an initiating flow, its corresponding responder flow is called MyFirstFlowResponder (defined below) -// to link the two sides of the flow together they need to have the same protocol. -@InitiatingFlow(protocol = "my-first-flow") -// MyFirstFlow should inherit from ClientStartableFlow, which tells Corda it can be started via an REST call -public class MyFirstFlow implements ClientStartableFlow { - - // Log messages from the flows for debugging. - private final static Logger log = LoggerFactory.getLogger(MyFirstFlow.class); - - // Corda has a set of injectable services which are injected into the flow at runtime. - // Flows declare them with @CordaInjectable, then the flows have access to their services. - - // JsonMarshallingService provides a service for manipulating JSON. - @CordaInject - public JsonMarshallingService jsonMarshallingService; - - // FlowMessaging provides a service that establishes flow sessions between virtual nodes - // that send and receive payloads between them. - @CordaInject - public FlowMessaging flowMessaging; - - // MemberLookup provides a service for looking up information about members of the virtual network which - // this CorDapp operates in. - @CordaInject - public MemberLookup memberLookup; - - public MyFirstFlow() {} - - // When a flow is invoked its call() method is called. - // Call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait - // for a response from the other flows and services. - - @Suspendable - @Override - public String call(ClientRequestBody requestBody) { - - // Follow what happens in the console or logs. - log.info("MFF: MyFirstFlow.call() called"); - - // Show the requestBody in the logs - this can be used to help establish the format for starting a flow on Corda. - log.info("MFF: requestBody: " + requestBody.getRequestBody()); - - // Deserialize the Json requestBody into the MyfirstFlowStartArgs class using the JsonSerialisation service. - MyFirstFlowStartArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, MyFirstFlowStartArgs.class); - - // Obtain the MemberX500Name of the counterparty. - MemberX500Name otherMember = flowArgs.getOtherMember(); - - // Get our identity from the MemberLookup service. - MemberX500Name ourIdentity = memberLookup.myInfo().getName(); - - // Create the message payload using the MessageClass we defined. - Message message = new Message(otherMember, "Hello from " + ourIdentity + "."); - - // Log the message to be sent. - log.info("MFF: message.message: " + message.getMessage()); - - // Start a flow session with the otherMember using the FlowMessaging service. - // The otherMember's virtual node will run the corresponding MyFirstFlowResponder responder flow. - FlowSession session = flowMessaging.initiateFlow(otherMember); - - // Send the Payload using the send method on the session to the MyFirstFlowResponder responder flow. - session.send(message); - - // Receive a response from the responder flow. - Message response = session.receive(Message.class); - - // The return value of a ClientStartableFlow must always be a String. This will be passed - // back as the REST response when the status of the flow is queried on Corda. - return response.getMessage(); - } -} - -/* -RequestBody for triggering the flow via REST: -{ - "clientRequestId": "r1", - "flowClassName": "com.r3.developers.csdetemplate.flowexample.workflows.MyFirstFlow", - "requestBody": { - "otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB" - } -} - */ diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlowResponder.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlowResponder.java deleted file mode 100644 index 7e7eb74..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlowResponder.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.r3.developers.csdetemplate.flowexample.workflows; - -import net.corda.v5.application.flows.CordaInject; -import net.corda.v5.application.flows.InitiatedBy; -import net.corda.v5.application.flows.ResponderFlow; -import net.corda.v5.application.membership.MemberLookup; -import net.corda.v5.application.messaging.FlowSession; -import net.corda.v5.base.annotations.Suspendable; -import net.corda.v5.base.types.MemberX500Name; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -// MyFirstFlowResponder is a responder flow, its corresponding initiating flow is called MyFirstFlow (defined in MyFirstFlow.java) -// to link the two sides of the flow together they need to have the same protocol. -@InitiatedBy(protocol = "my-first-flow") -// Responder flows must inherit from ResponderFlow -public class MyFirstFlowResponder implements ResponderFlow { - - // Log messages from the flows for debugging. - private final static Logger log = LoggerFactory.getLogger(MyFirstFlowResponder.class); - - // MemberLookup looks for information about members of the virtual network which - // this CorDapp operates in. - @CordaInject - public MemberLookup memberLookup; - - public MyFirstFlowResponder() {} - - // Responder flows are invoked when an initiating flow makes a call via a session set up with the virtual - // node hosting the responder flow. When a responder flow is invoked its call() method is called. - // call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait - // for a response from the other flows and services. - // The call() method has the flow session passed in as a parameter by Corda so the session is available to - // responder flow code, you don't need to inject the FlowMessaging service. - @Suspendable - @Override - public void call(FlowSession session) { - - // Follow what happens in the console or logs. - log.info("MFF: MyFirstResponderFlow.call() called"); - - // Receive the payload and deserialize it into a message class. - Message receivedMessage = session.receive(Message.class); - - // Log the message as a proxy for performing some useful operation on it. - log.info("MFF: Message received from " + receivedMessage.getSender() + ":" + receivedMessage.getMessage()); - - // Get our identity from the MemberLookup service. - MemberX500Name ourIdentity = memberLookup.myInfo().getName(); - - // Create a message to greet the sender. - Message response = new Message(ourIdentity, - "Hello " + session.getCounterparty().getCommonName() + ", best wishes from " + ourIdentity.getCommonName()); - - // Log the response to be sent. - log.info("MFF: response.message: " + response.getMessage()); - - // Send the response via the send method on the flow session - session.send(response); - } -} diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlowStartArgs.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlowStartArgs.java deleted file mode 100644 index bfcca2d..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlowStartArgs.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.r3.developers.csdetemplate.flowexample.workflows; - -import net.corda.v5.base.types.MemberX500Name; - -// A class to hold the arguments required to start the flow -public class MyFirstFlowStartArgs { - private MemberX500Name otherMember; - - // The JSON Marshalling Service, which handles serialisation, needs this constructor. - public MyFirstFlowStartArgs() {} - - public MyFirstFlowStartArgs(MemberX500Name otherMember) { - this.otherMember = otherMember; - } - - public MemberX500Name getOtherMember() { - return otherMember; - } -} diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/ChatStateResults.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/ChatStateResults.java deleted file mode 100644 index 5c3f37f..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/ChatStateResults.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -import java.util.UUID; - -// Class to hold the ListChatFlow results. -// The ChatState(s) cannot be returned directly as the JsonMarshallingService can only serialize simple classes -// that the underlying Jackson serializer recognises, hence creating a DTO style object which consists only of Strings -// and a UUID. It is possible to create custom serializers for the JsonMarshallingService, but this beyond the scope -// of this simple example. -public class ChatStateResults { - - private UUID id; - private String chatName; - private String messageFromName; - private String message; - - public ChatStateResults() {} - - public ChatStateResults(UUID id, String chatName, String messageFromName, String message) { - this.id = id; - this.chatName = chatName; - this.messageFromName = messageFromName; - this.message = message; - } - - public UUID getId() { - return id; - } - - public String getChatName() { - return chatName; - } - - public String getMessageFromName() { - return messageFromName; - } - - public String getMessage() { - return message; - } -} diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/CreateNewChatFlow.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/CreateNewChatFlow.java deleted file mode 100644 index f3c7286..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/CreateNewChatFlow.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract; -import com.r3.developers.csdetemplate.utxoexample.states.ChatState; -import net.corda.v5.application.flows.*; -import net.corda.v5.application.marshalling.JsonMarshallingService; -import net.corda.v5.application.membership.MemberLookup; -import net.corda.v5.base.annotations.Suspendable; -import net.corda.v5.base.exceptions.CordaRuntimeException; -import net.corda.v5.base.types.MemberX500Name; -import net.corda.v5.ledger.common.NotaryLookup; -import net.corda.v5.ledger.utxo.UtxoLedgerService; -import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; -import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder; -import net.corda.v5.membership.MemberInfo; -import net.corda.v5.membership.NotaryInfo; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.security.PublicKey; -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.Objects; -import java.util.UUID; - -import static java.util.Objects.*; - -// See Chat CorDapp Design section of the getting started docs for a description of this flow. -public class CreateNewChatFlow implements ClientStartableFlow { - - private final static Logger log = LoggerFactory.getLogger(CreateNewChatFlow.class); - - @CordaInject - public JsonMarshallingService jsonMarshallingService; - - @CordaInject - public MemberLookup memberLookup; - - // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API. - @CordaInject - public UtxoLedgerService ledgerService; - - @CordaInject - public NotaryLookup notaryLookup; - - // FlowEngine service is required to run SubFlows. - @CordaInject - public FlowEngine flowEngine; - - - @Suspendable - @Override - public String call( ClientRequestBody requestBody) { - - log.info("CreateNewChatFlow.call() called"); - - try { - // Obtain the deserialized input arguments to the flow from the requestBody. - CreateNewChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, CreateNewChatFlowArgs.class); - - // Get MemberInfos for the Vnode running the flow and the otherMember. - MemberInfo myInfo = memberLookup.myInfo(); - MemberInfo otherMember = requireNonNull( - memberLookup.lookup(MemberX500Name.parse(flowArgs.getOtherMember())), - "MemberLookup can't find otherMember specified in flow arguments." - ); - - // Create the ChatState from the input arguments and member information. - ChatState chatState = new ChatState( - UUID.randomUUID(), - flowArgs.getChatName(), - myInfo.getName(), - flowArgs.getMessage(), - Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0)) - ); - - // Obtain the Notary name and public key. - NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next(); - - // Use UTXOTransactionBuilder to build up the draft transaction. - UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder() - .setNotary(notary.getName()) - .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) - .addOutputState(chatState) - .addCommand(new ChatContract.Create()) - .addSignatories(chatState.getParticipants()); - - // Convert the transaction builder to a UTXOSignedTransaction. Verifies the content of the - // UtxoTransactionBuilder and signs the transaction with any required signatories that belong to - // the current node. - UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(); - - // Call FinalizeChatSubFlow which will finalise the transaction. - // If successful the flow will return a String of the created transaction id, - // if not successful it will return an error message. - return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName())); - } - // Catch any exceptions, log them and rethrow the exception. - catch (Exception e) { - log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage()); - throw new CordaRuntimeException(e.getMessage()); - } - } -} - -/* -RequestBody for triggering the flow via REST: -{ - "clientRequestId": "create-1", - "flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.CreateNewChatFlow", - "requestBody": { - "chatName":"Chat with Bob", - "otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB", - "message": "Hello Bob" - } -} - */ diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/CreateNewChatFlowArgs.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/CreateNewChatFlowArgs.java deleted file mode 100644 index c2b815e..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/CreateNewChatFlowArgs.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -// A class to hold the deserialized arguments required to start the flow. -public class CreateNewChatFlowArgs{ - - // Serialisation service requires a default constructor - public CreateNewChatFlowArgs() {} - - private String chatName; - private String message; - private String otherMember; - - public CreateNewChatFlowArgs(String chatName, String message, String otherMember) { - this.chatName = chatName; - this.message = message; - this.otherMember = otherMember; - } - - public String getChatName() { - return chatName; - } - - public String getMessage() { - return message; - } - - public String getOtherMember() { - return otherMember; - } -} \ No newline at end of file diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/FinalizeChatResponderFlow.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/FinalizeChatResponderFlow.java deleted file mode 100644 index 0cb2f3d..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/FinalizeChatResponderFlow.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -import com.r3.developers.csdetemplate.utxoexample.states.ChatState; -import net.corda.v5.application.flows.CordaInject; -import net.corda.v5.application.flows.InitiatedBy; -import net.corda.v5.application.flows.ResponderFlow; -import net.corda.v5.application.messaging.FlowSession; -import net.corda.v5.base.annotations.Suspendable; -import net.corda.v5.base.exceptions.CordaRuntimeException; -import net.corda.v5.base.types.MemberX500Name; -import net.corda.v5.ledger.utxo.UtxoLedgerService; -import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; -import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; -import java.util.List; - - -// See Chat CorDapp Design section of the getting started docs for a description of this flow. - -//@InitiatingBy declares the protocol which will be used to link the initiator to the responder. -@InitiatedBy(protocol = "finalize-chat-protocol") -public class FinalizeChatResponderFlow implements ResponderFlow { - private final static Logger log = LoggerFactory.getLogger(FinalizeChatResponderFlow.class); - - // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API. - @CordaInject - public UtxoLedgerService utxoLedgerService; - - @Suspendable - @Override - public void call(FlowSession session) { - - log.info("FinalizeChatResponderFlow.call() called"); - - try { - // Defines the lambda validator used in receiveFinality below. - UtxoTransactionValidator txValidator = ledgerTransaction -> { - ChatState state = (ChatState) ledgerTransaction.getOutputContractStates().get(0); - // Uses checkForBannedWords() and checkMessageFromMatchesCounterparty() functions - // to check whether to sign the transaction. - if (checkForBannedWords(state.getMessage()) || !checkMessageFromMatchesCounterparty(state, session.getCounterparty())) { - throw new CordaRuntimeException("Failed verification"); - } - log.info("Verified the transaction - " + ledgerTransaction.getId()); - }; - - // Calls receiveFinality() function which provides the responder to the finalise() function - // in the Initiating Flow. Accepts a lambda validator containing the business logic to decide whether - // responder should sign the Transaction. - UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService.receiveFinality(session, txValidator).getTransaction(); - log.info("Finished responder flow - " + finalizedSignedTransaction.getId()); - } - // Soft fails the flow and log the exception. - catch(Exception e) - { - log.warn("Exceptionally finished responder flow", e); - } - } - - - @Suspendable - Boolean checkForBannedWords(String str) { - List bannedWords = Arrays.asList("banana", "apple", "pear"); - return bannedWords.stream().anyMatch(str::contains); - } - - @Suspendable - Boolean checkMessageFromMatchesCounterparty(ChatState state, MemberX500Name otherMember) { - return state.getMessageFrom().equals(otherMember); - } - -} diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/FinalizeChatSubFlow.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/FinalizeChatSubFlow.java deleted file mode 100644 index b6b17f9..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/FinalizeChatSubFlow.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -import net.corda.v5.application.flows.*; -import net.corda.v5.application.messaging.FlowMessaging; -import net.corda.v5.application.messaging.FlowSession; -import net.corda.v5.base.annotations.Suspendable; -import net.corda.v5.base.types.MemberX500Name; -import net.corda.v5.ledger.utxo.UtxoLedgerService; -import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; -import java.util.List; - -// See Chat CorDapp Design section of the getting started docs for a description of this flow. - -// @InitiatingFlow declares the protocol which will be used to link the initiator to the responder. -@InitiatingFlow(protocol = "finalize-chat-protocol") -public class FinalizeChatSubFlow implements SubFlow { - - private final static Logger log = LoggerFactory.getLogger(FinalizeChatSubFlow.class); - private final UtxoSignedTransaction signedTransaction; - private final MemberX500Name otherMember; - - public FinalizeChatSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name otherMember) { - this.signedTransaction = signedTransaction; - this.otherMember = otherMember; - } - - // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API. - @CordaInject - public UtxoLedgerService ledgerService; - - @CordaInject - public FlowMessaging flowMessaging; - - @Override - @Suspendable - public String call() { - - log.info("FinalizeChatFlow.call() called"); - - // Initiates a session with the other Member. - FlowSession session = flowMessaging.initiateFlow(otherMember); - - // Calls the Corda provided finalise() function which gather signatures from the counterparty, - // notarises the transaction and persists the transaction to each party's vault. - // On success returns the id of the transaction created. (This is different to the ChatState id) - String result; - try { - List sessionsList = Arrays.asList(session); - - UtxoSignedTransaction finalizedSignedTransaction = ledgerService.finalize( - signedTransaction, - sessionsList - ).getTransaction(); - - result = finalizedSignedTransaction.getId().toString(); - log.info("Success! Response: " + result); - - } - // Soft fails the flow and returns the error message without throwing a flow exception. - catch (Exception e) { - log.warn("Finality failed", e); - result = "Finality failed, " + e.getMessage(); - } - // Returns the transaction id converted as a string - return result; - } -} diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/GetChatFlow.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/GetChatFlow.java deleted file mode 100644 index 6f192ea..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/GetChatFlow.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -import com.r3.developers.csdetemplate.utxoexample.states.ChatState; -import net.corda.v5.application.flows.ClientRequestBody; -import net.corda.v5.application.flows.ClientStartableFlow; -import net.corda.v5.application.flows.CordaInject; -import net.corda.v5.application.marshalling.JsonMarshallingService; -import net.corda.v5.base.annotations.Suspendable; -import net.corda.v5.base.exceptions.CordaRuntimeException; -import net.corda.v5.crypto.SecureHash; -import net.corda.v5.ledger.utxo.StateAndRef; -import net.corda.v5.ledger.utxo.UtxoLedgerService; -import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; -import static java.util.Objects.*; -import static java.util.stream.Collectors.toList; - -// See Chat CorDapp Design section of the getting started docs for a description of this flow. -public class GetChatFlow implements ClientStartableFlow { - - private final static Logger log = LoggerFactory.getLogger(GetChatFlow.class); - - @CordaInject - public JsonMarshallingService jsonMarshallingService; - - // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API. - @CordaInject - public UtxoLedgerService ledgerService; - - @Override - @Suspendable - public String call(ClientRequestBody requestBody) { - - // Obtain the deserialized input arguments to the flow from the requestBody. - GetChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, GetChatFlowArgs.class); - - // Look up the latest unconsumed ChatState with the given id. - // Note, this code brings all unconsumed states back, then filters them. - // This is an inefficient way to perform this operation when there are a large number of chats. - // Note, you will get this error if you input an id which has no corresponding ChatState (common error). - List> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class); - List> chatStateAndRefsWithId = chatStateAndRefs.stream() - .filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList()); - if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found"); - StateAndRef chatStateAndRef = chatStateAndRefsWithId.get(0); - - // Calls resolveMessagesFromBackchain() which retrieves the chat history from the backchain. - return jsonMarshallingService.format(resolveMessagesFromBackchain(chatStateAndRef, flowArgs.getNumberOfRecords() )); - } - - // resoveMessageFromBackchain() starts at the stateAndRef provided, which represents the unconsumed head of the - // backchain for this particular chat, then walks the chain backwards for the number of transaction specified in - // the numberOfRecords argument. For each transaction it adds the MessageAndSender representing the - // message and who sent it to a list which is then returned. - @Suspendable - private List resolveMessagesFromBackchain(StateAndRef stateAndRef, int numberOfRecords) { - - // Set up a Mutable List to collect the MessageAndSender(s) - List messages = new LinkedList<>(); - - // Set up initial conditions for walking the backchain. - StateAndRef currentStateAndRef = stateAndRef; - int recordsToFetch = numberOfRecords; - boolean moreBackchain = true; - - // Continue to loop until the start of the backchain or enough records have been retrieved. - while (moreBackchain) { - - // Obtain the transaction id from the current StateAndRef and fetch the transaction from the vault. - SecureHash transactionId = currentStateAndRef.getRef().getTransactionId(); - UtxoLedgerTransaction transaction = requireNonNull( - ledgerService.findLedgerTransaction(transactionId), - "Transaction " + transactionId + " not found." - ); - - // Get the output state from the transaction and use it to create a MessageAndSender Object which - // is appended to the mutable list. - List chatStates = transaction.getOutputStates(ChatState.class); - if (chatStates.size() != 1) throw new CordaRuntimeException( - "Expecting one and only one ChatState output for transaction " + transactionId + "."); - ChatState output = chatStates.get(0); - - messages.add(new MessageAndSender(output.getMessageFrom().toString(), output.getMessage())); - // Decrement the number of records to fetch. - recordsToFetch--; - - // Get the reference to the input states. - List> inputStateAndRefs = transaction.getInputStateAndRefs(); - - // Check if there are no more input states (start of chain) or we have retrieved enough records. - // Check the transaction is not malformed by having too many input states. - // Set the currentStateAndRef to the input StateAndRef, then repeat the loop. - if (inputStateAndRefs.isEmpty() || recordsToFetch == 0) { - moreBackchain = false; - } else if (inputStateAndRefs.size() > 1) { - throw new CordaRuntimeException("More than one input state found for transaction " + transactionId + "."); - } else { - currentStateAndRef = inputStateAndRefs.get(0); - } - } - return messages; - } -} - -/* -RequestBody for triggering the flow via REST: -{ - "clientRequestId": "get-1", - "flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.GetChatFlow", - "requestBody": { - "id":"** fill in id **", - "numberOfRecords":"4" - } -} - */ - diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/GetChatFlowArgs.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/GetChatFlowArgs.java deleted file mode 100644 index d1bac81..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/GetChatFlowArgs.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -import java.util.UUID; - -// A class to hold the deserialized arguments required to start the flow. -public class GetChatFlowArgs { - - private UUID id; - private int numberOfRecords; - public GetChatFlowArgs() {} - - public GetChatFlowArgs(UUID id, int numberOfRecords ) { - this.id = id; - this.numberOfRecords = numberOfRecords; - } - - public UUID getId() { - return id; - } - - public int getNumberOfRecords() { - return numberOfRecords; - } -} diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/ListChatsFlow.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/ListChatsFlow.java deleted file mode 100644 index 1416b7c..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/ListChatsFlow.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -import com.r3.developers.csdetemplate.utxoexample.states.ChatState; -import net.corda.v5.application.flows.ClientRequestBody; -import net.corda.v5.application.flows.ClientStartableFlow; -import net.corda.v5.application.flows.CordaInject; -import net.corda.v5.application.marshalling.JsonMarshallingService; -import net.corda.v5.base.annotations.Suspendable; -import net.corda.v5.ledger.utxo.StateAndRef; -import net.corda.v5.ledger.utxo.UtxoLedgerService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; -import java.util.stream.Collectors; - -// See Chat CorDapp Design section of the getting started docs for a description of this flow. -public class ListChatsFlow implements ClientStartableFlow { - - private final static Logger log = LoggerFactory.getLogger(ListChatsFlow.class); - - @CordaInject - public JsonMarshallingService jsonMarshallingService; - - // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API. - @CordaInject - public UtxoLedgerService utxoLedgerService; - - @Suspendable - @Override - public String call(ClientRequestBody requestBody) { - - log.info("ListChatsFlow.call() called"); - - // Queries the VNode's vault for unconsumed states and converts the result to a serializable DTO. - List> states = utxoLedgerService.findUnconsumedStatesByType(ChatState.class); - List results = states.stream().map( stateAndRef -> - new ChatStateResults( - stateAndRef.getState().getContractState().getId(), - stateAndRef.getState().getContractState().getChatName(), - stateAndRef.getState().getContractState().getMessageFrom().toString(), - stateAndRef.getState().getContractState().getMessage() - ) - ).collect(Collectors.toList()); - - // Uses the JsonMarshallingService's format() function to serialize the DTO to Json. - return jsonMarshallingService.format(results); - } -} - -/* -RequestBody for triggering the flow via REST: -{ - "clientRequestId": "list-1", - "flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.ListChatsFlow", - "requestBody": {} -} -*/ \ No newline at end of file diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/MessageAndSender.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/MessageAndSender.java deleted file mode 100644 index 7be54fd..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/MessageAndSender.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -// A class to pair the messageFrom and message together. -public class MessageAndSender { - - private String messageFrom; - private String message; - public MessageAndSender() {} - - public MessageAndSender(String messageFrom, String message) { - this.messageFrom = messageFrom; - this.message = message; - } - - public String getMessageFrom() { - return messageFrom; - } - - public String getMessage() { - return message; - } -} diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/UpdateChatFlow.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/UpdateChatFlow.java deleted file mode 100644 index 49e84a1..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/UpdateChatFlow.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract; -import com.r3.developers.csdetemplate.utxoexample.states.ChatState; -import net.corda.v5.application.flows.ClientRequestBody; -import net.corda.v5.application.flows.ClientStartableFlow; -import net.corda.v5.application.flows.CordaInject; -import net.corda.v5.application.flows.FlowEngine; -import net.corda.v5.application.marshalling.JsonMarshallingService; -import net.corda.v5.application.membership.MemberLookup; -import net.corda.v5.base.annotations.Suspendable; -import net.corda.v5.base.exceptions.CordaRuntimeException; -import net.corda.v5.ledger.utxo.StateAndRef; -import net.corda.v5.ledger.utxo.UtxoLedgerService; -import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction; -import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder; -import net.corda.v5.membership.MemberInfo; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Duration; -import java.time.Instant; -import java.util.List; - -import static java.util.Objects.*; -import static java.util.stream.Collectors.toList; - -// See Chat CorDapp Design section of the getting started docs for a description of this flow. -public class UpdateChatFlow implements ClientStartableFlow { - - private final static Logger log = LoggerFactory.getLogger(UpdateChatFlow.class); - - @CordaInject - public JsonMarshallingService jsonMarshallingService; - - @CordaInject - public MemberLookup memberLookup; - - // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API. - @CordaInject - public UtxoLedgerService ledgerService; - - // FlowEngine service is required to run SubFlows. - @CordaInject - public FlowEngine flowEngine; - - @Suspendable - @Override - public String call(ClientRequestBody requestBody) { - - log.info("UpdateNewChatFlow.call() called"); - - try { - // Obtain the deserialized input arguments to the flow from the requestBody. - UpdateChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, UpdateChatFlowArgs.class); - - // Look up the latest unconsumed ChatState with the given id. - // Note, this code brings all unconsumed states back, then filters them. - // This is an inefficient way to perform this operation when there are a large number of chats. - // Note, you will get this error if you input an id which has no corresponding ChatState (common error). - List> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class); - List> chatStateAndRefsWithId = chatStateAndRefs.stream() - .filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList()); - if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found"); - StateAndRef chatStateAndRef = chatStateAndRefsWithId.get(0); - - // Get MemberInfos for the Vnode running the flow and the otherMember. - MemberInfo myInfo = memberLookup.myInfo(); - ChatState state = chatStateAndRef.getState().getContractState(); - - List members = state.getParticipants().stream().map( - it -> requireNonNull(memberLookup.lookup(it), "Member not found from public Key "+ it + ".") - ).collect(toList()); - members.remove(myInfo); - if(members.size() != 1) throw new RuntimeException("Should be only one participant other than the initiator"); - MemberInfo otherMember = members.get(0); - - // Create a new ChatState using the updateMessage helper function. - ChatState newChatState = state.updateMessage(myInfo.getName(), flowArgs.getMessage()); - - // Use UTXOTransactionBuilder to build up the draft transaction. - UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder() - .setNotary(chatStateAndRef.getState().getNotaryName()) - .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis())) - .addOutputState(newChatState) - .addInputState(chatStateAndRef.getRef()) - .addCommand(new ChatContract.Update()) - .addSignatories(newChatState.getParticipants()); - - // Convert the transaction builder to a UTXOSignedTransaction. Verifies the content of the - // UtxoTransactionBuilder and signs the transaction with any required signatories that belong to - // the current node. - UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(); - - // Call FinalizeChatSubFlow which will finalise the transaction. - // If successful the flow will return a String of the created transaction id, - // if not successful it will return an error message. - return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName())); - - - } - // Catch any exceptions, log them and rethrow the exception. - catch (Exception e) { - log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage()); - throw e; - } - } -} - -/* -RequestBody for triggering the flow via REST: -{ - "clientRequestId": "update-1", - "flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.UpdateChatFlow", - "requestBody": { - "id":" ** fill in id **", - "message": "How are you today?" - } -} - */ \ No newline at end of file diff --git a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/UpdateChatFlowArgs.java b/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/UpdateChatFlowArgs.java deleted file mode 100644 index 8ac7009..0000000 --- a/corda/workflows/src/main/java/com/r3/developers/csdetemplate/utxoexample/workflows/UpdateChatFlowArgs.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.r3.developers.csdetemplate.utxoexample.workflows; - -import java.util.UUID; - -// A class to hold the deserialized arguments required to start the flow. -public class UpdateChatFlowArgs { - public UpdateChatFlowArgs() {} - - private UUID id; - private String message; - - public UpdateChatFlowArgs(UUID id, String message) { - this.id = id; - this.message = message; - } - - public UUID getId() { - return id; - } - - public String getMessage() { - return message; - } -} diff --git a/corda/workflows/src/test/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlowTest.java b/corda/workflows/src/test/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlowTest.java deleted file mode 100644 index 9dea76c..0000000 --- a/corda/workflows/src/test/java/com/r3/developers/csdetemplate/flowexample/workflows/MyFirstFlowTest.java +++ /dev/null @@ -1,41 +0,0 @@ -//package com.r3.developers.csdetemplate.flowexample.workflows; -// -//import net.corda.simulator.RequestData; -//import net.corda.simulator.SimulatedVirtualNode; -//import net.corda.simulator.Simulator; -//import net.corda.v5.base.types.MemberX500Name; -//import org.junit.jupiter.api.Test; -// -//class MyFirstFlowTest { -// // Names picked to match the corda network in config/dev-net.json -// private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB"); -// private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"); -// -// @Test -// @SuppressWarnings("unchecked") -// public void test_that_MyFirstFLow_returns_correct_message() { -// // Instantiate an instance of the simulator. -// Simulator simulator = new Simulator(); -// -// // Create Alice's and Bob's virtual nodes, including the classes of the flows which will be registered on each node. -// // Don't assign Bob's virtual node to a value. You don't need it for this particular test. -// SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceX500, MyFirstFlow.class); -// simulator.createVirtualNode(bobX500, MyFirstFlowResponder.class); -// -// // Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow. -// MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500); -// -// // Create a requestData object. -// RequestData requestData = RequestData.Companion.create( -// "request no 1", // A unique reference for the instance of the flow request. -// MyFirstFlow.class, // The name of the flow class which is to be started. -// myFirstFlowStartArgs // The object which contains the start arguments of the flow. -// ); -// -// // Call the flow on Alice's virtual node and capture the response. -// String flowResponse = aliceVN.callFlow(requestData); -// -// // Check that the flow has returned the expected string. -// assert(flowResponse.equals("Hello Alice, best wishes from Bob")); -// } -//}