I understand there's some annotation related to #Suspendable to mark a function as serialisable. How much does a flow checkpoint itself?
Does a node checkpoint itself only when there's a send/sendAndReceive when it waits for a response? Or does it serialise checkpoints at an interval?
Given a flow that does nothing but computation, how much does it serialise/write to disk, and does this affect performance if there's a peak load of read/write from other thread doing vault queries/writing.
How does #Suspendable play a part in the these private methods that just do computation and nothing else. If a method gets annotated, it will only get serialise on the next send else nothing gets serialised?
Example
#Suspendable
override fun call() {
val states = querySomeStates()
computeSomethingHeavy(states)
decideSomething()
}
#Suspendable
private querySomeStates()
#Suspendable
computeSomethingHeavy()
#Suspendable
decideSomething()
#Suspendable marks a function as potentially suspendable. The flow is actually suspended only when one of the following operations is performed:
Flow start
send
receive
sendAndReceive
waitForLedgerCommit
getFlowInfo
sleep
When one of these operations is performed, the node uses Quasar to capture the execution stack and create a checkpoint. If a function does not perform any of these operations, no checkpoints will be created. This is true even if the flow is doing heavy computation and/or the function is marked #Suspendable. In other words, Quasar does not do preemption, meaning we don't "checkpoint periodically", but only at specific call sites.
For example, here is the sequence of checkpoints in a simple flow:
#Suspendable
fun call() {
// checkpoint!
sendSomething()
computeSomething()
}
#Suspendable
fun sendSomething() {
send() // checkpoint!
}
#Suspendable
fun computeSomething() {
heavyComputation() // no checkpoint!
}
Related
Imagine A is in a network along with B and C. And A has one shared fact with B, and one shared fact with C. Both these are residing in A's Vault. Suppose, A's system gets crashed and node's H2 data is gone, how can A recover the same with both these facts back in its Vault ?
If B and C were both willing to re-share this information with A, they could both call a flow such as the one defined below to resend the transactions and their dependencies to A:
#InitiatingFlow
#StartableByRPC
class ShareTransactionHistory(val otherParty: Party, val signedTransaction: SignedTransaction) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
val otherPartySession = initiateFlow(otherParty)
subFlow(SendTransactionFlow(otherPartySession, signedTransaction))
}
}
#InitiatedBy(ShareTransactionHistory::class)
class ShareTransactionHistoryResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
subFlow(ReceiveTransactionFlow(otherPartySession, statesToRecord = StatesToRecord.ONLY_RELEVANT))
}
}
A will then automatically re-record the transactions and any relevant states.
However, please see this question for caveats in the restoration process.
I was performing testing on corda observer node. On IOU issue, put call of flow for observer. same thing on IOU lender transfer flow (Flow for changing lender property to the new Party).
I issue IOU From partyA to PartyB. On observer node stateAndRef gets displayed too.
but when performed transferred IOU, no change gets displayed on observer node. It's still showing old state.
Does observer node keep unconsume states only in vault or consume/unconsumed transaction both?
below code is working for one flow IOU issue only not for other flow:
I am referring IOU example here
Below is the code I called from each Flow:
object BroadcastTransaction {
#InitiatingFlow
class BroadcastTransactionToObservers(private val stx: SignedTransaction, private val observers: List<Party>) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
val sessions = observers.map { initiateFlow(it) }
sessions.forEach { subFlow(SendTransactionFlow(it, stx)) }
}
}
#InitiatedBy(BroadcastTransactionToObservers::class)
class RecordTransactionAsObserver(private val otherSession: FlowSession) :FlowLogic<Unit>() {
#Suspendable
override fun call() {
subFlow( ReceiveTransactionFlow(
otherSideSession = otherSession,
checkSufficientSignatures = true,
statesToRecord = StatesToRecord.ALL_VISIBLE
)
)
}
}
}
I have checked logs for node getting not enough signature even after put call to observer flow just after finalityflow call...plz help in this regard?
Finally found my mistake. In logs i got error "Insufficient signature corda". I had passes signedsignature after finalityflow but not passed finalityflow result to above mention flow.
This question is raised while discussing Making asynchronous HTTP calls from flows
Suppose, we are implementing a Loan Application. After a LoanRequest is received, Corda flow will make an HTTP call to verify the request and we want to invoke other transaction automatically according to the result of HTTP call i.e to record ApprovedLoan or RejectedLoan State.
Now problem in this scenario is, ApprovedLoan or RejectedLoan transaction will need input state as LoanRequest. So we can't invoke the other flow from Acceptor of LoanRequest flow as the input state is not committed yet and thus resulting in race condition.
Any suggestion or examples on how this can be implemented would be appreciated.
Thanks.
You need to commit the LoanRequest transaction to each node's storage first, before making the call in the acceptor to decide whether to approve or reject the request. You also need to use FlowLogic.waitForLedgerCommit to ensure you don't kick off the approval or rejection before the LoanRequest has been stored. Here's an example:
#InitiatingFlow
#StartableByRPC
class Initiator(val otherParty: Party) : FlowLogic<SignedTransaction>() {
/**
* The flow logic is encapsulated within the call() method.
*/
#Suspendable
override fun call(): SignedTransaction {
val session = initiateFlow(otherParty)
val fullySignedTx: SignedTransaction = TODO("Build fully signed transaction.")
subFlow(FinalityFlow(fullySignedTx))
session.send(fullySignedTx.id)
}
}
#InitiatedBy(Initiator::class)
class Acceptor(val session: FlowSession) : FlowLogic<SignedTransaction>() {
#Suspendable
override fun call(): SignedTransaction {
TODO("Response logic for building fully signed transaction.")
val txId = session.receive<SecureHash>().unwrap { secureHash -> secureHash }
waitForLedgerCommit(txId)
val approve: Boolean = TODO("Make HTTP call to decide whether to approve or reject.")
if (approve) {
TODO("Response logic for building approval transaction.")
} else {
TODO("Response logic for building rejection transaction.")
}
}
}
I am working with Event Scheduling functionality in Corda. I have added the same in one state. Now I want to create another state automatically when the existing state gets accepted. The accept functionality is with a party, whereas the action which triggers the creation of the other state has to be with other party itself. When I try doing this, both the Initiator are same. How to set the other party as the initiator when creating the new state flow?
PS: We are following a project structure that has separate modules for contract-states and flows.
One way to achieve this is to:
Define a flow that instead of executing any logic, simply hands off to a responder flow
Define a responder flow that executes the logic
Here is an example:
#InitiatingFlow
#SchedulableFlow
class InitiatorFlow(val counterparty: Party) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
// We send a flag message to the counterparty, causing them to start their responder flow.
val session = initiateFlow(counterparty)
session.send(true)
}
}
#InitiatedBy(InitiatorFlow::class)
class InitiatedFlow(val counterpartySession: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
// We process and discard the flag message.
counterpartySession.receive<Boolean>()
// TODO: Create the new state based on the acceptance.
}
}
I have the scenario where One party needs to read a list of states ( DEAL STATE for ex.), Then iterate through the list to find out the matching records. If it is matching, we need to create new output state by combining the matched fields.
So once the matching is performed within the loop, we can get the list of input and output states.
But I am confused on collecting the signature from other parties since the counterparties will be different for each of the record. Also, how will I call the finality flow for multiple transactions within single flow call method?
#joel, Another problem - Suppose outputstate1 particicpants are say A, B, C, and outputstate2 paticipants are B, C ,D ,ie B & C are involved in 2 transactions. So within matching states loop, when we make flowsessions map, it will have the signers are A, B, C, D. But when we call CollectSignaturesFlow, we need to pass each of the partialsigned trxn and sessions. So how to pass the session corresponding to a trxn ?
Here's an example of how we could collect the signatures for each state:
val flowSessionMap = mutableMapOf<Party, FlowSession>()
val fullySignedTransactions = matchingStates.forEach { matchingState ->
val requiredSigners: List<Party> = TODO("Derive this from the matching state somehow.")
val signedTransaction: SignedTransaction = TODO("Build transaction.")
val sessions = requiredSigners.map { signer ->
flowSessionMap.getOrPut(signer) {
initiateFlow(signer)
}
}
val fullySignedTransaction = subFlow(CollectSignaturesInitiatingFlow(
signedTransaction, sessions)
)
}
Where CollectSignaturesInitiatingFlow is defined as follows:
#InitiatingFlow
class CollectSignaturesInitiatingFlow(val signedTransaction: SignedTransaction, val sessions: List<FlowSession>): FlowLogic<SignedTransaction>() {
override fun call(): SignedTransaction {
return subFlow(CollectSignaturesFlow(signedTransaction, sessions))
}
}
And the responder for CollectSignaturesInitiatingFlow is defined as follows:
#InitiatedBy(CollectSignaturesInitiatingFlow::class)
class CollectSignaturesInitiatingFlowResponder(val otherPartyFlow: FlowSession) : FlowLogic<SignedTransaction>() {
#Suspendable
override fun call(): SignedTransaction {
val signTransactionFlow = object : SignTransactionFlow(otherPartyFlow) {
override fun checkTransaction(stx: SignedTransaction) {
TODO("Check the transaction here.")
}
}
return subFlow(signTransactionFlow)
}
}
Note that:
We're being careful to only create one session per counterparty. As of Corda 3, an error will be thrown if a flow creates multiple sessions per counterparty
We're wrapping the call to CollectSignaturesFlow in CollectSignaturesInitiatingFlow. Why? In Corda, there are two types of flow: Initiating and inlined. Each Initiating flow instance has a unique ID, while each inlined flow inherits the ID of the flow that called it as a subflow. As of Corda 3, an exception is thrown if a responder is invoked twice for the same flow ID. By wrapping CollectSignaturesFlow (an inlined flow) inside CollectSignaturesInitiatingFlow (an Initiating flow), we create a new flow ID for each attempt to gather signatures, and no exception is thrown
Once you have the fully-signed transactions, you can call FinalityFlow in a loop:
for (transaction in fullySignedTransactions) {
subFlow(FinalityFlow(transaction))
}