Corda 2.0
JDK 1.8.0_162
I'm trying to debug an inconsistent behaviour in FinalityFlow. Inconsistent as in different results in Mock and Real nodes.
The Procedure on Real Nodes
I'm trying to send a transaction to another node through one of the alternative FinalityFlow constructors:
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(transaction, extraParticipants, tracker())
I communicate with my node through RPC. The procedure starts by retreiving the other node's Party by it's name, eg. O=PartyA,L=London,C=GB:
val extraRecipientParties = myExtraRecipientsStringList.map { rpcOps.wellKnownPartyFromX500Name(CordaX500Name.build(X500Principal(it)))!! }
Then, rpcOps calls the flow responsible for creating a state:
val flow = rpcOps.startFlow(::CreateStateFlow, other, arguments, extraRecipientParties)
val result = flow.returnValue.getOrThrow()
val newState = result.tx.outRef<MyStateClass>(0)
CreateStateFlow is pretty standard:
#StartableByRPC
class CreateStateFlow(
val s: String,
val p: String,
val o: String,
val extraParticipants: List<Party>
) : FlowLogic<SignedTransaction>() {
constructor(s: String, p: String, o: String): this(s, p, o, emptyList())
#Suspendable
override fun call() : SignedTransaction {
val notary = serviceHub.networkMapCache.notaryIdentities.first()
val newState = MyStateClass(ourIdentity, s, p, o, extraRecipients=extraParticipants)
val command = Command(TripleContract.Create(), listOf(ourIdentity.owningKey))
val outputState = StateAndContract(newState, TripleContract.CONTRACT_REF)
val utx = TransactionBuilder(notary=notary).withItems(
command,
outputState
)
val stx = serviceHub.signInitialTransaction(builder=utx, signingPubKeys=listOf(ourIdentity.owningKey))
if (newState.extraRecipients.isEmpty()) {
return subFlow(FinalityFlow(stx))
}
return subFlow(FinalityFlow(stx, newState.extraRecipients.toSet() ))
}
}
What I expect is that now, on any node owned by parties in the extraRecipients variable, I should be able to find newState by querying the vault.
Indeed, this is true when I test it on Mock nodes, but not when rpc calls
rpcOps.vaultQueryBy<MyStateClass>().states --> returns an empty list
Test on Mock Nodes
#Test
fun `FinalityFlow used to federate a transaction`(){
val partyAString = node1.info.legalIdentities.first().name.toString()
val aStringX500Name = CordaX500Name.build(X500Principal(partyAString))
val node2FindPartyA = node2.rpcOps.wellKnownPartyFromX500Name(aStringX500Name)!!
assert(node1.info.legalIdentities.contains(node2FindPartyA))
val executingFlow = node2.start(CreateStateFlow("fo", "boo", "bar", listOf(node2FindPartyA)))
val flowResult = executingFlow.getOrThrow()
val stateInNode2 = flowResult.tx.outRef<MyStateClass>(0)
val stateInNode1 = node1.database.transaction {
node1.services.loadState(stateInNode2.ref)
}
assert(stateInNode1.data == stateInNode2.state.data)
Edit:
MyStateClass.kt
data class MyStateClass(
val owner: Party,
val s: String,
val p: String,
val o: String,
val extraRecipients: List<Party>,
val lastEditor: AbstractParty = owner,
override val participants: List<AbstractParty> = listOf(owner),
override val linearId: UniqueIdentifier = UniqueIdentifier()
) : LinearState, QueryableState {
object MyStateSchemaV1 : MappedSchema(MyStateClass::class.java, 1, listOf(MyStateEntity::class.java)) {
#Entity
#Table(name = "my-state")
class MyStateEntity(state: MyStateClass) : PersistentState() {
#Column #Lob
var owner: ByteArray = state.owner.owningKey.encoded
#Column
var s: String = state.s
#Column
var p: String = state.p
#Column
var o: String = state.o
#Column #ElementCollection
var extra_recipients: Set<ByteArray> = state.extraRecipients.map { it.owningKey.encoded }.toSet()
#Column #ElementCollection
var participants: Set<ByteArray> = state.participants.map { it.owningKey.encoded }.toSet()
#Column #Lob
var last_editor: ByteArray = state.owner.owningKey.encoded
#Column
var linear_id: String = state.linearId.id.toString()
}
}
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(MyStateSchemaV1)
override fun generateMappedObject(schema: MappedSchema): PersistentState = MyStateSchemaV1.MyStateEntity(this)
}
Although you introduced a new variable val extraRecipients: List<Party>, your participants is only on the owner, override val participants: List<AbstractParty> = listOf(owner) Therefore only the owner party should have the state in the vault.
The extraRecipients in FinalityFlow do not store the states in the vault (states storage), but they store the copy of the notarised transaction in the transaction storage.
The definition of loadState function is Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]. Because node 1 was added as a extra recipient of the transaction in finality flow (think of it as the cc-ed recipient of an email), when asked to loadState, it was able to deduce the state from the transaction storage since it consists of inputs, commands, outputs, etc. So here you've proven that the transaction was sent to the other parties during FinalityFlow.
While on the rpcOps.vaultQueryBy<MyStateClass>().states, it was actually querying states from the node states vault - not transaction storage, therefore returned an empty list.
If you want the extraRecipients to store the state, you'll need to add them in the participants field of the state or use observable-states concept here.
Related
I had cloned the cordapp-kotlin-template . I've defined a state called LoadState which looks like this:
#BelongsToContract(LoadContract::class)
data class LoadState(
val loadId: String,
val transporterName: String? = null,
val vehicleModel: String? = null,
val regLicenseNo: String? = null,
val totalWeight: Int? = null,
val notes: String? = null,
val suppliers: MutableList<SupplierDetailsModel> = mutableListOf(),
val recycler: Party,
override val participants: List<AbstractParty> = listOf(recycler)
) : QueryableState {
override fun generateMappedObject(schema: MappedSchema): PersistentState {
if (schema is LoadSchemaV1) {
return PersistentLoadState(this.loadId)
} else {
throw IllegalArgumentException("Unsupported Schema.")
}
}
override fun supportedSchemas(): Iterable<MappedSchema> {
return listOf(LoadSchemaV1())
}
}
When I use the application for the first time and issue a LoadState say LO123 it works fine. The state is issued and recorded in the vault as well.
This is how my the LoadState issue flow looks like:
#Suspendable
override fun call(): SignedTransaction {
val notary = serviceHub.networkMapCache.getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB"))
progressTracker.currentStep = TX_COMPONENTS
val recyclerName = CordaX500Name(
organisation = "MyRecycler",
locality = "Mumbai",
country = "IN"
)
val recycler = serviceHub.identityService.wellKnownPartyFromX500Name(recyclerName)
?: throw IllegalArgumentException("Could not find party Recycler.")
val outputState = LoadState(
loadId = req.loadId,
loadDate = req.loadDate,
transporterName = req.transporterName,
vehicleModel = req.vehicleModel,
regLicenseNo = req.regLicenseNo,
totalWeight = req.totalWeight,
assets = req.assets,
suppliers = req.suppliers,
recycler = recycler,
notes = req.notes
)
val command = Command(LoadContract.Commands.Create(), listOf(ourIdentity.owningKey))
progressTracker.currentStep = TX_BUILDING
val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, LoadContract.ID)
.addCommand(command)
// signing the transaction
progressTracker.currentStep = TX_SIGNING
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// verifying the transaction
progressTracker.currentStep = TX_VERIFICATION
txBuilder.verify(serviceHub)
// We finalise the transaction and then send it to the counterparty.
progressTracker.currentStep = FINALIZATION
val recyclerSession = initiateFlow(recycler)
return subFlow(FinalityFlow(signedTx, listOf()))
}
Now there's a requirement to add a new field to our LoadState:
val myNewField: String? = null
After adding this new field to the LoadState I'm running the deployNodes command. Once the build folders are generated I'm copying the contents of the Node/cordapps folder to my old build Node/cordapps folder.
Now, while starting the nodes I'm running the migration commands (core schemas and app schemas). Once the migration process is complete and the nodes are up, I'm calling an api endpoint which invokes a flow which takes L0123 as input, copies it, and modifies some params and create a new output state of type LoadState. The error is thrown at:
txBuilder.verify(serviceHub) in my UpdateLoadFlow. This is how my update flow looks like:
#Suspendable
override fun call(): SignedTransaction {
val notary = serviceHub.networkMapCache.getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB"))
progressTracker.currentStep = TX_COMPONENTS
val recyclerName = CordaX500Name(
organisation = "MyRecycler",
locality = "Mumbai",
country = "IN"
)
val recycler = serviceHub.identityService.wellKnownPartyFromX500Name(recyclerName)
?: throw IllegalArgumentException("Could not find party Recycler.")
val inputState = QueryVault().queryLoadById(req.loadId, Vault.StateStatus.UNCONSUMED, serviceHub.vaultService)
?: throw Exception("Load ${req.loadId} not found.")
val vaultData = inputState.state.data
var outputState = vaultData.copy(myNewField = "some new value");
val command = Command(LoadContract.Commands.Update(), listOf(ourIdentity.owningKey))
progressTracker.currentStep = TX_BUILDING
val txBuilder = TransactionBuilder(notary = notary)
.addInputState(inputState)
.addOutputState(outputState, LoadContract.ID)
.addCommand(command)
txBuilder.verify(serviceHub)
// signing the transaction
progressTracker.currentStep = TX_SIGNING
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// verifying the transaction
progressTracker.currentStep = TX_VERIFICATION
// We finalise the transaction and then send it to the counterparty.
progressTracker.currentStep = FINALIZATION
val recyclerSession = initiateFlow(recycler)
return subFlow(FinalityFlow(signedTx, listOf()))
}
Please help me out with this.
After our discussion in the above comments section, it appears that your states have been issued using Whitelist zone constraint.
Also looking at the code it is clear that you have not explicitly added Whitelist Zone constraints for your states.
Then there remain two more possibilities by which states are issued using Whitelist Zone constraint.
One is you are using some Corda version before 4 or you have included the necessary configs to include white list zone constraints in network bootstrapper as specified here.
You have two options - either start from scratch and make sure you use Corda 4.
If you cannot start from scratch follow this path to first migrate the whitelist zone constraints to signature constraints, before running your UpdateLoadFlow. You can refer to this blog which talks about constraint migration.
I'm currently training to become a corda developer.
I've created a simple corDapp which only has 1 participant, Here is the state I created:
#BelongsToContract(UserContract::class)
class UserState(val name: String,
val age: Int,
val address: String,
val gender: GenderEnums,
val node: Party,
val status: StatusEnums,
override val linearId: UniqueIdentifier,
override val participants : List<Party>
) : LinearState
So when I run the corDapp, I get my desired output https://imgur.com/mOKhNpI
But what I want to do is to update the vault. I would, for example, like to update the address from "Pampanga" to "Manila", But I don't know where to start. All I know is that since States are immutable, you have to consume the state first.
I tried to create a flow:
#InitiatingFlow
#StartableByRPC
class UpdateUserFlows(private val name :String,
private val age : Int,
private val address : String,
private val gender: GenderEnums,
private val status : StatusEnums,
private val counterParty: Party): FlowLogic<SignedTransaction>() {
private fun userStates(): UserState {
return UserState(
name = name,
age = age,
address = address,
gender = gender,
status = status,
node = ourIdentity,
linearId = UniqueIdentifier(),
participants = listOf(ourIdentity, counterParty)
)
}
#Suspendable
override fun call(): SignedTransaction {
val transaction: TransactionBuilder = transaction()
val signedTransaction: SignedTransaction = verifyAndSign(transaction)
val sessions: List<FlowSession> = (userStates().participants - ourIdentity).map { initiateFlow(it) }.toSet().toList()
val transactionSignedByAllParties: SignedTransaction = collectSignature(signedTransaction, sessions)
return recordTransaction(transactionSignedByAllParties, sessions)
}
private fun transaction(): TransactionBuilder {
val notary: Party = serviceHub.networkMapCache.notaryIdentities.first()
val issueCommand = Command(UserContract.Commands.Issue(), userStates().participants.map { it.owningKey })
val builder = TransactionBuilder(notary = notary)
builder.addOutputState(userStates(), UserContract.ID)
builder.addCommand(issueCommand)
return builder
}
private fun verifyAndSign(transaction: TransactionBuilder): SignedTransaction {
transaction.verify(serviceHub)
return serviceHub.signInitialTransaction(transaction)
}
#Suspendable
private fun collectSignature(
transaction: SignedTransaction,
sessions: List<FlowSession>
): SignedTransaction = subFlow(CollectSignaturesFlow(transaction, sessions))
#Suspendable
private fun recordTransaction(transaction: SignedTransaction, sessions: List<FlowSession>): SignedTransaction =
subFlow(FinalityFlow(transaction, sessions))
}
But it's not working.
You are right, states are immutable in Corda and to mimic an update you basically need to create a transaction where the input is your current version of the state (i.e. address = Pampanga), and the output would be a new instance of UserState which has the same linearId and other attribute values as the input, but a different address value (i.e. Manila).
This way you are creating a new state (the output of the transaction), but because it will have the same linearId value; it would be as if you updated the input to become the output, this will allow you to see all the previous versions of a state by querying the vault by its linearId.
You didn't share the code of your contract, but you need to add a new command (let's call it Update); and also you need to add the verification rules that apply to it.
Inside your flow, you'd use that command (instead of Issue).
i want to use addIssueTokens to add token to TransactionBuilder.
my code
#InitiatingFlow
#StartableByRPC
class Issue(val otherParty: Party) : FlowLogic<SignedTransaction>() {
override val progressTracker = ProgressTracker()
#Suspendable
override fun call() : SignedTransaction{
val jpyToken = createFungibleToken("JPY", 1000, otherParty)
val gbToken = createFungibleToken("GB", 1000, otherParty)
val notary = serviceHub.networkMapCache.notaryIdentities.single()
val txBuilder = TransactionBuilder(notary)
//may be add other output input
//....
//add token to txBuilder
addIssueTokens(txBuilder, listOf(jpyToken,gbToken))
txBuilder.verify(serviceHub)
// Sign the transaction
val ptx = serviceHub.signInitialTransaction(txBuilder, ourIdentity.owningKey)
// Instantiate a network session with the shareholder
val holderSession = initiateFlow(otherParty)
val sessions = listOf(holderSession)
// Ask the shareholder to sign the transaction
val stx = subFlow(CollectSignaturesFlow(ptx, listOf(holderSession)))
return subFlow<SignedTransaction>(FinalityFlow(stx, sessions))
}
fun createFungibleToken(symbol:String,amout:Long,target : AbstractParty) : FungibleToken{
val tokenType = TokenType(symbol, 0)
val issuedTokenType = IssuedTokenType(ourIdentity, tokenType)
val amount = Amount<IssuedTokenType>(amout, issuedTokenType)
return FungibleToken(amount, target)
}
}
#InitiatedBy(Issue::class)
class IssueResponder(val otherPartySession: FlowSession) : FlowLogic<SignedTransaction>() {
#Suspendable
override fun call(): SignedTransaction {
val signTransactionFlow = object : SignTransactionFlow(otherPartySession) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
}
}
val txId = subFlow(signTransactionFlow).id
return subFlow(ReceiveFinalityFlow(otherPartySession, expectedTxId = txId))
}
}
shell: start flow
>>start com.template.flows.Issue otherParty: "O=PartyB,L=New York,C=US"
Starting
Collecting signatures from counterparties.
Starting
Broadcasting transaction to participants
Done
Flow completed with result: SignedTransaction(id=547B812BA5574168DA8085C87AADFCAFA2A098CF62F375C21D450C0FE2402547)
it seems that Flow completed. but i check partyB database ,there is no data in vault_states table .
why?
ps.i knew how to use com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens flow
The holder is not a required signer when issuing tokens; try removing the CollectSignaturesFlow and SignTransactionFlow calls from the initiator and responder flows; only keep FinalityFlow and ReceiveFinalityFlow.
You can see here that the required signers are:
The issuer on issuing.
The holder on moving.
The issuer and holder on redeeming.
But also the contract says here that there could be other signers on issue; so I'm not 100% sure that's the cause of the problem.
Try removing the signature flows and re-run your test; and let me know if it worked.
Another thing, check the logs of the initiating node (the log is inside logs folder inside your node's folder structure); are there any errors?.
Is it possible to getting transaction consumed time which can be seen in vault_state through vault query?
Yes. You can find the time the state was consumed using the statesMetadata field of the query result.
Within a flow:
#Suspendable
override fun call() {
val results = serviceHub.vaultService.queryBy(ContractState::class.java)
val statesAndMetadata = results.states.zip(results.statesMetadata)
statesAndMetadata.forEach { (stateAndRef, metadata) ->
val consumedTime = metadata.consumedTime
}
}
Within an RPC client:
fun main(args: Array<String>) {
require(args.size == 1) { "Usage: TemplateClient <node address>" }
val nodeAddress = parse(args[0])
val client = CordaRPCClient(nodeAddress)
// Can be amended in the com.template.MainKt file.
val cordaRPCOps = client.start("user1", "test").proxy
val results = cordaRPCOps.vaultQueryBy<ContractState>()
val statesAndMetadata = results.states.zip(results.statesMetadata)
statesAndMetadata.forEach { (stateAndRef, metadata) ->
val consumedTime = metadata.consumedTime
}
}
I have been following the official tutorials on how to implement observer nodes from here and here. I tried testing the flow to broadcast the transaction to observer nodes, however, I am not sure if I implemented the flow correctly. After running the flow, no states showed up in the vault of the observer node. No states (that corresponded to the transaction that was broadcast) showed up when I ran a RPC vault query nor did it show when I accessed the H2 database of the observer node. Debugging showed that the flow code was called. No exception was thrown as well.
Is the flow working correctly? Also how can I view the broadcasted transactions as an observer node - is it stored as a consumed state in its vault?
The flow code:
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
)
)
}
}
}
How I call the flow:
subFlow(BroadcastTransaction.BroadcastTransactionToObservers(fullySignedTx, listOf(observer)))
Prior initiating flow:
#InitiatingFlow
#StartableByRPC
class Initiator (val id: String,
val transferParty : Party,
val observer : Party) : BaseFlow() {
#Suspendable
override fun call() : SignedTransaction {
progressTracker.currentStep = ID_OTHER_NODES
val notary = serviceHub.networkMapCache.notaryIdentities[0]
progressTracker.currentStep = EXTRACTING_VAULT_STATES
val stateAndRef = getItemStateByItemId(id)
val inputState = stateAndRef.state.data
progressTracker.currentStep = TX_BUILDING
val txBuilder = TransactionBuilder(notary = notary)
val outputState = createOutput(inputState)
val signerKeys = listOf(ourIdentity.owningKey, transferParty.owningKey)
val cmd = Command(outputState.command, signerKeys)
txBuilder.addInputState(stateAndRef)
.addOutputState(outputState.ownableState, CONTRACT_ID)
.addCommand(cmd)
progressTracker.currentStep = TX_VERIFICATION
txBuilder.verify(serviceHub)
progressTracker.currentStep = TX_SIGNING
val signedTx = serviceHub.signInitialTransaction(txBuilder)
progressTracker.currentStep = SENDING_AND_RECEIVING_DATA
val sessions = setOf(initiateFlow(transferParty))
progressTracker.currentStep = SIGS_GATHERING
val fullySignedTx: SignedTransaction = subFlow(CollectSignaturesFlow(signedTx, sessions, SIGS_GATHERING.childProgressTracker()))
subFlow(BroadcastTransaction.BroadcastTransactionToObservers(fullySignedTx, listOf(observer)))
progressTracker.currentStep = FINALISATION
return subFlow(FinalityFlow(fullySignedTx, setOf(ourIdentity),FINALISATION.childProgressTracker()))
}
You are broadcasting the transaction to the observer before it has received a signature from the notary as part of FinalityFlow. Nodes will not record a transaction's states in their vault unless it has all the required signatures, including the notary's.
Try moving the call to FinalityFlow above the call to BroadcastTransaction.BroadcastTransactionToObservers.