First apologize that I am still a learner on Corda. Had read the Hello World example about IOU situation. Per my understanding, the current sample only covers the IOU transaction, but does not include the change of amount of both lender and borrower have after IOU. If I want to make this, how can I modify the contract and flow parts?
Before IOU
State of UserA: 100 unit
State of UserB: 0 unit
After IOU of 10 unit
State of UserA: 90 unit
State of UserB: 10 unit
As far as I know that there must be two input states and two output states on contract shape constraints. But how can I create two new states on flow part, as most examples only have one output state?
Thanks a lot for your help and appreciate more if you can give me some hints in java but not in kotlin format.
Retrieving the two input states
Suppose you are running the flow on UserA. You can retrieve the input state for UserA by querying the vault. For example:
val queryCriteria = QueryCriteria.LinearStateQueryCriteria(
null, listOf(linearId), UNCONSUMED, null)
val inputStateAndRef = serviceHub.vaultService.queryBy<IOUState>(queryCriteria).states.singleOrNull()
?: throw FlowException("IOU with ID $linearId not found.")
Retrieving the input state for UserB is more complex, since it is stored in UserB's vault, which UserA can't query directly. You need to write a responder flow where UserB retrieves the state from their vault, then sends it across to the initiator who is building the transaction. For example:
#InitiatingFlow
class Initiator(val counterparty: Party, val linearId: UniqueIdentifier) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
val flowSession = initiateFlow(counterparty)
flowSession.send(linearId)
val inputStateAndRef = flowSession.receive<StateAndRef<IOUState>>().unwrap { it }
}
}
#InitiatedBy(Initiator::class)
class Acceptor(val flowSession: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
val linearId = flowSession.receive<UniqueIdentifier>().unwrap { it }
val queryCriteria = QueryCriteria.LinearStateQueryCriteria(
null, listOf(linearId), Vault.StateStatus.UNCONSUMED, null)
val inputStateAndRef = serviceHub.vaultService.queryBy<IOUState>(queryCriteria).states.singleOrNull()
?: throw FlowException("IOU with ID $linearId not found.")
flowSession.send(inputStateAndRef)
}
}
Adding two output states
You can add two output states to a transaction by simply calling TransactionBuilder.addOutputState twice:
transactionBuilder.addOutputState(new TemplateState(), TemplateContract.ID);
transactionBuilder.addOutputState(new TemplateState(), TemplateContract.ID);
Related
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.
In Corda, nodes only store states for which they are one of the participants (unless the state is an OwnableState, in which case they only store it if they are the owner).
How can I override this behaviour and get a node to store a state for which they are not a participant?
Instead of choosing to record only states in which they are one of the participants, nodes can choose to record every state in a transaction they receive. I've written out an example below. You can also take a look at the Observable States CorDapp implementing this pattern here.
Sending the transaction
First, a node who has the transaction that contains the states in question needs to send the transaction to the counterparty(s) who want to record it but who are not participants. Here is how we'd define a BrodcastTransactionFlow to do this:
#InitiatingFlow
class BroadcastTransaction(
val stx: SignedTransaction,
val counterparty: Party) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
val session = initiateFlow(counterparty)
subFlow(SendTransactionFlow(session, stx))
}
}
Receiving the transaction and storing all the states
The counterparty(s) would have to register a responder flow that records all the states in the transaction. Here is how we'd define a RecordTransactionAsObserver to do this:
#InitiatedBy(BroadcastTransaction::class)
class RecordTransactionAsObserver(val otherSession: FlowSession) : FlowLogic<Unit>() {
#Suspendable
override fun call() {
val flow = ReceiveTransactionFlow(
otherSideSession = otherSession,
checkSufficientSignatures = true,
// We are recording all the states,
// and not just the ones where we are participants.
statesToRecord = StatesToRecord.ALL_VISIBLE
)
subFlow(flow)
}
}
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))
}
Can I find a state with a txhash ? I want something like this : val state = rpcOps.findStateFromTXhash(txhash)
I have found that there is a type of state called linearState that have a linearId property . There is also a hash property but I don't know if it is what I search.
In your flows, you can do getServiceHub().loadState() here you can pass in the securehash to get your state. not sure if we can do something like that directly from CordaRpcConnection object.
Your state will have a linearId if it's a type of linear state. You can search your state using the linearId easily.
read here.
I'd recommend you read more about states to see what best suits your requirement. Link
There is no RPC operation to load a transaction's states given a transaction ID.
However, you can write a flow to do this, as follows, then invoke this flow via RPC:
#InitiatingFlow
#StartableByRPC
class GetStatesFromTransactionFlow(val transactionID: SecureHash) : FlowLogic<List<ContractState>>() {
#Suspendable
override fun call(): List<ContractState> {
val signedTransaction = serviceHub.validatedTransactions.getTransaction(transactionID)
if (signedTransaction == null) {
throw FlowException("Transaction does not exist in node's transaction storage.")
}
val ledgerTransaction = signedTransaction.toLedgerTransaction(serviceHub)
val inputs = ledgerTransaction.inputs.map { it.state.data }
val outputs = ledgerTransaction.outputs.map { it.data }
return inputs + outputs
}
}
Currently we are planning to have one "Draft" version of contract which will not be sent to the counterparty and the initiator can make any changes before send it to the network, so this should be as one "unshared fact". As we know Corda and the vault is used for shared facts, so here I am not sure whether we can still use vault to store this type of "unshared fact", my idea as below and I already can make this work in my local based on the tutorial CorDapp, but would like to get some inputs from other Corda team/experts.
The main change is in the initiator flow:
Initiate the create command's only with the initiator's key
Do not invoke the "CollectSignaturesFlow" so this will not be sent to any others
Invoke "FinalityFlow" after the verify(), so this will be committed to the ledger
Below are the codes for above mentioned points.
override fun call(): SignedTransaction {
// We create a transaction builder
val txBuilder = TransactionBuilder()
val notaryIdentity = serviceHub.networkMapCache.getAnyNotary()
txBuilder.notary = notaryIdentity
// We create the transaction's components.
val ourIdentity = serviceHub.myInfo.legalIdentity
val iou = TemplateState(iouValue, ourIdentity, ourIdentity)
val txCommand = Command(TemplateContract.Create(), listOf(ourIdentity.owningKey))
// Adding the item's to the builder.
txBuilder.withItems(iou, txCommand)
// Verifying the transaction.
txBuilder.toWireTransaction().toLedgerTransaction(serviceHub).verify()
// Signing the transaction.
val partSignedTx = serviceHub.signInitialTransaction(txBuilder)
// Finalising the transaction.
return subFlow(FinalityFlow(partSignedTx)).single()
}
You can indeed create an unshared fact in Corda! The key here is in the state's participants list. Just add yourself in the participants list and only your public key to the command. Here's a simple example:
//Contract and State.
class UnsharedFact: Contract {
override val legalContractReference: SecureHash = SecureHash.zeroHash
override fun verify(tx: TransactionForContract) = Unit // Stubbed out.
class Create: CommandData
data class State(val parties: Set<Party>): ContractState {
override val contract: Contract get() = UnsharedFact()
override val participants: List<AbstractParty> get() = parties.toList()
fun addParty(newParty: Party) = copy(parties = parties + newParty)
}
}
// Create flow.
#InitiatingFlow
#StartableByRPC
class CreateUnsharedFact(): FlowLogic<SignedTransaction>() {
#Suspendable
override fun call(): SignedTransaction {
val me = serviceHub.myInfo.legalIdentity
val notary = serviceHub.networkMapCache.getAnyNotary()
val state = UnsharedFact.State(setOf(me))
val command = Command(UnsharedFact.Create(), listOf(me.owningKey))
val utx = TransactionBuilder(notary = notary).withItems(state, command)
val stx = serviceHub.signInitialTransaction(utx)
return subFlow(FinalityFlow(stx)).single()
}
}
When FinalityFlow is called, you will be the only node that receives the output state.
If you wish to subsequently involve another party then you can create a new version of the state using the addParty method on UnsharedFact.State. Then, create a new transaction, adding the original state as the input and the new version (with the new party) as the output. When this transaction is finalised (notarised) then both parties will have a copy in their respective vaults. Now, I guess the name 'UnsharedFact' is inappropriate :)
You can also remove parties using a similar approach.