I am creating a flow to fetch signatures from CollectSignaturesFlow of other party and I am facing the below issue in log.
#InitiatingFlow
#StartableByRPC
public static class BGInitiator extends FlowLogic<SignedTransaction> {
private final Party manufacturer;
private final Party regulator;
private final String bgData;
public BGInitiator(Party manufacturer,Party regulator, String bgData) {
this.manufacturer = manufacturer;
this.regulator = regulator;
this.bgData = bgData;
}
private final Step GENERATING_TRANSACTION = new Step("Generating transaction based on YO.");
private final Step BUILDING_TRANSACTION = new Step("Verifying contract constraints.");
private final Step SIGNING_TRANSACTION = new Step("Signing transaction with our private key.");
private final Step GATHERING_SIGS = new Step("Gathering the counterparty's signature.") {
#Override
public ProgressTracker childProgressTracker() {
return CollectSignaturesFlow.Companion.tracker();
}
};
private final Step FINALISING_TRANSACTION = new Step("Obtaining notary signature and recording transaction.") {
#Override
public ProgressTracker childProgressTracker() {
return FinalityFlow.Companion.tracker();
}
};
private final ProgressTracker progressTracker = new ProgressTracker(
GENERATING_TRANSACTION,
BUILDING_TRANSACTION,
SIGNING_TRANSACTION,
GATHERING_SIGS,
FINALISING_TRANSACTION
);
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
#Suspendable
#Override
public SignedTransaction call() throws FlowException {
progressTracker.setCurrentStep(GENERATING_TRANSACTION);
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
BGState bgState = new BGState(getOurIdentity(),manufacturer,regulator,bgData);
progressTracker.setCurrentStep(BUILDING_TRANSACTION);
final List<PublicKey> requiredSigners = bgState.getParticipantKeys();
final List<Party> parties = bgState.getParties();
final PublicKey me = bgState.getSeller().getOwningKey();
final TransactionBuilder tb = new TransactionBuilder(notary)
.addOutputState(bgState,BGContract.BG_CONTRACT_ID)
.addCommand(new BGContract.Commands.Send(),requiredSigners);
progressTracker.setCurrentStep(SIGNING_TRANSACTION);
final SignedTransaction ptx = getServiceHub().signInitialTransaction(tb,me);
progressTracker.setCurrentStep(GATHERING_SIGS);
FlowSession manufacturerflow = initiateFlow(manufacturer);
final SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx,ImmutableSet.of(manufacturerflow),ImmutableList.of(me),GATHERING_SIGS.childProgressTracker()));
progressTracker.setCurrentStep(FINALISING_TRANSACTION);
return subFlow(new FinalityFlow(stx,FINALISING_TRANSACTION.childProgressTracker()));
}
}
After deploying and executing, the flow stops, giving me the following error:
java.lang.IllegalArgumentException: The Initiator of CollectSignaturesFlow must pass in exactly the sessions required to sign the transaction.
at net.corda.core.flows.CollectSignaturesFlow.call(CollectSignaturesFlow.kt:108) ~[corda-core-2.0.0.jar:?]
at net.corda.core.flows.CollectSignaturesFlow.call(CollectSignaturesFlow.kt:64) ~[corda-core-2.0.0.jar:?]
at net.corda.core.flows.FlowLogic.subFlow(FlowLogic.kt:243) ~[corda-core-2.0.0.jar:?]
at com.example.flow.BGFlow$BGInitiator.call(BGFlow.java:107) ~[java-source-0.1.jar:?]
I believe I am passing the required flow session and I am still getting this. Any ideas on how to solve this?
Edit 1: when I replace the flowsession to multiple sessions using the code below and executing it, the flow struck and even wrote nothing in logs. I would like to know whether the following is the correct way to fetch signatures.
List<FlowSession> flowSessions = parties.stream().map(a -> initiateFlow(a)).collect(Collectors.toList());
final SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx,flowSessions,ImmutableList.of(me),GATHERING_SIGS.childProgressTracker()));
The getParties() code in BGState:
public List<Party> getParties(){
return Arrays.asList(manufacturer,regulator);
}
The BGState Definition:
public class BGState implements LinearState,QueryableState {
private final Party seller;
private final Party manufacturer;
private final Party regulator;
private final String senderToReceiverInformation;
private final UniqueIdentifier linearId;
public BGState(Party seller, Party manufacturer,Party regulator,String senderToReceiverInformation) {
this.seller = seller;
this. manufacturer= manufacturer;
this.regulator = regulator;
this.senderToReceiverInformation = senderToReceiverInformation;
this.linearId = new UniqueIdentifier();
}
public Party getSeller() {
return seller;
}
public Party getManufacturer() {
return manufacturer;
}
public Party getRegulator() {
return regulator;
}
#NotNull
#Override
public UniqueIdentifier getLinearId() {
return linearId;
}
#NotNull
#Override
public PersistentState generateMappedObject(MappedSchema schema) {
if (schema instanceof BGSchema) {
return new BGSchema.Bg760(
this.seller,
this.manufacturer,
this.regulator,
this.senderToReceiverInformation,
this.linearId
);
} else {
throw new IllegalArgumentException("Unrecognised schema $schema");
}
}
#NotNull
#Override
public Iterable<MappedSchema> supportedSchemas() {
return ImmutableList.of(new BGSchema());
}
#NotNull
#Override
public List<AbstractParty> getParticipants() {
return Arrays.asList(seller,manufacturer,regulator);
}
public List<PublicKey> getParticipantKeys(){
return getParticipants().stream().map(AbstractParty :: getOwningKey).collect(Collectors.toList());
}
public List<Party> getParties(){
return Arrays.asList(manufacturer,regulator);
}
}
The list of FlowSessions passed to CollectSignaturesFlow must correspond exactly to the transaction's required signers.
In this case, no FlowSession was passed for the regulator, who is one of the required signers.
In BGState - your case there are 3 participants so you need to pass other two parties except initiating node FlowSession also in CollectSignaturesFlow.
FlowSession manufacturerflow = initiateFlow(manufacturer);
FlowSession regulator = initiateFlow(manufacturer);
final SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx,ImmutableSet.of(manufacturerflow,regulator),ImmutableList.of(me),GATHERING_SIGS.childProgressTracker()));
Related
I have this code, i want to trigger another node to run specific flow inside on a flow.
When Requester Node run the IssuanceFlow, I want some code in that flow when it will trigger the Approver Node to run ApproverIssuanceFlow. And I want to use the output of ApproverIssuanceFlow as an input in the next step in IssuanceFlow.
public class IssuanceFlow {
#InitiatingFlow
#StartableByRPC
public static class IssuanceInitiator extends FlowLogic<SignedTransaction> {
private Amount<Currency> amount;
public IssuanceInitiator(Amount<Currency> amount) {
this.amount = amount;
}
//STEP GENERATOR
private final ProgressTracker.Step GENERATE_TRX = new ProgressTracker.Step("Generating trx.");
private final ProgressTracker.Step VERIFY_TRX = new ProgressTracker.Step("Verify trx.");
private final ProgressTracker.Step SIGN_TRX = new ProgressTracker.Step("Sign trx.");
private final ProgressTracker.Step GATHER_SIGN = new ProgressTracker.Step("Collecting other sign.") {
#Override
public ProgressTracker childProgressTracker() {
return CollectSignaturesFlow.Companion.tracker();
}
};
private final ProgressTracker.Step FINALIZE_TRX = new ProgressTracker.Step("Notarize and record.") {
#Override
public ProgressTracker childProgressTracker() {
return FinalityFlow.Companion.tracker();
}
};
public ProgressTracker progressTracker = new ProgressTracker(
GENERATE_TRX,
VERIFY_TRX,
SIGN_TRX,
GATHER_SIGN,
FINALIZE_TRX
);
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
#Suspendable
#Override
public SignedTransaction call() throws FlowException {
//obtain notary
final Party notary =getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
progressTracker.setCurrentStep(GENERATE_TRX);
UniqueIdentifier linearID = new UniqueIdentifier();
Date issuanceDate = new Date();
Party approver = getServiceHub().getNetworkMapCache().getPeerByLegalName(CordaX500Name.parse("O=Approver,L=Jakarta,C=ID")); //always choose Central Bank as approver
**// I want to trigger another node to run another flow, and use the output in this flow**
//build output
IssuanceState newIssuance = new IssuanceState(linearID, this.amount, this.getOurIdentity(), approver, issuanceDate);
//build transaction
TransactionBuilder transactionBuilder = new TransactionBuilder(notary)
.addOutputState(newIssuance)
.addCommand(new IssuanceContract.Commands.Issue(), Arrays.asList(getOurIdentity().getOwningKey(), approver.getOwningKey()));
progressTracker.setCurrentStep(VERIFY_TRX);
//verify transaction
transactionBuilder.verify(getServiceHub());
progressTracker.setCurrentStep(SIGN_TRX);
//sign transaction
final SignedTransaction partiallySign = getServiceHub().signInitialTransaction(transactionBuilder);
progressTracker.setCurrentStep(GATHER_SIGN);
//send to counterparty, back with signature
FlowSession otherPartySession = initiateFlow(approver);
final SignedTransaction fullySign = subFlow(new CollectSignaturesFlow(partiallySign,Arrays.asList(otherPartySession)));
progressTracker.setCurrentStep(FINALIZE_TRX);
//notarize, record transaction
final SignedTransaction finalityFlow = subFlow(new FinalityFlow(fullySign, Arrays.asList(otherPartySession)));
return finalityFlow;
}
}
#InitiatedBy(IssuanceInitiator.class)
public static class IssuanceResponder extends FlowLogic<Void> {
private FlowSession otherPartySession;
public IssuanceResponder(FlowSession otherPartySession) {
this.otherPartySession = otherPartySession;
}
#Override
#Suspendable
public Void call() throws FlowException {
SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(otherPartySession) {
#Override
#Suspendable
protected void checkTransaction(#NotNull SignedTransaction stx) throws FlowException {
}
});
//stored to db
subFlow(new ReceiveFinalityFlow(otherPartySession,signedTransaction.getId()));
return null;
}
}
You cannot trigger anything in other nodes, it would be a big breach of security if a party could trigger unexpected behaviors in other parties' nodes. Remember that other nodes are expected to be other companies, competitors, legal entities in general.
What you want to do could be achieved with a simple p2p communication between parties using send or sendAndReceive (documentation is here). They allow the initiator flow to send a message to the receiver and the receiver could send back another message. That response could be the input for whatever you need to do next.
Still all this needs to be done anyway inside a session in a Flow, so in a transaction between PartyA - the initiator - and PartyB - the receiver.
I'm quite new with Corda.
I want to do a Scheduler, is like a Todo List that check if the Task is assigned every 30 seconds.
I use the Java Template without any specific configuration, the database is H2 and the Corda version is 4.9
The State class extend ContractState, LinearState and SchedulableState
ToDoState
#BelongsToContract(ToDoContract.class)
public class TodoState implements ContractState, LinearState, SchedulableState {
private final Instant deadlineReminder;
public Party getAssignedBy() {
return assignedBy;
}
private final Party assignedBy;
public Party getAssignedTo() {
return assignedTo;
}
private final Party assignedTo;
public String getTaskDescription() {
return taskDescription;
}
private final String taskDescription;
private UniqueIdentifier linearId;
public TodoState(Party assignedBy, Party assignedTo, String taskDescription) {
this.assignedBy = assignedBy;
this.assignedTo = assignedTo;
this.taskDescription = taskDescription;
this.linearId = new UniqueIdentifier();
this.deadlineReminder = Instant.now().plusSeconds(30);
}
#ConstructorForDeserialization
public TodoState(Party assignedBy, Party assignedTo, String taskDescription, UniqueIdentifier linearId, Instant deadlineReminder) {
this.assignedBy = assignedBy;
this.assignedTo = assignedTo;
this.taskDescription = taskDescription;
this.linearId = linearId;
this.deadlineReminder = deadlineReminder;
}
public TodoState assign(Party assignedTo) {
return new TodoState(assignedBy, assignedTo, taskDescription, linearId, deadlineReminder);
}
#NotNull
#Override
public List<AbstractParty> getParticipants() {
return Arrays.asList(assignedBy, assignedTo);
}
#NotNull
#Override
public UniqueIdentifier getLinearId() {
return linearId;
}
#Nullable
#Override
public ScheduledActivity nextScheduledActivity(#NotNull StateRef thisStateRef, #NotNull FlowLogicRefFactory flowLogicRefFactory) {
System.out.println("nextScheduledActivity invoked");
System.out.println("StateRef TX is " + thisStateRef.getTxhash());
final ScheduledActivity scheduledActivity = new ScheduledActivity(flowLogicRefFactory.create(
"com.template.flows.AlarmFlow", thisStateRef
), deadlineReminder);
System.out.println("Passed");
return scheduledActivity;
}
}
The Flow
public class CreateToDoFlow {
#InitiatingFlow
#StartableByRPC
public static class CreateTodoFlowInitiator extends FlowLogic<Void> {
private final String taskDescription;
private Party me;
public CreateTodoFlowInitiator(String task) {
this.taskDescription = task;
}
#Override
#Suspendable
public Void call() throws FlowException {
this.me = getOurIdentity();
final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB"));
final TodoState output = new TodoState(this.me, this.me, this.taskDescription);
final TransactionBuilder builder = new TransactionBuilder(notary);
builder.addOutputState(output);
builder.addCommand(new Command.CreateToDoCommand(), me.getOwningKey());
builder.verify(getServiceHub());
final SignedTransaction ptx = getServiceHub().signInitialTransaction(builder);
subFlow(new FinalityFlow(ptx, Collections.<FlowSession>emptySet()));
System.out.println("1");
return null;
}
}
}
And the AlarmFlow called from the Scheduler, where, i guess the error come.
AlarmFlow
public class AlarmFlow {
#InitiatingFlow
#SchedulableFlow
public static class AlarmFlowInitiator extends FlowLogic<Void> {
private StateRef stateRef;
//public constructor
public AlarmFlowInitiator(StateRef stateRef) {
this.stateRef = stateRef;
}
#Override
#Suspendable
public Void call() throws FlowException {
ServiceHub sb = getServiceHub();
StateAndRef<TodoState> todoStateAndRef = sb.toStateAndRef(stateRef);
TodoState todo = todoStateAndRef.getState().getData();
sb.getVaultService().addNoteToTransaction(
stateRef.getTxhash(), "Reminder made: " + Instant.now()
);
System.out.println("DeadLine is coming up for task: " + todo.getTaskDescription());
return null;
}
}
}
when i execute the flow
flow start CreateTodoFlow task: "Pay bill"
i get the following error
[ERROR] 23:48:19+0200 [Node thread-1] vault.NodeVaultService. - Failed to record transaction states locally - the node could be now in an inconsistent state with other peers and/or the notary - hospitalising the flow {actor_id=internalShell, actor_owning_identity=O=PartyA, L=London, C=GB, actor_store_id=NODE_CONFIG, fiber-id=10000001, flow-id=5eb282b3-4b47-459d-917d-06ea0de16e6f, invocation_id=aadb5b6a-4716-4875-a18e-78a351592365, invocation_timestamp=2022-09-19T21:48:19.052Z, origin=internalShell, session_id=b78e7e1d-b91f-4c40-b42e-468e7b5f4fb0, session_timestamp=2022-09-19T21:48:18.782Z, thread-id=139, tx_id=1BBDFF4EC549457D1C8D60E30041AE97436D44778675BEB2C78E737DBFFFE124}
seems that the vault fail when is called in the Scheduled Activity method
Thanks in advance for your help
I have found the solution:
The scheduler call the AlarmFlow Class and the class is embebbed on the AlarmFlow class like that:
public class AlarmFlow {
#InitiatingFlow
#SchedulableFlow
public static class AlarmFlowInitiator extends FlowLogic<Void> {
private StateRef stateRef;
//public constructor
public AlarmFlowInitiator(StateRef stateRef) {
this.stateRef = stateRef;
}
#Override
#Suspendable
public Void call() throws FlowException {
ServiceHub sb = getServiceHub();
StateAndRef<TodoState> todoStateAndRef = sb.toStateAndRef(stateRef);
TodoState todo = todoStateAndRef.getState().getData();
sb.getVaultService().addNoteToTransaction(
stateRef.getTxhash(), "Reminder made: " + Instant.now()
);
System.out.println("DeadLine is coming up for task: " + todo.getTaskDescription());
return null;
}
}
}
If i extract the AlarmFlowInitiator class out side like that
#InitiatingFlow
#SchedulableFlow
public class AlarmFlow extends FlowLogic<Void>{
private StateRef stateRef;
//public constructor
public AlarmFlow(StateRef stateRef) {
this.stateRef = stateRef;
}
#Override
#Suspendable
public Void call() throws FlowException {
ServiceHub sb = getServiceHub();
StateAndRef<TodoState> todoStateAndRef = sb.toStateAndRef(stateRef);
TodoState todo = todoStateAndRef.getState().getData();
sb.getVaultService().addNoteToTransaction(
stateRef.getTxhash(), "Reminder made: " + Instant.now()
);
System.out.println("DeadLine is coming up for task: " + todo.getTaskDescription());
return null;
}
}
the Scheduler work properly
I need to update a state in Corda, once after it is created. I am using LinearState class for this, which have a LinearID field of type UniqueIdentifier. I am facing an issue in how to pass the UniqueIdentifier of the state to be consumed to the constructor of the UpdateFlow in the node's terminal. The code for my Update Flow is as follows:
#InitiatingFlow
#StartableByRPC
public static class UpdateOffer extends FlowLogic<SignedTransaction> {
private final ProgressTracker.Step GENERATING_TRANSACTION = new ProgressTracker.Step("Generating transaction.");
private final ProgressTracker.Step ADDING_NEW_OFFER = new ProgressTracker.Step("Adding the New Offer State.");
private final ProgressTracker.Step VERIFYING_TRANSACTION = new ProgressTracker.Step("Verifying contract constraints.");
private final ProgressTracker.Step SIGNING_TRANSACTION = new ProgressTracker.Step("Signing transaction with our private key.");
private final ProgressTracker.Step GATHERING_SIGS = new ProgressTracker.Step("Gathering the counterparty's signature.") {
#Override
public ProgressTracker childProgressTracker() {
return CollectSignaturesFlow.Companion.tracker();
}
};
private final ProgressTracker.Step FINALISING_TRANSACTION = new ProgressTracker.Step("Obtaining notary signature and recording transaction.") {
#Override
public ProgressTracker childProgressTracker() {
return FinalityFlow.Companion.tracker();
}
};
private final ProgressTracker progressTracker = new ProgressTracker(
GENERATING_TRANSACTION,
ADDING_NEW_OFFER,
VERIFYING_TRANSACTION,
SIGNING_TRANSACTION,
GATHERING_SIGS,
FINALISING_TRANSACTION
);
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
//Class Variables
private final UniqueIdentifier linearID;
private final String sender;
private final String receiver;
private final String policyID;
private final double faceValue;
private final double offeredAmount;
public UpdateOffer(UniqueIdentifier linearID, String sender, String receiver, String policyID, double faceValue, double offeredAmount) {
this.linearID = linearID;
this.sender = sender;
this.receiver = receiver;
this.policyID = policyID;
this.faceValue = faceValue;
this.offeredAmount = offeredAmount;
}
#Override
public SignedTransaction call() throws FlowException {
List<UUID> listOfLinearIds = new ArrayList<>();
listOfLinearIds.add(linearID.getId());
QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(null, listOfLinearIds);
// 2. Get a reference to the inputState data that we are going to settle.
Vault.Page results = getServiceHub().getVaultService().queryBy(OfferState.class, queryCriteria);
StateAndRef inputStateAndRefToTransfer = (StateAndRef) results.getStates().get(0);
OfferState inputStateToTransfer = (OfferState) inputStateAndRefToTransfer.getState().getData();
AccountService accountService = getServiceHub().cordaService(KeyManagementBackedAccountService.class);
//Owner Account
AccountInfo lspAccountInfo = accountService.accountInfo(sender).get(0).getState().getData();
PublicKey lspKey = subFlow(new NewKeyForAccount(lspAccountInfo.getIdentifier().getId())).getOwningKey();
AnonymousParty lspAccount = subFlow(new RequestKeyForAccount(lspAccountInfo));
//Insurance Company Account
AccountInfo sellerAccountInfo = accountService.accountInfo(receiver).get(0).getState().getData();
AnonymousParty sellerAccount = subFlow(new RequestKeyForAccount(sellerAccountInfo));
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
final OfferState output = new OfferState(inputStateToTransfer.getLinearId(), lspAccount,sellerAccount,policyID,faceValue,offeredAmount, true);
progressTracker.setCurrentStep(GENERATING_TRANSACTION);
TransactionBuilder builder = new TransactionBuilder(notary);
progressTracker.setCurrentStep(ADDING_NEW_OFFER);
builder.addInputState(inputStateAndRefToTransfer);
builder.addOutputState(output,OfferContract.ID);
builder.addCommand(new OfferContract.Commands.Send(), Arrays.asList(lspKey,sellerAccount.getOwningKey()));
progressTracker.setCurrentStep(SIGNING_TRANSACTION);
builder.verify(getServiceHub());
SignedTransaction locallySignedTx = getServiceHub().signInitialTransaction(builder,Arrays.asList(getOurIdentity().getOwningKey(),lspKey));
progressTracker.setCurrentStep(GATHERING_SIGS);
FlowSession session = initiateFlow(sellerAccountInfo.getHost());
List<TransactionSignature> accountToMoveToSignature = (List<TransactionSignature>) subFlow(new CollectSignatureFlow(locallySignedTx,
session,sellerAccount.getOwningKey()));
SignedTransaction signedByCounterParty = locallySignedTx.withAdditionalSignatures(accountToMoveToSignature);
progressTracker.setCurrentStep(FINALISING_TRANSACTION);
return subFlow(new FinalityFlow(signedByCounterParty, session));
}
}
Can anyone help me with this? I have been stuck here for more than a week now.
Instead of having a UniqueIdentifier input parameter in your flow's constructor, use a String:
public UpdateOffer(String linearID...
Then inside your flow you can query like this:
LinearStateQueryCriteria linearCriteria = new LinearStateQueryCriteria()
.withExternalId(Collections.singletonList(linearId));
Since your input parameter is now a String, you can easily pass it from the terminal.
Suppose there are two nodes, Alice and Bob. Alice has a state she wants to make Bob aware of. How can Alice send the state to Bob and get him to store it in his vault?
You will need both an initiator and a responder flow:
The initiator will retrieve the state from its vault, retrieve the transaction that created this state, and send the transaction to the counterparty to be recorded
The responder will record the transaction, storing all its states
Initiator flow
#InitiatingFlow
#StartableByRPC
public class Initiator extends FlowLogic<Void> {
private final UUID stateId;
private final Party otherParty;
private final ProgressTracker progressTracker = new ProgressTracker();
public Initiator(UUID stateId, Party otherParty) {
this.stateId = stateId;
this.otherParty = otherParty;
}
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
#Suspendable
#Override
public Void call() throws FlowException {
// Find the correct state.
LinearStateQueryCriteria criteria = new LinearStateQueryCriteria(null, Collections.singletonList(stateId));
Vault.Page<IOUState> queryResults = getServiceHub().getVaultService().queryBy(IOUState.class, criteria);
if (queryResults.getStates().size() != 1)
throw new IllegalStateException("Not exactly one match for the provided ID.");
StateAndRef<IOUState> stateAndRef = queryResults.getStates().get(0);
// Find the transaction that created this state.
SecureHash creatingTransactionHash = stateAndRef.getRef().getTxhash();
SignedTransaction creatingTransaction = getServiceHub().getValidatedTransactions().getTransaction(creatingTransactionHash);
// Send the transaction to the counterparty.
final FlowSession counterpartySession = initiateFlow(otherParty);
subFlow(new SendTransactionFlow(counterpartySession, creatingTransaction));
return null;
}
}
Responder flow
#InitiatedBy(Initiator.class)
public class Responder extends FlowLogic<Void> {
private final FlowSession counterpartySession;
public Responder(FlowSession counterpartySession) {
this.counterpartySession = counterpartySession;
}
#Suspendable
#Override
public Void call() throws FlowException {
// Receive the transaction and store all its states.
// If we don't pass `ALL_VISIBLE`, only the states for which the node is one of the `participants` will be stored.
subFlow(new ReceiveTransactionFlow(counterpartySession, true, StatesToRecord.ALL_VISIBLE));
return null;
}
}
I have a Corda flow where the responder Party needs to add components to the transaction (for example, the responder may have to add their own cash to the transaction to pay for an asset in a trade).
How can I allow the responder to add these inputs?
Below is an example of how this is done. In summary:
The initiator uses a send to send the responder the parameters of the state to add
The responder uses a receive to receive these parameters and creates the state
The responder uses a send to send the created state back to the initiator
The initiator adds the state to the transaction builder and continues with the flow as usual
When the responder is asked to sign the transaction as part of CollectSignaturesFlow, they check that the state they provided and the transaction generally are as expected
Initiator code
#InitiatingFlow
#StartableByRPC
public class IOUFlowInitiator extends FlowLogic<SignedTransaction> {
private final int iouValue;
private final Party otherParty;
private final ProgressTracker progressTracker = new ProgressTracker();
public IOUFlowInitiator(int iouValue, Party otherParty) {
this.iouValue = iouValue;
this.otherParty = otherParty;
}
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
#Suspendable
#Override
public SignedTransaction call() throws FlowException {
// Get the counterparty to create the output IOUState.
final FlowSession otherPartySession = initiateFlow(otherParty);
final IOUState iouState = otherPartySession.sendAndReceive(IOUState.class, iouValue).unwrap(wrappedIOU -> wrappedIOU);
// Create the command.
final List<PublicKey> requiredSigners = Arrays.asList(iouState.getLender().getOwningKey(), iouState.getBorrower().getOwningKey());
final Command<IOUContract.Commands.Create> txCommand = new Command<>(
new IOUContract.Commands.Create(), requiredSigners);
// Build the transaction.
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
final TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addOutputState(iouState, IOU_CONTRACT_ID)
.addCommand(txCommand);
// Verify, sign and finalise.
txBuilder.verify(getServiceHub());
final SignedTransaction partSignedTx = getServiceHub().signInitialTransaction(txBuilder);
final SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(partSignedTx, ImmutableSet.of(otherPartySession), CollectSignaturesFlow.Companion.tracker()));
return subFlow(new FinalityFlow(fullySignedTx));
}
}
Responder code
#InitiatedBy(IOUFlowInitiator.class)
public class IOUFlowResponder extends FlowLogic<SignedTransaction> {
private final FlowSession counterpartySession;
public IOUFlowResponder(FlowSession counterpartySession) {
this.counterpartySession = counterpartySession;
}
#Suspendable
#Override
public SignedTransaction call() throws FlowException {
// Receive the IOU's value from the counterparty.
Integer iouValue = counterpartySession.receive(Integer.class).unwrap(wrappedInt -> wrappedInt);
// Create the output IOUState.
Party otherParty = counterpartySession.getCounterparty();
IOUState iouState = new IOUState(iouValue, getOurIdentity(), otherParty, new UniqueIdentifier());
// Send the output IOUState to the counterparty.
counterpartySession.send(iouState);
// Sign the resulting transaction if it meets expectations.
return subFlow(new CheckIOUAndSignFlow(iouState, counterpartySession, SignTransactionFlow.Companion.tracker()));
}
}
class CheckIOUAndSignFlow extends SignTransactionFlow {
private final IOUState expectedIOU;
CheckIOUAndSignFlow(IOUState expectedIOU, FlowSession otherPartyFlow, ProgressTracker progressTracker) {
super(otherPartyFlow, progressTracker);
this.expectedIOU = expectedIOU;
}
#Override
protected void checkTransaction(SignedTransaction stx) throws FlowException {
LedgerTransaction ltx = null;
try {
ltx = stx.toLedgerTransaction(getServiceHub(), false);
} catch (SignatureException e) {
throw new FlowException("Transaction had invalid signature.");
}
// Check that the resulting transaction meets expectations.
List<IOUState> outputs = ltx.outputsOfType(IOUState.class);
List<Command<IOUContract.Commands.Create>> commands = ltx.commandsOfType(IOUContract.Commands.Create.class);
if (outputs.size() != 1) throw new FlowException("Expected single IOU output in the transaction.");
if (commands.size() != 1) throw new FlowException("Expected single IOU Create command in the transaction.");
IOUState outputState = outputs.get(0);
if (!outputState.equals(expectedIOU)) throw new FlowException("IOU does not match expected IOU..");
}
}