delete csde default code

This commit is contained in:
djmil 2023-09-12 16:17:13 +02:00
parent 01fd273c3a
commit 9a49e68a1f
18 changed files with 0 additions and 1075 deletions

View File

@ -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);
}
}
}

View File

@ -1,55 +0,0 @@
package com.r3.developers.csdetemplate.utxoexample.states;
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
import net.corda.v5.base.annotations.ConstructorForDeserialization;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.BelongsToContract;
import net.corda.v5.ledger.utxo.ContractState;
import java.security.PublicKey;
import java.util.*;
@BelongsToContract(ChatContract.class)
public class ChatState implements ContractState {
private UUID id;
private String chatName;
private MemberX500Name messageFrom;
private String message;
public List<PublicKey> participants;
// Allows serialisation and to use a specified UUID.
@ConstructorForDeserialization
public ChatState(UUID id,
String chatName,
MemberX500Name messageFrom,
String message,
List<PublicKey> participants) {
this.id = id;
this.chatName = chatName;
this.messageFrom = messageFrom;
this.message = message;
this.participants = participants;
}
public UUID getId() {
return id;
}
public String getChatName() {
return chatName;
}
public MemberX500Name getMessageFrom() {
return messageFrom;
}
public String getMessage() {
return message;
}
public List<PublicKey> getParticipants() {
return participants;
}
public ChatState updateMessage(MemberX500Name name, String message) {
return new ChatState(id, chatName, name, message, participants);
}
}

View File

@ -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;
}
}

View File

@ -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"
}
}
*/

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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"
}
}
*/

View File

@ -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;
}
}

View File

@ -1,75 +0,0 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.flows.InitiatedBy;
import net.corda.v5.application.flows.ResponderFlow;
import net.corda.v5.application.messaging.FlowSession;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.exceptions.CordaRuntimeException;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
//@InitiatingBy declares the protocol which will be used to link the initiator to the responder.
@InitiatedBy(protocol = "finalize-chat-protocol")
public class FinalizeChatResponderFlow implements ResponderFlow {
private final static Logger log = LoggerFactory.getLogger(FinalizeChatResponderFlow.class);
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
@CordaInject
public UtxoLedgerService utxoLedgerService;
@Suspendable
@Override
public void call(FlowSession session) {
log.info("FinalizeChatResponderFlow.call() called");
try {
// Defines the lambda validator used in receiveFinality below.
UtxoTransactionValidator txValidator = ledgerTransaction -> {
ChatState state = (ChatState) ledgerTransaction.getOutputContractStates().get(0);
// Uses checkForBannedWords() and checkMessageFromMatchesCounterparty() functions
// to check whether to sign the transaction.
if (checkForBannedWords(state.getMessage()) || !checkMessageFromMatchesCounterparty(state, session.getCounterparty())) {
throw new CordaRuntimeException("Failed verification");
}
log.info("Verified the transaction - " + ledgerTransaction.getId());
};
// Calls receiveFinality() function which provides the responder to the finalise() function
// in the Initiating Flow. Accepts a lambda validator containing the business logic to decide whether
// responder should sign the Transaction.
UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService.receiveFinality(session, txValidator).getTransaction();
log.info("Finished responder flow - " + finalizedSignedTransaction.getId());
}
// Soft fails the flow and log the exception.
catch(Exception e)
{
log.warn("Exceptionally finished responder flow", e);
}
}
@Suspendable
Boolean checkForBannedWords(String str) {
List<String> bannedWords = Arrays.asList("banana", "apple", "pear");
return bannedWords.stream().anyMatch(str::contains);
}
@Suspendable
Boolean checkMessageFromMatchesCounterparty(ChatState state, MemberX500Name otherMember) {
return state.getMessageFrom().equals(otherMember);
}
}

View File

@ -1,71 +0,0 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
import net.corda.v5.application.flows.*;
import net.corda.v5.application.messaging.FlowMessaging;
import net.corda.v5.application.messaging.FlowSession;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.types.MemberX500Name;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
// @InitiatingFlow declares the protocol which will be used to link the initiator to the responder.
@InitiatingFlow(protocol = "finalize-chat-protocol")
public class FinalizeChatSubFlow implements SubFlow<String> {
private final static Logger log = LoggerFactory.getLogger(FinalizeChatSubFlow.class);
private final UtxoSignedTransaction signedTransaction;
private final MemberX500Name otherMember;
public FinalizeChatSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name otherMember) {
this.signedTransaction = signedTransaction;
this.otherMember = otherMember;
}
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
@CordaInject
public UtxoLedgerService ledgerService;
@CordaInject
public FlowMessaging flowMessaging;
@Override
@Suspendable
public String call() {
log.info("FinalizeChatFlow.call() called");
// Initiates a session with the other Member.
FlowSession session = flowMessaging.initiateFlow(otherMember);
// Calls the Corda provided finalise() function which gather signatures from the counterparty,
// notarises the transaction and persists the transaction to each party's vault.
// On success returns the id of the transaction created. (This is different to the ChatState id)
String result;
try {
List<FlowSession> sessionsList = Arrays.asList(session);
UtxoSignedTransaction finalizedSignedTransaction = ledgerService.finalize(
signedTransaction,
sessionsList
).getTransaction();
result = finalizedSignedTransaction.getId().toString();
log.info("Success! Response: " + result);
}
// Soft fails the flow and returns the error message without throwing a flow exception.
catch (Exception e) {
log.warn("Finality failed", e);
result = "Finality failed, " + e.getMessage();
}
// Returns the transaction id converted as a string
return result;
}
}

View File

@ -1,119 +0,0 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow;
import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.exceptions.CordaRuntimeException;
import net.corda.v5.crypto.SecureHash;
import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import static java.util.Objects.*;
import static java.util.stream.Collectors.toList;
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
public class GetChatFlow implements ClientStartableFlow {
private final static Logger log = LoggerFactory.getLogger(GetChatFlow.class);
@CordaInject
public JsonMarshallingService jsonMarshallingService;
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
@CordaInject
public UtxoLedgerService ledgerService;
@Override
@Suspendable
public String call(ClientRequestBody requestBody) {
// Obtain the deserialized input arguments to the flow from the requestBody.
GetChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, GetChatFlowArgs.class);
// Look up the latest unconsumed ChatState with the given id.
// Note, this code brings all unconsumed states back, then filters them.
// This is an inefficient way to perform this operation when there are a large number of chats.
// Note, you will get this error if you input an id which has no corresponding ChatState (common error).
List<StateAndRef<ChatState>> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
List<StateAndRef<ChatState>> chatStateAndRefsWithId = chatStateAndRefs.stream()
.filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList());
if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found");
StateAndRef<ChatState> chatStateAndRef = chatStateAndRefsWithId.get(0);
// Calls resolveMessagesFromBackchain() which retrieves the chat history from the backchain.
return jsonMarshallingService.format(resolveMessagesFromBackchain(chatStateAndRef, flowArgs.getNumberOfRecords() ));
}
// resoveMessageFromBackchain() starts at the stateAndRef provided, which represents the unconsumed head of the
// backchain for this particular chat, then walks the chain backwards for the number of transaction specified in
// the numberOfRecords argument. For each transaction it adds the MessageAndSender representing the
// message and who sent it to a list which is then returned.
@Suspendable
private List<MessageAndSender> resolveMessagesFromBackchain(StateAndRef<?> stateAndRef, int numberOfRecords) {
// Set up a Mutable List to collect the MessageAndSender(s)
List<MessageAndSender> messages = new LinkedList<>();
// Set up initial conditions for walking the backchain.
StateAndRef<?> currentStateAndRef = stateAndRef;
int recordsToFetch = numberOfRecords;
boolean moreBackchain = true;
// Continue to loop until the start of the backchain or enough records have been retrieved.
while (moreBackchain) {
// Obtain the transaction id from the current StateAndRef and fetch the transaction from the vault.
SecureHash transactionId = currentStateAndRef.getRef().getTransactionId();
UtxoLedgerTransaction transaction = requireNonNull(
ledgerService.findLedgerTransaction(transactionId),
"Transaction " + transactionId + " not found."
);
// Get the output state from the transaction and use it to create a MessageAndSender Object which
// is appended to the mutable list.
List<ChatState> chatStates = transaction.getOutputStates(ChatState.class);
if (chatStates.size() != 1) throw new CordaRuntimeException(
"Expecting one and only one ChatState output for transaction " + transactionId + ".");
ChatState output = chatStates.get(0);
messages.add(new MessageAndSender(output.getMessageFrom().toString(), output.getMessage()));
// Decrement the number of records to fetch.
recordsToFetch--;
// Get the reference to the input states.
List<StateAndRef<?>> inputStateAndRefs = transaction.getInputStateAndRefs();
// Check if there are no more input states (start of chain) or we have retrieved enough records.
// Check the transaction is not malformed by having too many input states.
// Set the currentStateAndRef to the input StateAndRef, then repeat the loop.
if (inputStateAndRefs.isEmpty() || recordsToFetch == 0) {
moreBackchain = false;
} else if (inputStateAndRefs.size() > 1) {
throw new CordaRuntimeException("More than one input state found for transaction " + transactionId + ".");
} else {
currentStateAndRef = inputStateAndRefs.get(0);
}
}
return messages;
}
}
/*
RequestBody for triggering the flow via REST:
{
"clientRequestId": "get-1",
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.GetChatFlow",
"requestBody": {
"id":"** fill in id **",
"numberOfRecords":"4"
}
}
*/

View File

@ -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;
}
}

View File

@ -1,58 +0,0 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow;
import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
public class ListChatsFlow implements ClientStartableFlow {
private final static Logger log = LoggerFactory.getLogger(ListChatsFlow.class);
@CordaInject
public JsonMarshallingService jsonMarshallingService;
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
@CordaInject
public UtxoLedgerService utxoLedgerService;
@Suspendable
@Override
public String call(ClientRequestBody requestBody) {
log.info("ListChatsFlow.call() called");
// Queries the VNode's vault for unconsumed states and converts the result to a serializable DTO.
List<StateAndRef<ChatState>> states = utxoLedgerService.findUnconsumedStatesByType(ChatState.class);
List<ChatStateResults> results = states.stream().map( stateAndRef ->
new ChatStateResults(
stateAndRef.getState().getContractState().getId(),
stateAndRef.getState().getContractState().getChatName(),
stateAndRef.getState().getContractState().getMessageFrom().toString(),
stateAndRef.getState().getContractState().getMessage()
)
).collect(Collectors.toList());
// Uses the JsonMarshallingService's format() function to serialize the DTO to Json.
return jsonMarshallingService.format(results);
}
}
/*
RequestBody for triggering the flow via REST:
{
"clientRequestId": "list-1",
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.ListChatsFlow",
"requestBody": {}
}
*/

View File

@ -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;
}
}

View File

@ -1,120 +0,0 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
import net.corda.v5.application.flows.ClientRequestBody;
import net.corda.v5.application.flows.ClientStartableFlow;
import net.corda.v5.application.flows.CordaInject;
import net.corda.v5.application.flows.FlowEngine;
import net.corda.v5.application.marshalling.JsonMarshallingService;
import net.corda.v5.application.membership.MemberLookup;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.exceptions.CordaRuntimeException;
import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
import net.corda.v5.membership.MemberInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import static java.util.Objects.*;
import static java.util.stream.Collectors.toList;
// See Chat CorDapp Design section of the getting started docs for a description of this flow.
public class UpdateChatFlow implements ClientStartableFlow {
private final static Logger log = LoggerFactory.getLogger(UpdateChatFlow.class);
@CordaInject
public JsonMarshallingService jsonMarshallingService;
@CordaInject
public MemberLookup memberLookup;
// Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
@CordaInject
public UtxoLedgerService ledgerService;
// FlowEngine service is required to run SubFlows.
@CordaInject
public FlowEngine flowEngine;
@Suspendable
@Override
public String call(ClientRequestBody requestBody) {
log.info("UpdateNewChatFlow.call() called");
try {
// Obtain the deserialized input arguments to the flow from the requestBody.
UpdateChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, UpdateChatFlowArgs.class);
// Look up the latest unconsumed ChatState with the given id.
// Note, this code brings all unconsumed states back, then filters them.
// This is an inefficient way to perform this operation when there are a large number of chats.
// Note, you will get this error if you input an id which has no corresponding ChatState (common error).
List<StateAndRef<ChatState>> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
List<StateAndRef<ChatState>> chatStateAndRefsWithId = chatStateAndRefs.stream()
.filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList());
if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found");
StateAndRef<ChatState> chatStateAndRef = chatStateAndRefsWithId.get(0);
// Get MemberInfos for the Vnode running the flow and the otherMember.
MemberInfo myInfo = memberLookup.myInfo();
ChatState state = chatStateAndRef.getState().getContractState();
List<MemberInfo> members = state.getParticipants().stream().map(
it -> requireNonNull(memberLookup.lookup(it), "Member not found from public Key "+ it + ".")
).collect(toList());
members.remove(myInfo);
if(members.size() != 1) throw new RuntimeException("Should be only one participant other than the initiator");
MemberInfo otherMember = members.get(0);
// Create a new ChatState using the updateMessage helper function.
ChatState newChatState = state.updateMessage(myInfo.getName(), flowArgs.getMessage());
// Use UTXOTransactionBuilder to build up the draft transaction.
UtxoTransactionBuilder txBuilder = ledgerService.createTransactionBuilder()
.setNotary(chatStateAndRef.getState().getNotaryName())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(newChatState)
.addInputState(chatStateAndRef.getRef())
.addCommand(new ChatContract.Update())
.addSignatories(newChatState.getParticipants());
// Convert the transaction builder to a UTXOSignedTransaction. Verifies the content of the
// UtxoTransactionBuilder and signs the transaction with any required signatories that belong to
// the current node.
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction();
// Call FinalizeChatSubFlow which will finalise the transaction.
// If successful the flow will return a String of the created transaction id,
// if not successful it will return an error message.
return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName()));
}
// Catch any exceptions, log them and rethrow the exception.
catch (Exception e) {
log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage());
throw e;
}
}
}
/*
RequestBody for triggering the flow via REST:
{
"clientRequestId": "update-1",
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.UpdateChatFlow",
"requestBody": {
"id":" ** fill in id **",
"message": "How are you today?"
}
}
*/

View File

@ -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;
}
}

View File

@ -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"));
// }
//}