0
votes

Our Corda network has 3 nodes in addition to the notary. The image shows what each node should do.

Corda nodes

Only in this scenario we are in trouble "Need to move tokens from account holder to Party B"

Flow code:


    class TransferETokenDiffNodeFlow(val actualHolder: AbstractParty,
                                     val newHolder: AbstractParty,
                                     val numEtokens: Double,
                                     val observables: MutableList = mutableListOf()) :
            FlowLogic() {
    
        private fun notary() = serviceHub.networkMapCache.notaryIdentities.first()
    @Suspendable
    override fun call(): SignedTransaction {
    
        progressTracker.currentStep = INITIALIZING
        val txBuilder = TransactionBuilder(notary())
    
        val actualHolderStateRef = accountService.accountInfo(actualHolder.owningKey)
                ?: throw AccountNotFoundException("Account not found exception.")
        val actualHolderInfo = actualHolderStateRef.state.data
    
        val actualHolderSession = initiateFlow(actualHolderInfo.host)
    
        actualHolderSession.send(numEtokens)
        actualHolderSession.send(actualHolder)
        actualHolderSession.send(newHolder)
    
        val inputs = subFlow(ReceiveStateAndRefFlow(actualHolderSession))
        val tokens: List = actualHolderSession.receive>().unwrap { it -> it}
    
        progressTracker.currentStep = BUILDING
        addMoveTokens(txBuilder, inputs, tokens)
    
        progressTracker.currentStep = SIGNING
        val initialSignedTrnx = serviceHub.signInitialTransaction(txBuilder)
    
        progressTracker.currentStep = GATHERING_SIGS
        val fulySignedTx= subFlow(CollectSignaturesFlow(initialSignedTrnx, listOf(actualHolderSession)))
    
        progressTracker.currentStep = FINALISING_CREATE
        val stx = subFlow(FinalityFlow(fulySignedTx, listOf(actualHolderSession)))
    
        progressTracker.currentStep = FINALISING
        val statesTx = stx.tx.outRefsOfType()
        statesTx.forEach { state ->
            observables.forEach { observable ->
                subFlow(ShareStateAndSyncAccounts(state, observable))
            }
        }
    
        return stx
    }
    }
    
    
    //ResponderFlow code:
    class TransferETokenDiffNodeFlowResponder(val counterpartySession: FlowSession) : FlowLogic() {
    @Suspendable
    override fun call(): SignedTransaction {
    
        val numEtokens = counterpartySession.receive().unwrap { it }
        val actualHolder = counterpartySession.receive().unwrap { it }
        val newHolder = counterpartySession.receive().unwrap { it }
    
        val partyAndAmount = PartyAndAmount(newHolder, numEtokens of EnergyTokenType.getInstance("ENERGY"))
    
        val actualHolderStateRef = accountService.accountInfo(actualHolder.owningKey)
                ?: throw AccountNotFoundException("Account not found exception.")
        val actualHolderInfo = actualHolderStateRef.state.data
        val criteria = QueryCriteria.VaultQueryCriteria(externalIds = listOf(actualHolderInfo.identifier.id),
                status = Vault.StateStatus.UNCONSUMED)
    
        val selector = DatabaseTokenSelection(serviceHub)
    
        val (inputs, outputs) = selector.generateMove(listOf(partyAndAmount).toPairs(),
                actualHolder, TokenQueryBy(queryCriteria = criteria), runId.uuid)
    
        subFlow(SendStateAndRefFlow(counterpartySession, inputs))
    
        counterpartySession.send(outputs)
    
        subFlow(object : SignTransactionFlow(counterpartySession) {
            @Throws(FlowException::class)
            override fun checkTransaction(stx: SignedTransaction) {
            }
        })
    
        return subFlow(ReceiveFinalityFlow(counterpartySession))
    }
    }

We need to execute the flow at Party C and the actualHolder is the account holder and the newHolder is Party B.

With this code the returns an error: net.corda.core.CordaRuntimeException: java.lang.IllegalArgumentException: Flow sessions were not provided for the following transaction participants: [O = Party B, L = Curitiba, C = BR]

But if I change the code and add the Party B session, it returns the error: java.lang.IllegalArgumentException: The Initiator of CollectSignaturesFlow must pass in exactly the sessions required to sign the transaction.

The question is, why doesn't addMoveTokens put newHolder as a required signer? And how can I solve this problem?

1

1 Answers

1
votes

There are many points to discuss in your code; let's start with the error:

  1. When you move tokens, the only required signer is the current holder; so in your case you should pass only one FlowSession which is the session with PartyA (since it's the holder of the tokens); so you should only have:
    CollectSignaturesFlow(initialSignedTrnx, listOf(actualHolderSession))
    
  2. As for finalizing the transaction, sessions for all participants must be passed. The union of all participants in your case is the current holders of the tokens (i.e. PartyA) and the new holders (i.e. PartyB); so you should have:
    FinalityFlow(fulySignedTx, listOf(actualHolderSession, partyB))
    
    That's why when you passed only actualHolderSession for both sub-flows; you received that a session is missing (because finality flow wants a session for PartyB as well); and when you added PartyB session for both, the collect signatures flow complained that you're passing an extra session (PartyB isn't required to sign the move command, only the current holder is).
  3. Since your responder flow is handling both tasks (signing and finalizing); you must send from the initiator to the responder some data (String or whatever you choose) to signify the role of the responder, so for PartyA you would send SignerAndFinalizer; while for PartyB you send Finalizer; and in the beginning of your responder, you receive the "role" and act accordingly (i.e. if it's Finalizer you don't call SignTransactionFlow, only ReceiveFinalityFlow).

Now to other topics:

  1. I don't recommend that a party moves tokens that are held by another party. Imagine you own money in your account, and I move that money from your account. It should be the node that holds the tokens that initiates the move. Actually that's how the ready flows of the SDK operate.

    If you look at the AbstractMoveTokensFlow you will see that they only sign the transaction locally by relying on ObserverAwareFinalityFlow (see here); to confirm that, you can see that inside ObserverAwareFinalityFlow there is no CollectSignaturesFlow (i.e. it doesn't request signatures from other nodes), only signs locally (see here).

    What this all means is that if you used the ready flow of the SDK to move tokens that are held by a node different than the node that's calling the move flow; you'll get an error, because the signature of the holder is required, but the ready flow doesn't collect signatures from other nodes, only from the node that called the move flow, which isn't the holder of the tokens.

    I recommend following the approach of the SDK, in that only the holder of the tokens can move their tokens.

  2. Another things mentioned by the Accounts library documentation is not to mix using accounts and non-accounts (i.e. only move from account to account or from party to party); it is advisable instead to create a "default" account for your node (e.g. create an account with a name that matches the X500 name of your node), read here.