ReceiveStateAndRefFlow does not receive StatesToRecord parameter - corda

In Corda, there is a flow which provides the functionality to send SignedTransaction to another party.
open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) : DataVendingFlow(otherSide, stx)
And another flow, which sends StatesAndRefs to another party:
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
On the acceptor's side corresponding receiver flow should be invoked:
open class ReceiveTransactionFlow #JvmOverloads constructor(private val otherSideSession: FlowSession,
private val checkSufficientSignatures: Boolean = true,
private val statesToRecord: StatesToRecord = StatesToRecord.NONE) : FlowLogic<SignedTransaction>()
or
class ReceiveStateAndRefFlow<out T : ContractState>(private val otherSideSession: FlowSession) : FlowLogic<#JvmSuppressWildcards List<StateAndRef<T>>>() // which invokes **ReceiveTransactionFlow** with **StatesToRecord.NONE**
The key difference between these two acceptor flows is that ReceiveStateAndRefFlow will resolve SignedTransaction on acceptor's side, but will not record states to the counterparties vault. On the other, hand: ReceiveTransactionFlow accepts StatesToRecord property and will store states into the counterparty's vault.
Question: What is the rationale that ReceiveStateAndRefFlow doesn't accept StatesToRecord property?

Turns out there is no particular reason why StatesToRecord cannot be set but there are some tricky edge cases to deal with which may end up in CorDapp developers messing up their vault!
For example: when you force the storing of a single state in the vault the corresponding output in the same transaction will not be stored, resulting you having an unconsumed state in the vault which is actually consumed.
Normally, this wouldn't matter too much because you can't spend it anyway. However, it will result in confusing vault query behaviour.

Related

Deserialisation error and logging the partition, topic and offset

I am handling deserialisation error using the ErrorHandlingDeserialiser sent on my DefaultKafkaConsumerFactory.
I have code a custom
try (ErrorHandlingDeserializer<MyEvent> errorHandlingDeserializer = new ErrorHandlingDeserializer<>(theRealDeserialiser)) {
errorHandlingDeserializer.setFailedDeserializationFunction(myCustomFunction::apply);
return new DefaultKafkaConsumerFactory<>(getConsumerProperties(), consumerKeyDeserializer, errorHandlingDeserializer);
}
My custom function does some processing and publishes to a poison pill topic and returns null.
When a deserialisation error occurs, I would like to log the topic, partition and offset. The only way I can think of doing this is to stop returning null in the function and return a new sub type of MyEvent. My KafkaListener could then interrogate the new sub type.
I have a #KafkaListener component, which listens for the ConsumerRecord as follows:
#KafkaListner(....)
public void onMessage(ConsumerRecord<String, MyEvent> record) {
...
...
// if record.value instance of MyNewSubType
// I have access to the topic, partition and offset here, so I could log it here
// I'd have to check that the instance of MyEvent is actually my sub type representing a failed record.
}
Is this the way to do it? I know null has special meaning Kafka.
The downside of this sub type approach is, I'd have to create a subtype every type using the ErrorHandlingDeserialiser.
Don't use a function; instead, the thrown DeserializationException is passed directly the container's ErrorHandler.
The SeekToCurrentErrorHandler considers these exceptions to be fatal and won't retry them, it passes the record to the recoverer.
There is a provided DeadLetterPublishingRecoverer which sends the record.
See https://docs.spring.io/spring-kafka/docs/current/reference/html/#annotation-error-handling
and
https://docs.spring.io/spring-kafka/docs/current/reference/html/#dead-letters

Error calling cordaRPCOps.startFlowDynamic()

I am building an RPC interface that calls cordaRPCOps.startFlowDynamic() and got the following error:
Class "class com.sun.proxy.$Proxy29" is not on the whitelist or
annotated with #CordaSerializable.
I accidentally included an extra parameter to the cordaRPCOps.startFlowDynamic() call:
FlowHandle<SignedTransaction> flowHandle = cordaRPCOps.startFlowDynamic(
FungibleTokenRedeem.class,
amount,
issuer,
observers,
queryCriteria,
changeHolder,
cordaRPCOps // !!! Extra parameter !!!
);
Here is the constructor for the Flow:
public FungibleTokenRedeem(Amount<TokenType> amount, Party issuer, List<Party> observers, QueryCriteria queryCriteria, AbstractParty changeHolder) {
this.amount = amount;
this.issuer = issuer;
this.observers = observers;
this.queryCriteria = queryCriteria;
this.changeHolder = changeHolder;
}
Normally when you have too many, too few or the wrong type of parameter to cordaRPCOps.startFlowDynamic() you get a nice error that helps you identify what you did wrong:
net.corda.core.flows.IllegalFlowLogicException: A FlowLogicRef cannot
be constructed for FlowLogic of type
com.template.flows.FungibleToken.FungibleTokenRedeem: due to ambiguous
match against the constructors
In the past when I accidentally included an extra parameter it just happened to be one that is on Corda’s whitelist (i.e. String, List<> etc.). This time the class happened to be class CordaRPCOps (copy and paste mistake) which is not on the whitelist or annotated with #CordaSerializable and this more cryptic error message was the result. I just wanted a record of it in case someone makes the same mistake.

Parsing Amount<Currency> in Corda shell

I'd like to initiate a flow in Corda (v3.3) shell with
flow start IOUIssueFlow state: { newIOUState: { amount: $100 } }
(the rest of the flow parameters are cut for brevity.)
however the parsing fails with
No matching constructor found:
- [com.template.IOUState]: Could not parse as a command: Instantiation of [simple type, class com.template.IOUState] value failed for JSON
property amount due to missing (therefore NULL) value for creator
parameter amount which is a non-nullable type at [Source: UNKNOWN;
line: -1, column: -1] (through reference chain:
com.template.IOUState["amount"])
IOUIssueFlow's constructor has a single state parameter of type IOUState. IOUState's constructor starts with:
data class IOUState(val amount: Amount<Currency>,
val lender : Party,
val borrower: Party,
val paid : Amount<Currency> = Amount(0, amount.token),
override val linearId: UniqueIdentifier = UniqueIdentifier()): LinearState {...
What am I missing here?
I have the same problem and it looks like bug in jackson. I wasn't able to construct an object as parameter in Corda shell, so I had to fallback to providing all necessary "simple" parameters as input of the Flow and constructing the object inside it.

How to pass signers inside a flow in new state creation under "Event Scheduling " in Corda?

I have incorporated "Event-Scheduling" functionality in one state , which when accepted will in turn create another new state.The new state creation is triggered by Event scheduling function.I'm setting Initiator in the new state creation flow.
val session = initiateFlow(output.IOT)
session.send(true)
Also , in responder flow,I'm trying to pass signers.There as well, we are calling Initiate flow as below .
val sessions = listOf(SiemenParty, CustomerParty).toSet().map { party: Party -> initiateFlow(party) }.toSet()
val fullySignedTx = subFlow(CollectSignaturesFlow(partSignedTx, sessions, Companion.GATHERING_SIGS.childProgressTracker()))"
I'm getting exception at CollectSignatureFlow like :
com.template.flow.CreateIOTFlow$Acceptor, as a flow that initiates other flows, must be annotated with net.corda.core.flows.InitiatingFlow.
What am i doing wrong ? How to set signers ?
you are missing #InitiatingFlow annotation over your flow as result you are facing this exception. as you are using initiateFlowthe flow must be annotated with #InitiatingFlow annotation

How to upgrade stateV1 to V2 without changing the Contract version?

I have an ObligationV1 and two states ObligationStateV1 and ObligationStateV2.
How do I achieve A state is upgraded while the contract stays the same. where the state goes from V1 to V2 without changing the contract version. Based on the examples exampleLink, docs
It seems that the code will end up looking like this where you have a new ObligationContractV2? The example was trying to achieve
This CorDapp shows how to upgrade a state without upgrading the Contract. But I don't see how does the implementation actually prove that the new states is still referring to the old contract?
open class ObligationContractV2 : UpgradedContractWithLegacyConstraint {
override val legacyContract: ContractClassName = ObligationContractV1.id
override val legacyContractConstraint: AttachmentConstraint = AlwaysAcceptAttachmentConstraint
override fun upgrade(oldState: ObligationStateV1) = ObligationContractV2.ObligationStateV2(oldState.a, oldState.b, 0)
data class ObligationStateV2(val a: AbstractParty, val b: AbstractParty, val value:Int ) : ContractState {
override val participants get() = listOf(a, b)
}
override fun verify(tx: LedgerTransaction) {}
}
The contract class must change whenever you upgrade a state, but the rules it imposes can remain the same.
You could achieve this by delegating the transaction checking to the old contract:
override fun verify(tx: LedgerTransaction) {
ObligationContractV1().verify()
}
You could also delegate the checking to the old contract, and adding additional checks:
override fun verify(tx: LedgerTransaction) {
ObligationContractV1().verify()
additionalChecks()
}
However, note that delegating verify in this way while upgrading states will only work if the original contract isn't hardcoded to verify the transaction in terms of the old state. You'd have to write the original contract in terms of some interface or abstract class implemented by both the old state class and the new state class, or in some other way write the old contract in an open-ended manner. If you didn’t write the old contract in this forward-thinking way initially, you'll have to re-write the verify method.

Resources