When trying to start a flow via the secondary constructor
flow start ProposeFlow$Sender info: { recipientBank: "CN=Bank A,O=Bank A,L=Amsterdam,C=NL", amount: $10, paymentInstruction: "abc", toCurrency: USD, isFXConversionBySenderBank: true, linearId: e8a8c979-c889-433e-b102-fe43fdae7e1a }
I got
No matching constructor found:
- [recipientBank: Party, amount: Amount, paymentInstruction: String, toCurrency: Currency, isFXConversionBySenderBank: boolean, linearId: UniqueIdentifier]: missing parameter recipientBank
- [Info]: Could not parse as a command: Can not construct instance of net.corda.core.contracts.UniqueIdentifier: no String-argument constructor/factory method to deserialize from String value ('e8a8c979-c889-433e-b102-fe43fdae7e1a')
at [Source: N/A; line: -1, column: -1] (through reference chain: argent.flow.ProposeFlow$Info["linearId"])
- [Party, Amount, String, Currency, boolean, UniqueIdentifier, int, DefaultConstructorMarker]:
Via the primary constructor,
flow start ProposeFlow$Sender recipientBank: "CN=Bank A,O=Bank A,L=Amsterdam,C=NL", amount: $10, paymentInstruction: "abc", toCurrency: USD, isFXConversionBySenderBank: true, linearId: e8a8c979-c889-433e-b102-fe43fdae7e1a
I got
No matching constructor found:
- [Party, Amount, String, Currency, boolean, UniqueIdentifier]: Could not parse as a command: Can not construct instance of net.corda.core.contracts.UniqueIdentifier: no String-argument constructor/factory method to deserialize from String value ('e8a8c979-c889-433e-b102-fe43fdae7e1a')
at [Source: N/A; line: -1, column: -1]
- [info: Info]: too many parameters
- [Party, Amount, String, Currency, boolean, UniqueIdentifier, int, DefaultConstructorMarker]:
Here are the flow constructors -
object ProposeFlow {
#CordaSerializable
data class Info (val recipientBank: Party,
val amount: Amount,
val paymentInstruction: String,
val toCurrency: Currency = amount.token,
val isFXConversionBySenderBank: Boolean = true,
val linearId: UniqueIdentifier = UniqueIdentifier())
#InitiatingFlow
#StartableByRPC
class Sender(val recipientBank: Party,
val amount: Amount<Currency>,
val paymentInstruction: String,
val toCurrency: Currency = amount.token,
val isFXConversionBySenderBank: Boolean = true,
val linearId: UniqueIdentifier = UniqueIdentifier()) : FlowLogic<SignedTransaction>() {
constructor(info: Info): this (
recipientBank = info.recipientBank,
amount = info.amount,
paymentInstruction = info.paymentInstruction,
toCurrency = info.toCurrency,
isFXConversionBySenderBank = info.isFXConversionBySenderBank,
linearId = info.linearId
)
Is there a way around?
Thanks.
\Sean
That's a bug (missing feature) - the shell should support UUID parsing. It's an easy fix if you'd like to submit a PR: look here:
https://github.com/corda/corda/blob/master/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt#L184
You'd just need to add a Yaml deserialiser for UniqueIdentifier which is probably two or three lines of code.
I've filed it here: https://github.com/corda/corda/issues/1123
Related
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).
My question is similar to serviceHub.vaultQueryService.queryBy returns all related states by default? except we have schemas mapped for states and use TypedQueries to fetch states from database.
The flow which queries a state from database:
#StartableByRPC
#Transactional
open class FetchServiceById(
private val serviceId: UniqueIdentifier
) : FlowLogic<List<ServiceSchemaV1.PersistentService>>() {
#Suspendable
override fun call(): List<ServiceSchemaV1.PersistentService> {
return serviceHub.withEntityManager {
createQuery(
"SELECT s FROM $TABLE_NAME s WHERE s.linearId = ?1",
ServiceSchemaV1.PersistentService::class.java
).setParameter(
1,
serviceId.id
).resultList
}
}
private companion object {
val TABLE_NAME = ServiceSchemaV1.PersistentService::class.jvmName
}
}
This returns a state correctly but after updating the particular state and running query again it returns both the old, consumed state and the new unconsumed state.
I checked the h2 database and can see that after a state update, in VAULT_STATE there is a date in CONSUMED_TIMESTAMP and its STATE_STATUS is 1.
How can we only query unconsumed states with typed queries? I know we could just use linearquery but when we need to query and join data from different states I don't think that would be computationally feasible.
The schema the state uses
object ServiceSchema
object ServiceSchemaV1 : MappedSchema(
schemaFamily = ServiceSchema.javaClass,
version = 1,
mappedTypes = listOf(
PersistentService::class.java
)) {
#Entity
#Table(name = "service_states")
class PersistentService(
#Column(name = "accountoperator")
var accountOperator: String,
#Column(name = "serviceprovider")
var serviceProvider: String,
#Column(name = "servicename")
var serviceName: String,
#Column(name = "servicedescription")
var serviceDescription: String,
#Column(name = "datacreated")
var dataCreated: LocalDate,
#Column(name = "linear_id")
var linearId: UUID,
#ElementCollection
#Column(name = "service_data_ids")
var serviceDataIds: MutableList<UUID>,
#ElementCollection
#Column(name = "service_partners")
var servicePartners: MutableList<String>
) : PersistentState() {
constructor() : this(
"",
"",
"",
"",
LocalDate.now(),
UUID.randomUUID(),
mutableListOf(),
mutableListOf()
)
}
}
You could perform a join query on your State's Schema and the VAULT_STATE table on the basis of the TRANSACTION_ID and OUTPUT_INDEX and filter based on STATE_STATUS.
Select X, Y Z, from MY_STATE M, VAULT_STATES V WHERE M.TRANSACTION_ID =
V.TRANSACTION_ID AND M.OUTPUT_INDEX = V.OUTPUT_INDEX AND STATE_STATUS = 0
You should check that all the necessary parties are included for the service call. Otherwise you can only see the states the particular node was included with the last state update's contract.
Where do I provide the constructor input as the error message states? I am unsure of the correct usage of the StateRef when scheduling activities. I successfully ran the Heartbeat CorDapp for testing basic usage.
ForwardState:
data class ForwardState(val initiator: Party, val acceptor: Party, val asset: String, val deliveryPrice: BigDecimal, val startDate: Instant, val settlementDate: Instant, val buySell: String) : SchedulableState {
override val participants get() = listOf(initiator, acceptor)
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
return ScheduledActivity(flowLogicRefFactory.create("com.template.ForwardSettleFlow"), settlementDate)
}
ForwardFlow:
#InitiatingFlow
#StartableByRPC
class ForwardFlow(val initiator: Party, val acceptor: Party, val asset: String, val deliveryPrice: BigDecimal,
val startDate: Instant, val settlementDate: Instant, val buySell: String) : FlowLogic<Unit>() {
companion object {
object GENERATING_TRANSACTION : ProgressTracker.Step("Generating transaction")
object SIGNING_TRANSACTION : ProgressTracker.Step("Signing transaction with our private key")
object FINALISING_TRANSACTION : ProgressTracker.Step("Recording transaction") {
override fun childProgressTracker() = FinalityFlow.tracker()
}
fun tracker() = ProgressTracker(
GENERATING_TRANSACTION,
SIGNING_TRANSACTION,
FINALISING_TRANSACTION
)
}
override val progressTracker = tracker()
#Suspendable
override fun call() {
// Adapted from hello world pt 1/2
}
}
ForwardSettleFlow:
#InitiatingFlow
#SchedulableFlow
#StartableByRPC
class ForwardSettleFlow(val initiator: Party, val acceptor: Party, val asset: String, val deliveryPrice: BigDecimal,
val startDate: Instant, val settlementDate: Instant, val buySell: String,
val thisStateRef: StateRef) : FlowLogic<Unit>() {
// progress tracker redacted
#Suspendable
override fun call() {
progressTracker.currentStep = GENERATING_TRANSACTION
val input = serviceHub.toStateAndRef<ForwardState>(thisStateRef)
val output = ForwardState(initiator, acceptor, asset, deliveryPrice, startDate, settlementDate, buySell)
val beatCmd = Command(ForwardContract.Commands.Settle(), ourIdentity.owningKey)
val txBuilder = TransactionBuilder(serviceHub.networkMapCache.notaryIdentities.first())
.addInputState(input)
.addOutputState(output, FORWARD_CONTRACT_ID)
.addCommand(beatCmd)
progressTracker.currentStep = SIGNING_TRANSACTION
val signedTx = serviceHub.signInitialTransaction(txBuilder)
progressTracker.currentStep = FINALISING_TRANSACTION
subFlow(FinalityFlow(signedTx))
}
}
ForwardFlow initiates and has a Responder for both party signing.
Scheduled activity setup to respond once the settlementDate has been reached via the ForwardSettleFlow and Responder. This flow accepts thisStateRef in the class constructor. Testing showed that leaving this out made no difference to the error output. This process has two flows and two respective responders.
The crash shell for Party A freezes around the time of FINALISING_TRANSACTION during the ForwardFlow.
rx.exceptions.OnErrorNotImplementedException: A FlowLogicRef cannot be
constructed for FlowLogic of type com.template.ForwardSettleFlow: due
to missing constructor for arguments: [class
net.corda.core.contracts.StateRef]
I believe this stops the activity from ever occurring, including when the contract is blank during testing with no requirements.
FlowLogicRefFactory.create() has the following constructor:
override fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
When you invoke FlowLogicRefFactory.create() in ForwardState.nextScheduledActivity(), you do not pass any arguments at all:
flowLogicRefFactory.create("com.template.ForwardSettleFlow")
But there is no ForwardSettleFlow constructor that takes zero arguments:
class ForwardSettleFlow(val initiator: Party, val acceptor: Party, val asset: String,
val deliveryPrice: BigDecimal, val startDate: Instant, val settlementDate: Instant,
val buySell: String, val thisStateRef: StateRef) : FlowLogic<Unit>() {
And thus you get a "missing constructor" exception. Either you need to update ForwardSettleFlow to have a zero-argument constructor, or you need to pass some arguments to FlowLogicRefFactory.create().
I am trying to match the type of the nullable String? in a Kotlin reflection exercise:
data class Test(a: String, b: String?)
val test = Test("1", "2")
val properties = test::class.declaredMemberProperties
val propertyNames = properties.joinToString(",") {
when (it.returnType) {
String?::class.createType() -> "string?"
String::class.createType() -> "string"
else -> throw Exception()
}
}
Alas, it is failing with the error, Type in a class literal must not be nullable, for String?::class.
The createType function has an optional nullable parameter that seemed to work when I tested it.
import kotlin.reflect.full.*
String::class.createType(nullable = true) -> "string?"
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.