#Corda Unable to process a transaction containing big amount of states due to exceeded message size limit #Corda


oleksandra@...
 

Hi guys,
we've faced an issue that during the CollectionSignatures and Finalization of the transaction the net.corda.nodeapi.internal.ArtemisUtils will throw an error:
Message exceeds maxMessageSize network parameter, maxMessageSize: [10485760] when the number of the states in one transaction is big.
I understand that this is a logical/robust check on the ArtemisMQ side since as more bytes 1 message contains - more chances to fail during the network transmission.
The questions and details of this topic are more related to:
  • Could anyone provide information on what is the best way/practice in Corda API of handling such scenarios of a big amount of states and still has a transactional flow? 
  • Can we somehow send the states in subflows, but finalize them only in the parent flow?
  • Is there any way of sending the states in 1 transaction by parts(chunks) or as a stream to prevent exceeding the limits but still have the transactional flow?
I'll try to describe the details with code examples of the issue:

Use case: we have the states which represents obligations (Obligation State), and the state that represents a payment. One payment state can cover multiple obligations and settle them. So the structure of the Payment is:
 
    data class PaymentState(
    val paymentItems: List<PaymentItem>,
    override val linearId: UniqueIdentifier = UniqueIdentifier()
    ) : LinearState, QueryableState { ... }
 
    data class PaymentItem(
    val id: UUID = UUID.randomUUID(),
    val obligationId: UUID,
    val amount: Amount
    ) // not a state just a child
 
    data class ObligationState(
    val amount: Amount, // initial obligation amount
    val availableAmount, // initially = remaining/settled/paid amount, and will be reduced when be payed/settled 
    override val linearId: UniqueIdentifier = UniqueIdentifier()
    ) : LinearState, QueryableState { ... }
 
The user1 creates 1000 obligations during the month (not a single operation)
The user2 pays for 1000 obligations and wants to do it in one transaction.
One corda transaction can be described like this:
 - Input State and Refs - 1000 obligation state&refs (prev versions)
 - Output States - 1000 obligation states with updated money (states that have to be updated), 1 Payment State (state that has to be created)
 
So the described example contains a lot of states in the same transaction which leads us to have a huge message to be sent to another party. And potentially have a situation when the message will exceed its limit (for example if the Payment was raised to cover more states ~1300 will exceed the limit).
 
So we've tried to think about diving this transaction into multiple small parts. But faced that if we do it separately it will lead to the situation when one of them possibly can fail, since the others were Finalized they can't be rollbacked, since it's no more than 1 transaction that can be rollbacked if smth happened.  
 
Please find the code in the attachment PaymentCreateDemo.kts (not actual, simplified one, in order to reflect the work with states, flows, subflows). The first attempt of resolving msg limits error.
 
The dividing into pieces we've done in two approaches, firstly we've tried to update the ObligationStates chunked them by 500 in one tx (each tx processing in the subflow separately), and when all txs completed it's ready to create a PaymentState in another tx. This has two drawbacks, 1st it's no more than 1 transaction and the rollback of prev txs is impossible, the 2nd - PaymentState can have any number of PaymentItems and if it's approximately 86.000 it will exceed the limit of message size too.
 
The second approach was to have an additional state to aggregate the PaymentItems in groups. Each group has a max size=500 of items. So if it's 1000 items it will be divided into 2 groups for 1 payment. This resolved the issue of the second drawback described in a prev paragraph.
 
    data class PaymentState(
    override val linearId: UniqueIdentifier = UniqueIdentifier(),
    val version: Int
    // no more contains items
    ) : LinearState, QueryableState { ... }
 
    data class PaymentItemGroup(
    val paymentStateId: UUID, // check always the head state
    val paymentItems: List<PaymentItem>,
    override val linearId: UniqueIdentifier = UniqueIdentifier()
    ) : LinearState, QueryableState { ... }
 
I've attached schemas describing the sequential processing of the subflows and parallel one - to speed up the sharing process.
But still, we have a situation when one of the transactions will fail and only part of the actual payment was reflected as paid/settled on obligations.

 
Thanks