TokenPointer’s & SendStateAndRefFlow()/ReceiveStateAndRefFlow() - corda

Setup:
Corda 4.6
Testing with MockNodes
Scenario:
I am building atomic swaps where I exchange FungibleToken’s that point to an EvolvableTokenType for FungibleToken’s representing USD.
In the past I have always used SendTransactionFlow()/ReceiveTransactionFlow() like this:
SignedTransaction stx = subFlow(new ReceiveTransactionFlow(otherSideSession, true, StatesToRecord.ALL_VISIBLE));
It works great because it saves all the states to the vault on the receiver’s node – including the reference states (i.e. the EvolvableTokenType that the FungibleToken’s points to) . However in this stackoverflow answer Mike Hearn mentioned:
“You may also use SendStateAndRefFlow, which will reduce the amount of
migration work involved in supporting SGX ledger encryption in
future.”
So I am trying to switch to SendStateAndRefFlow()/ReceiveStateAndRefFlow().
Problem:
I cannot force the states to save in the vault using ReceiveStateAndRefFlow(). Only the transaction chain is stored in transaction storage.
When I try to add the FungibleToken’s that point to an EvolvableTokenType to the TransactionBuilder on the receiver’s node (the node constructing the swap):
List<StateAndRef<FungibleToken>> inputs = subFlow(new ReceiveStateAndRefFlow<>(otherSideSession));
txBuilder.addInputState(inputs.get(0));
… I will get an error:
java.lang.IllegalStateException: The LinearState with ID 598e1d3e-3b89-428c-b343-21c54a066856 is unknown to this node or it has been exited from the ledger.
The UUID the error refers to is the LinearId of the EvolvableTokenType the FungibleToken’s are pointing to.
Questions:
Are Mike’s comments still valid? Should I avoid using SendTransactionFlow/ReceiveTransactionFlow because SGX will break its functionality?
How can I send and save the EvolvableTokenType the FungibleToken’s point to so the state is available to the TransactionBuilder?

I would still appreciate if R3 or a more experienced CorDapp developer weighed in on this and answered my first question. I am curious if the move to SGX would also break the solution I settled on because its basically a manual SendTransactionFlow/ReceiveTransactionFlow:
Solution 1:
Use the same code in the documentation that informs Observers:
On the node that is selling the FungibleToken's (they have a copy of the SignedTransaction that created the EvolvableTokenType the FungibleToken's point to)
SignedTransaction forwardFtStx = getServiceHub().getValidatedTransactions().getTransaction(forwardFtStateAndRef.getRef().getTxhash());
if(forwardFtStx != null) {
otherSideSession.send(forwardFtStx);
} else {
throw new FlowException("Unable to locate SignedTransaction that created ForwardFt");
}
On the node that is buying the FungibleToken's:
SignedTransaction forwardFtStx = otherSideSession.receive(SignedTransaction.class).unwrap(stx -> {
if (stx.getId().equals(forwardFtStateAndRef.getRef().getTxhash())) {
return stx;
} else {
throw new FlowException("SignedTransaction received by seller that created ForwardFt does not match StateAndRef<ForwardFt> sent earlier");
}
});
getServiceHub().recordTransactions(StatesToRecord.ALL_VISIBLE, Collections.singletonList(forwardFtStx));
Solution 2:
You could reverse the transaction so the node that has the EvolvableTokenType in its vault builds the TransactionBuilder. The problem with this approach is it won’t handle situations where FungibleToken’s that point to an EvolvableTokenType are being traded for different FungibleToken’s that point to an EvolvableTokenType.

Related

If a StateRef is consumed how can I locate the Transaction that consumed it?

Background:
This question has been asked and answered here and here. Both replies state that there is no way to locate the Transaction that consumed a State with the current API.
Question:
Any application I am thinking about building on Corda would require users to examine the “lifecycle” of a state for audit/legal purposes.
Transactions are essential to piece together that audit trail. If this functionality has not been built into the API I am concerned that R3 is trying to tell me that users/CorDapps should not be allowed to search the Transactions in their vault. For example I currently make heavy use of CordaRPCOps.internalFindVerifiedTransaction() which is deprecated.
Is there a specific reason that we can’t search Transactions with a rich set of tools like we search for States? Am I overlooking some security concern? How would I prove to a lawyer that I’ve transferred an asset if I can’t pull up the Transaction that shows what Party received the asset along with their signature?
In the first question that you referenced, Ashutosh provided a high-level solution but there was no follow up from the person that posted the question (not cool and unprofessional); the second question references Corda 3; we're now at Corda 4.6! This was ages ago.
Just because CordaRPSOps doesn't have a certain API, doesn't mean you can't achieve your goal.
You can write a flow that executes your requirement, then call that flow with your RPC proxy:
List<SignedTransaction> auditTrail = proxy.startFlowDynamic(AuditTrailFlow.class,
linearId);
Inside your flow you can do something like this:
// Criteria to get CONSUMED and UNCONSUMED states.
QueryCriteria statusAllCriteria = new VaultQueryCriteria(Vault.StateStatus.ALL);
// Criteria to get a certain LinearState by its linearId.
LinearStateQueryCriteria linearStateCriteria = new LinearStateQueryCriteria()
.withExternalId(Collections.singletonList(linearId));
// By default Corda returns the results in pages of 200 records,
// you can modify that default size; and you must loop through the pages
// to get all of the results.
Vault.Page<YourState> results = getServiceHub().getVaultService()
.queryBy(YourState.class, statusAllCriteria.and(linearStateCriteria));
// This is a simplified version where I get the first record in the result set;
// again, you must loop through all the pages if you have more than 200 records
// (it's highly unlikely that your state was updated more than 200 times though).
SecureHash txHash = results.getStates().get(0).getRef().getTxhash();
// Now you have the transaction that created your state
// (i.e. the transaction that consumed a previous version of your state
// and created the new version of your state).
SignedTransaction tx = getServiceHub().getValidatedTransactions()
.getTransaction(txHash);
Now it's up to you to decide what your flow returns, it can for instance return a List<SignedTransaction> which contains all the transactions that form the audit trail of your state.
The method Adel has provided is actually why I created this question. I was doing exactly what he outlines and stumbled onto the fact that it only works about ~75% of the time. Let me elaborate…
Adel is correct – anytime you need functionality for your RPC client that hasn’t been provided through the API but is possible using the ServiceHub you can create a flow, annotate it with #StartableByRPC and perform the necessary steps that return a result. Then simply call that flow from your RPC client. I was highlighting the deprecation of cordaRPCOps.internalFindVerifiedTransaction() more as indicator that R3 is moving towards less ability to search Transactions from RPC versus more.
I’m still really new to Corda but generally my States follow a consistent “Lifecycle”. This is especially true of tokens. Let’s discuss each stage and whether the method that I/Adel am using will work:
Stage 1 - creation/issue
This method will always work because the Transaction will produce an output State that you will be a Participant of so you will save it in your vault. When you search for the LinearID this State will be found and you can cross-reference to the Transaction.
Stage 2 – updating/moving
This method might work depending on the nature of your CordApp and how you’ve written your flows. For example the MoveNonFungibleTokens() flow in the TokenSDK moves something out of your vault to someone else’s vault. You will not be a Participant in this new State so there will be no record of it in your vault. When you search your vault for the LinearID it will not return any State that you can cross-reference to this Transaction.
I think this situation can be avoided if you build your flows differently. For example by including a StatesToRecord.ALL_VISIBLE in the FinalityFlow you can force the storage of States that you are not a Participant of. I haven’t gone through the code but I assume R3 chose not to do that with the MoveNonFungibleTokens() flow.
Stage 3 – deletion/redeeming
This method doesn’t work because there is no output State produced. So when you search your vault for the LinearID it will not return any State that you can cross-reference to this Transaction. The only way I have come up with to find these Transactions is to:
Acquire the ConsumedTime in the StateMetaData returned from a QueryCriteria.VaultQueryCriteria
Use this time to query the NODE_TRANSACTIONS table by TIMESTAMP and grab the TX_ID
Examine the Inputs of each Transaction returned from that query and see if they contain the LinearID I am looking for
(If anyone has a better idea please let me know)
I hope I haven’t made some rookie mistake in my analysis/testing. It would be great if Corda saved the consuming Transaction ID along with the other MetaData. That would make it easy to locate transactions under all circumstances.
Adel I really appreciate the time you took to answer my question. I am not going to mark it as solved because I am hoping you or someone else can show me that I have overlooked something. If there isn’t a better way maybe someone from R3 will see this and consider it.
Appendix
Below I’ve included some logging I use to help analyze transactions. It’s a nice visual way to see some of the examples I am talking about if you are just getting into Corda. If it is confusing just ignore it.
Here is a Transaction where PartyA moves a NonFungibleToken to PartyB. Note that the output state doesn't have PartyA listed as a Participant which means PartyA will not store this state. So if you search for the LinearID nothing will be found:
______________________________________________________________________________________
SignedTransaction Info from node: PartyA...
_____________________________________________________________________________________
- getID: 8AD8CEE3340EE23BA1751C24D0652253A8ABB2EA19755704C6A8F42466B14A72 (SecureHash)
- Notes: []
- getNotary: O=Notary, L=London, C=GB (Party)
- # of RequiredSigningKeys: 2 (Set<PublicKey>)
PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg (Base58)
Notary with PublicKey: DL47z5vzXwPzFeMnfHsPiWru7wpWi7c5QmthPw4P8az7Ay (Base58)
- # of MissingSigners: 0 (Set<PublicKey>)
______________________________________________________________________________________
WireTransaction Info from node: PartyA...
______________________________________________________________________________________
- getID: 8AD8CEE3340EE23BA1751C24D0652253A8ABB2EA19755704C6A8F42466B14A72 (SecureHash)
- # of Commands: 1 (List<Command>)
MoveTokenCommand
# of Signers: 1 List<PublicKey>
PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg (Base58)
- # of Inputs: 1 (List<StateRef>)
SecureHash: B4AD7096BDF29628E1D30907DCFF84E68C1AF8344F1781DE7D549B96D112A836 Index: 0
TransactionState<NonFungibleToken>
getData <ContractState>: TokenType(tokenIdentifier='Ruby-3b1f1ccd-b175-48f1-af0f-e4d82015d31e', fractionDigits=0) issued by PartyA held by PartyA
Participants: PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg
getContract (String): com.r3.corda.lib.tokens.contracts.NonFungibleTokenContract
getNotary (Party): O=Notary, L=London, C=GB
getConstraint (AttachmentConstraint): Type - SignatureAttachmentConstraint, PublicKey - DLGqTr8CsXnGGh9d8uve8NWhHsHNm5b6eKzUpNPGqeNVMH (Base58)
getEncumbrance (Int, max allowed = 1): null
- # of Outputs: 1 (List<TransactionState<ContractState>>)
Index 0:
TransactionState<NonFungibleToken>
getData <ContractState>: TokenType(tokenIdentifier='Ruby-3b1f1ccd-b175-48f1-af0f-e4d82015d31e', fractionDigits=0) issued by PartyA held by PartyB
Participants: PartyB with PublicKey: DL5nGmwTZ6jF6FrpNRZ4hbDAW48ut8DxjJ8YZTQw4xY339 !!! PartyA is not listed !!!
getContract (String): com.r3.corda.lib.tokens.contracts.NonFungibleTokenContract
getNotary (Party): O=Notary, L=London, C=GB
getConstraint (AttachmentConstraint): Type - SignatureAttachmentConstraint, PublicKey - DLGqTr8CsXnGGh9d8uve8NWhHsHNm5b6eKzUpNPGqeNVMH (Base58)
getEncumbrance (Int, max allowed = 1): null
- # of Attachments: 1 (List<SecureHash>)
SecureHash: 34705DC9A2C8599998BBDBA7C3D13609AF04D8B3A772F7134D685ECC926D8320 (CordaRPC successfully located Attachment in storage)
- getTimeWindow: null (TimeWindow)
Here is an example of removing a LinearState (redeeming a token looks the same). Note that there is no Outputs so if you search for LinearID no state will be found making it impossible to cross-reference to this transaction:
__________________________________________________________________________________
SignedTransaction Info from node: PartyA...
__________________________________________________________________________________
- getID: 4D09C6CBACD104821BEB3095E90C350FE4EA690FD7F8CEA24B71E847EEE10684 (SecureHash)
- Notes: []
- getNotary: O=Notary, L=London, C=GB (Party)
- # of RequiredSigningKeys: 2 (Set<PublicKey>)
PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg (Base58)
Notary with PublicKey: DL47z5vzXwPzFeMnfHsPiWru7wpWi7c5QmthPw4P8az7Ay (Base58)
- # of MissingSigners: 0 (Set<PublicKey>)
______________________________________________________________________________________
WireTransaction Info from node: PartyA...
______________________________________________________________________________________
- getID: 4D09C6CBACD104821BEB3095E90C350FE4EA690FD7F8CEA24B71E847EEE10684 (SecureHash)
- # of Commands: 1 (List<Command>)
Delete
# of Signers: 1 List<PublicKey>
PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg (Base58)
- # of Inputs: 1 (List<StateRef>)
SecureHash: A45446E1764AB7055B348BC6AAB5E9332BCCFEF21E157A1B28360CC986CDD459 Index: 0
TransactionState<MyLinearState>
getData <ContractState>: linearID: 73fac293-2c89-42fa-8572-7d31e0de567f ExternalID: LinearSate-123 administrator: O=PartyA, L=London, C=GB badPeople: []
Participants: PartyA with PublicKey: DL363eMqRKM8FegtiyEbF1F1zpq4nLp4paaTrKYbMjdKKg
getContract (String): com.template.contracts.MyLinearStateContract
getNotary (Party): O=Notary, L=London, C=GB
getConstraint (AttachmentConstraint): Type - SignatureAttachmentConstraint, PublicKey - DLCTymWiNxvGWE117QUKSjeW71yaaGc5tNL2i8Zp8dFUvE (Base58)
getEncumbrance (Int, max allowed = 1): null
- # of Outputs: 0 (List<TransactionState<ContractState>>) !!! No Outputs !!!
- # of Attachments: 1 (List<SecureHash>)
SecureHash: DB05B1A5B285C73C3C48F81E8D07B8DDF51E7EE9852219D4D65EE934F9524527 (CordaRPC successfully located Attachment in storage)
- getTimeWindow: 22-Oct-2020 15:24:40 to 22-Oct-2020 15:24:50 (TimeWindow)
I just wanted to record what I settled on:
If I build my own Flows from scratch I can force the storage of all states with StatesToRecord.ALL_VISIBLE in the FinalityFlow and then use what Adel outlined to cross reference states to transactions. This will cover Creation/Issue and Change/Move transactions. It will not work for Delete/Redeem transactions because the lack of an output state.
If I am using pre-built flows like in the TokenSDK it is more difficult. If the developers didn't add in a parameter to set the StatesToRecord there may be no solution.
The TokenSDK documentation says that if someone is an Observer their recording strategy will be StatesToRecord.ALL_VISIBLE. I did test this and it works unless you are the Party performing the move/issue. It seems you get filtered out as an Observer.
To solve this I switched to using the inline version i.e. MoveNonFungibleTokensFlow/MoveFungibleTokensHandler. While these flows don’t have a parameter for StatesToRecord I discovered that after I perform the move I could call a FinalityFlow/ReceiveFinalityFlow with StatesToRecord.ALL_VISIBLE...
Mover Node:
SignedTransaction signedTransactionMove = subFlow(new MoveNonFungibleTokensFlow(partyAndToken, participantSessions, observerSessions, queryCriteria));
return subFlow(new FinalityFlow(signedTransactionMove, participantSessions, StatesToRecord.ALL_VISIBLE));
Receiver Node:
subFlow(new MoveFungibleTokensHandler(otherSideSession));
subFlow(new ReceiveFinalityFlow(otherSideSession, null, StatesToRecord.ALL_VISIBLE));
I was really surprised this didn’t cause an error. I thought the notary would complain but it doesn’t. This ensures that the mover will save the moved state (normally they wouldn’t because they aren’t a Participant). Then you can use the solution Adel outlined.
You can also do this for issuing (i.e. when you issue directly to another party you will not be a participant of that state and thus will not save the state)...
Issuer Node:
SignedTransaction signedTransactionIssueToken = subFlow(new IssueTokensFlow(nonFungibleToken, participantSessions, observerSessions));
return subFlow(new FinalityFlow(signedTransactionIssueToken, participantSessions, StatesToRecord.ALL_VISIBLE));
Receiver Node:
subFlow(new IssueTokensFlowHandler(otherSideSession));
subFlow(new ReceiveFinalityFlow(otherSideSession, null, StatesToRecord.ALL_VISIBLE));
I have no solution for “exiting” type Transactions that have no Output i.e. RedeemNonFungibleTokens() because there is no output state that could get saved with StatesToRecord.ALL_VISIBLE. If you do not save the TransactionID outside of Corda at the time of transaction there is no way to locate that transaction with the API.
Ideally R3 would save the consuming Transaction ID in the VAULT_STATES table when it records the CONSUMED_TIMESTAMP. Then it would be easily available in the metata of a vault query. I’m considering putting this in as a request.

Corda - Does Finality flow ends before states were recorded in all participants vaults?

Recently I came across a problem when it seems that when the flow ends on initiator's node, and immediately after that I query the output state of the transaction in vaults of all transaction participant nodes, it turns out that the state is present only on initiator's node, and only after a while it appears in vaults of other participants nodes.
Reading the documentations here "Send the transaction to the counterparty for recording", and it does not say that it will wait until counterparty will successfully record the transaction and its states to their vault, which sort of confirms that the issue that I am facing is actually the way Corda is implemented and not a bug or so.
On the other hand, it seems not that logical to end a flow without being sure that on the counterparty node everything was finished successfully and all the states are written in their vaults.
And also I looked into the code, and reached to this method in ServiceHubInternal, which seems to be responsible for recording states into vault.
fun recordTransactions(statesToRecord: StatesToRecord,
txs: Collection<SignedTransaction>,
validatedTransactions: WritableTransactionStorage,
stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage,
vaultService: VaultServiceInternal,
database: CordaPersistence) {
database.transaction {
require(txs.isNotEmpty()) { "No transactions passed in for recording" }
val orderedTxs = topologicalSort(txs)
val (recordedTransactions, previouslySeenTxs) = if (statesToRecord != StatesToRecord.ALL_VISIBLE) {
orderedTxs.filter(validatedTransactions::addTransaction) to emptyList()
} else {
orderedTxs.partition(validatedTransactions::addTransaction)
}
val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id
if (stateMachineRunId != null) {
recordedTransactions.forEach {
stateMachineRecordedTransactionMapping.addMapping(stateMachineRunId, it.id)
}
} else {
log.warn("Transactions recorded from outside of a state machine")
}
vaultService.notifyAll(statesToRecord, recordedTransactions.map { it.coreTransaction }, previouslySeenTxs.map { it.coreTransaction })
}
}
And it does not seem to me that this method is doing something async, so I am really confused.
And so the actual question is:
Does Initiator flow in Corda actually waits until all the relevant states are recorded in vaults of all participant nodes before it finishes, or it finishes right after it sends the states to participant nodes for recording without waiting for a confirmation from their side that states were recorded?
Edited
So in case that Corda by default does not wait for counterparty flows to store states in their vaults, but my implementation needs this behavior anyways, would it be good solution to implement the following:
At the very end of Initiator flow, before returning from method call receiveAll in order to suspend and wait, then in the very end of the receiver flow, before returning from method do a vault query with trackBy to wait until the state of an interest is recorded in the vault, and whenever it is, call sendAll to notify the initiator's receiveAll method. And finish the initiator's flow only when it has received confirmation from all receivers..
Would it be a normal approach to solve this problem? can it have any drawbacks or side effects that you can think of?
Corda is able to handle your scenario, it is actually explained here under the Error handling behaviour section; below is an excerpt, but I recommend reading the full section:
To recover from this scenario, the receiver’s finality handler will automatically be sent to the Flow Hospital where it’s suspended and retried from its last checkpoint upon node restart
The Initiator flow is not responsible for the storage of the states in the Responder's vault. So, there is no storage confirmation from the Responder, since it has already checked the transaction and provided signatures. So, from the Initiator's point of view it's all good once the Transaction has been notarised and stored on its side, it is up to the Responder to manage errors in its storage phase, as mentioned in the previous comment.

What do we mean by writing multiple responder flows in cordapp?

What is literally happening in multiple responder flow?Are we communicating with multiple nodes?Can we do use multiple responder flow for some other purpose?
Imagine this, you (the initiator) is applying for a loan; you go to 5 different banks (i.e. 5 responders). Every bank has their own criteria to approve your loan application (one bank requires you to own a house as a guarantee, another requires you to have an annual salary higher than 100,000, and so on).
So even though all responding nodes can use the responder flow that was supplied by the organization that wrote the initiating flow; they are not obliged too, actually it is the responsibility of the responding node to write its own version of the responder flow to implement its own business rules.
If the received transaction passes those business rules, the responder approves it (i.e. it signs the transaction); otherwise it rejects it (i.e. throws a FlowException).
I suppose you are referring to the overriding of responder flows. It allows developers to configure a node to respond using an overridden responder flow rather than the base responder.
You could find more details here:
https://docs.corda.net/docs/corda-os/4.5/flow-overriding.html
As the previous comments say, you can choose to create multiple responder flows or having a single one that implements different logics depending on the information stored in the responding node. There are multiple example in the source code of Corda, if you look for flow handlers, like this one below (on github) where you can see that different logics apply depending on the role of the responding node (set by the InitiatingFlow, in this case):
class ObserverAwareFinalityFlowHandler(val otherSession: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
val role = otherSession.receive<TransactionRole>().unwrap { it }
val statesToRecord = when (role) {
TransactionRole.PARTICIPANT -> StatesToRecord.ONLY_RELEVANT
TransactionRole.OBSERVER -> StatesToRecord.ALL_VISIBLE
}
// If states are issued to self, then ReceiveFinalityFlow does not need to be invoked.
if (!serviceHub.myInfo.isLegalIdentity(otherSession.counterparty)) {
subFlow(ReceiveFinalityFlow(otherSideSession = otherSession, statesToRecord = statesToRecord))
}
}
}

Implement the constraints requiring the participants to sign the transaction in the IOUContract code

I was going through Corda training material and there is one activity "Impose a constraint on the required signatures in IOUContract.verify". How to implement this constraint requiring both the participant (lender and borrower) to sign the
transaction?
If anybody is aware of then please let me know.
My code:
if (!((command.getSigners().contains(state.getLender().getOwningKey())) && (command.getSigners().contains(state.getBorrower().getOwningKey()))))
throw new IllegalArgumentException("Both lender and borrower together only may sign IOU issue transaction.");
Your code looks legit.
If you cannot pass the test, please make sure the getter of the lender and the borrower are correct in the IOUState class.
If you are uncertain, you can look into this contract class example.
Also, you can check here for an alternative syntax to implement the contract class.
The code you paste in is from the contract of a cordapp. You will initiate the signature collection in the flow of the cordapp.
Let's assume the case that there are only lender and borrower in this transaction (no third party moderator), and the lender is the flow initiator. The lender will draft the state and sign it himself, and sent a half signed transaction to the borrower via a session and the borrower will check it and sign it via the responder.
At that moment you will have a fully signed transaction.
As for code, I would suggest you to take look at this simple negotiation cordapp for reference. https://github.com/corda/samples/blob/release-V4/negotiation-cordapp/workflows-kotlin/src/main/kotlin/negotiation/flows/AcceptanceFlow.kt
val partStx = serviceHub.signInitialTransaction(txBuilder)
// Gathering the counterparty's signature.
val counterparty = if (ourIdentity == input.proposer) input.proposee else input.proposer
val counterpartySession = initiateFlow(counterparty)
val fullyStx = subFlow(CollectSignaturesFlow(partStx, listOf(counterpartySession)))
The state contract will enforce who are the required signers. In this example, the contract is expecting all participants (i.e. lender and borrower) to be required signers of the Create command.
When the flow is crafting a transaction to create a new IOU, it must specify which command to use (Create IOU in our case); and who will sign this command (so the flow will work on collecting those signatures).
The flow will verify the transaction before signing it, which in return will run the contract; if the requested signatures that were specified on the Create command inside the flow don't match what the contract is expecting to see as required signers; then the contract will fail the transaction. Otherwise the contract will pass the transaction, and the flow will proceed to collect the signature of the lender, then collect the signature of the borrower.
So in short, the contract will say who are the required signers for a certain state command, and the flow that's creating a transaction for that state/command combination must obey the contract rules by specifying the same set of signers and collect their signatures.
Thanks, everyone for the detailed explanation. I have implemented the below code and it's working fine now.
List<PublicKey> signers = tx.getCommands().get(0).getSigners();
HashSet<PublicKey> signersSet = new HashSet<>();
for (PublicKey key: signers) {signersSet.add(key);}
List<AbstractParty> participants = tx.getOutputStates().get(0).getParticipants();
HashSet<PublicKey> participantKeys = new HashSet<>();
for (AbstractParty party: participants) {participantKeys.add(party.getOwningKey());}
require.using("Both lender and borrower together only may sign IOU issue transaction.", signersSet.containsAll(participantKeys) && signersSet.size() == 2);

Where to check contract constraints

From release-M13 of Corda, in the CordApp-Tutorial example, there are some constraints checks made within the flow itself (ExampleFlow.Acceptor). My question is, what constraints may I check on the flow, and what constraints in the Contract? Or it's just an organization issue?
This is a great question. I believe you are referring to:
#InitiatedBy(Initiator::class)
class Acceptor(val otherParty: Party) : FlowLogic<SignedTransaction>() {
#Suspendable
override fun call(): SignedTransaction {
val signTransactionFlow = object : SignTransactionFlow(otherParty) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
val output = stx.tx.outputs.single().data
"This must be an IOU transaction." using (output is IOUState)
val iou = output as IOUState
"The IOU's value can't be too high." using (iou.iou.value < 100)
}
}
return subFlow(signTransactionFlow)
}
}
The CollectSignaturesFlow and its counterpart, the SignTransactionFlow automates the collection of signatures for any type of transaction. This automation is super useful because developers don't have to manually write flows for signature collection anymore! However, developers have to be aware that given any valid transaction - as per the referenced contract code in the transaction - the counter-party will always sign! This is because transactions are validated in isolation, not relative to some expected external values.
Let me provide two examples:
If I have access to one of your unspent cash states from a previous transaction, then perhaps I could create a cash spend transaction (from you to me) and ask you to sign it via the CollectSignaturesFlow. If the transaction is valid then, without any additional checking, you'll sign it which will result in you sending me the cash. Clearly you don't want this!
The contract code can only go part of the way to verifying a transaction. If you want to check that the transaction represents a deal you want to enter into e.g. price < some amount then you'll have to do some additional checking. The contract code cannot opine on what constitutes a commercially viable deal for you. This checking has to be done as part of the SignTransactionFlow by overriding signTransaction
In a production CorDapp, one may wish to defer to human judgement on whether to sign a transaction and enter into a deal. Or alternatively, this process could be automated by reaching out to some external reference data system via HTTP API or MQ to determine if the deal is one that should be entered into.
In the code example above, we added two simple constraints:
One which prevents a borrower from creating an overly large (greater than 100) IOU state
One which ensures the transaction does indeed contain an IOU state and not some other state we are not expecting
Note that these two constraints cannot be placed inside the contract code. Contract code is more appropriate for defining the constraints that govern how an asset or agreement should evolve over time. For example, with regards to an IOU:
Issuances must be signed by lender and borrower
Issuances must be for a value greater than zero
Redemptions must involve a cash payment of the correct currency
The IOU must be redeemed before the expiry date
More examples available here: https://github.com/roger3cev/iou-cordapp-v2/blob/master/src/main/kotlin/net/corda/iou/contract/IOUContract.kt
Remember, Corda is designed for potentially mutually distrusting parties to enter into consensus about shared facts. As such, nodes cannot implicitly trust what they receive over the wire from their counter-parties, therefore we always have to check what we receive is what we expect to receive.
Hope that makes sense!

Resources