Implementation Of Amend Command


(Deepak Kumar Purohit) #1

Hi All,

When I try to apply Amend Command, In the verify() of amend , the output parameter show list size is 0.
And endup with error stating java.util.NoSuchElementException: List is empty.

Please tell me who is invoking this verify(-) method so that I can check where I am doing mistake.

Thanks for any Help


Example Code to Create Output State
(Joel Dudley) #2

Hi Deepak,

It depends on the flow you implement. If the notary is a validating notary, they will call this function. Additionally, in the ExampleFlow, both parties run the Place clause’s verify() function to check that the transaction is valid.

If you’re seeing weird values for the inputs/outputs size, double-check the Group clause - you should be including the Amend clause in the AnyComposition() call. If the states aren’t being grouped correctly, you can end up in a situation where verify() is run once on the inputs and once on the outputs, causing the size constraints to fail.

Since you’re implementing multiple clauses, also make sure you override requiredCommands in each clause - see https://docs.corda.net/tutorial-contract-clauses.html.


(Deepak Kumar Purohit) #3

Hi Joel,
Thank you for quick response.

I have tried the way you said. But doesn’t charges the result. Still I am getting the same error.

Thank you .


(Joel Dudley) #4

Since you’re implementing Amend and Corda states are immutable, how are you implementing the amendment?

Since the states are grouped by linearId, even if you create an amended state with the same values for each field, they will likely have different linearIds, and therefore the GroupClauseVerifier will handle them incorrectly. You need to ensure that the old and new states have identical IDs.

Does that make sense?


(Deepak Kumar Purohit) #6

Thank @joeldudley

Its working now but some doubts we got. To make It work we did following changes…

1. In the above code , we remover requireThat{ … } part.
2. In same code, we are getting outputs size =0.
The verify function executes 2 times when the Flow is execution.
The very first time it gives input state as 0 and output states size as 1.
Second time it gives 1 as input states Size and 0 as Output states size

Thanks for Help


(Alexandre Makiyama) #7

Hi Deepak,

I’m trying to implement the Amend() clause too. One thing I notice is that the Amend clause is invoked twice each time I invoke the amendment flow. Did you end up with the same situation? Did you solve the problem you’re having?

Alexandre

PS. As Joel said, I ensured the old and the new state have the same linearIds, but even so, the clause is being invoked twice


(Deepak Kumar Purohit) #8

Hi @amakiyama.

Yes, In my case also, the verify is invoked twice. Once for Initiator and 2nd time for Acceptor. As I believe that both the parties have to agree on the Clause conditions.

Since you’re implementing multiple clauses, also make sure you override requiredCommands in each clause - see https://docs.corda.net/tutorial-contract-clauses.html

This solves the problem.

If anything,please feel free to post.


(Deepak Kumar Purohit) #9

Hi,

While Amending or Updating any record, the previous state is consumed.
But how to perform partial consumption.

Like I have some $1000 out of which I want to send $200 to X. How to achieve this functionality.

Thanks for any Help


(Alexandre Makiyama) #10

Hi @depurohi.

Thanks for the reply. Actually, the Amend() clause is invoked twice in the Initiator. I noticed that when I saw the log file.
I’ll keep investigating it, and if I find out what’s hapenning, I’ll inform the problem.

Thanks,
Alexandre


(Roger Willis) #11

I haven’t looked at this yet but it might be due to use of AnyComposition() as opposed to FirstComposition() - see if that makes a difference. Cheers


(Alexandre Makiyama) #12

Hi @roger.

I changed and it’s the same behavior.

Thanks,
Alexandre


(Roger Willis) #13

Thanks @amakiyama I’ll have a look at this today… Haven’t done any coding for a while :frowning:


(Deepak Kumar Purohit) #14

Hi @roger,

On Amed,command as we were getting output size 0 we are unable to apply any validations.
Please suggest correct approach to implement the same.

Thanks


(Roger Willis) #15

Could you mail me your code or push it to github? I have the following code to “Amend” a PurchaseOrder, the amend in my case is just switching a flag from “pending” to “approved”.

Flow code:

    @Suspendable
    override fun call(): ApproveFlowResult {
        // Naively, wrapped the whole flow in a try ... catch block so we can
        // push the exceptions back through the web API.
        try {
            val myKeyPair = serviceHub.legalIdentityKey
            val notary = serviceHub.networkMapCache.notaryNodes.single().notaryIdentity

            // Stage 1 - Retrieve the input purchaseOrderState
            progressTracker.currentStep = RETRIEVING_PURCHASE_ORDER
            val purchaseOrders = serviceHub.vaultService.currentVault.statesOfType<PurchaseOrderState>()
            val poStateAndRef = purchaseOrders.find { it.ref.txhash.toString() == txHash && it.ref.index.toString() == index }!!

            // Stage 2 - Create the updated output purchase order
            progressTracker.currentStep = UPDATING_PURCHASE_ORDER
            val oldPurchaseOrderState = poStateAndRef.state.data
            val newPurchaseOrderState = oldPurchaseOrderState.copy(status = "Approved")

            // Stage 3 - Generate an unsigned transaction
            progressTracker.currentStep = CONSTRUCTING_TRANSACTION
            val txBuilder = TransactionType.General.Builder(notary)
                    .withItems(
                            // Input state.
                            poStateAndRef,
                            // Command.
                            Command(
                                    PurchaseOrderContract.Commands.Approve(),
                                    serviceHub.myInfo.legalIdentity.owningKey),
                            // Output state.
                            newPurchaseOrderState
                    )

            // Stage 4 - Add a timestamp, as mandated by the contract code
            progressTracker.currentStep = TIMESTAMPING_TRANSACTION
            val currentTime = serviceHub.clock.instant()
            txBuilder.setTime(currentTime, 30.seconds)

            // Stage 5 - Sign the transaction
            progressTracker.currentStep = SIGNING_TRANSACTION
            val sigTx = txBuilder.signWith(myKeyPair).toSignedTransaction(checkSufficientSignatures = false)

            // Stage 6 - Obtain the notary's signature
            progressTracker.currentStep = NOTARISING_TRANSACTION
            val notarySignature = subFlow(NotaryFlow.Client(sigTx))
            val notTx = sigTx + notarySignature

            // Stage 7 - Record the transaction in our vault
            progressTracker.currentStep = RECORDING_TRANSACTION
            serviceHub.recordTransactions(listOf(notTx))

            // Stage 8 - Send the transaction to the counterparty
            progressTracker.currentStep = SENDING_TO_SELLER
            send(otherParty, notTx)

            return ApproveFlowResult.Success("Transaction approved: ${notTx.id}.")
        } catch(ex: Exception) {
            println(ex)
            // Just catch all exception types.
            return ApproveFlowResult.Failure(ex.message)
        }
    }

PurchaseOrderContract with Approve (amend) command:

open class PurchaseOrderContract() : Contract {
/**
 * The AllComposition() clause mandates that all specified clauses clauses (in this case, only [Group])
 * must be executed and valid for a transaction involving this type of contract to be valid.
 */
override fun verify(tx: TransactionForContract) =
        verifyClause(tx, Clauses.Group(), tx.commands.select<Commands>())

/** Currently this contract only implements one command. */
interface Commands : CommandData {
    data class Approve(override val nonce: Long = random63BitValue()) : IssueCommand, Commands
    data class Place(override val nonce: Long = random63BitValue()) : IssueCommand, Commands
}

/** This is where we implement our clauses. */
interface Clauses {
    // If you add additional clauses, make sure to reference them within the 'AnyComposition()' clause.
    class Group : GroupClauseVerifier<PurchaseOrderState, Commands, UniqueIdentifier>(AnyComposition(Place(), Approve())) {
        override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<PurchaseOrderState, UniqueIdentifier>>
                // Group by purchase order linearId for in / out states
                = tx.groupStates(PurchaseOrderState::linearId)
    }

    class Approve : Clause<PurchaseOrderState, Commands, UniqueIdentifier>() {
        override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Approve::class.java)

        override fun verify(tx: TransactionForContract,
                            inputs: List<PurchaseOrderState>,
                            outputs: List<PurchaseOrderState>,
                            commands: List<AuthenticatedObject<Commands>>,
                            groupingKey: UniqueIdentifier?): Set<Commands> {
            val command = tx.commands.requireSingleCommand<Commands.Approve>()
            val in_ = inputs.single()
            val out = outputs.single()
            requireThat {
                // Generic constraints around generation of the issue purchase order transaction.
                "One input state should be consumed." by (inputs.size == 1)
                "One output state should be created." by (outputs.size == 1)
                "The buyer and the seller cannot be the same entity." by (out.buyer != out.seller)
                "The seller must be a signer." by (command.signers.contains(out.seller.owningKey))

                // Status constraints.
                "The input's status must be pending." by (in_.status == "Pending")
                "The output's status must be approved." by (out.status == "Approved")

                // Other-attributes-unchanged constraints.
                "We only deliver to the UK." by (in_.purchaseOrder == out.purchaseOrder)
                "You must order at least one type of item." by (in_.buyer == out.buyer)
                "You cannot order zero or negative amounts of an item." by (in_.seller == out.seller)
                "You can only order up to 100 items in total." by (in_.linearId == out.linearId)
            }

            return setOf(command.value)
        }
    }

    class Place : Clause<PurchaseOrderState, Commands, UniqueIdentifier>() {
        override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Place::class.java)

        override fun verify(tx: TransactionForContract,
                            inputs: List<PurchaseOrderState>,
                            outputs: List<PurchaseOrderState>,
                            commands: List<AuthenticatedObject<Commands>>,
                            groupingKey: UniqueIdentifier?): Set<Commands> {
            val command = tx.commands.requireSingleCommand<Commands.Place>()
            val out = outputs.single()
            requireThat {
                // Generic constraints around generation of the issue purchase order transaction.
                "No inputs should be consumed when issuing a purchase order." by (inputs.isEmpty())
                "Only one output state should be created for each group." by (outputs.size == 1)
                "The buyer and the seller cannot be the same entity." by (out.buyer != out.seller)
                "The buyer must be a signer." by (command.signers.contains(out.buyer.owningKey))

                // Purchase order specific constraints.
                "We only deliver to the UK." by (out.purchaseOrder.deliveryAddress.country == "UK")
                "You must order at least one type of item." by (out.purchaseOrder.items.isNotEmpty())
                "You cannot order zero or negative amounts of an item." by (out.purchaseOrder.items.map(Item::amount).all { it > 0 })
                "You can only order up to 100 items in total." by (out.purchaseOrder.items.map(Item::amount).sum() <= 100)
                val time = tx.timestamp?.midpoint
                "There must be a timestamp." by (time != null)
                "The delivery date must be in the future." by (out.purchaseOrder.deliveryDate.toInstant() > time)
                "The status must be pending." by (out.status == "Pending")
            }

            return setOf(command.value)
        }
    }
}

/** This is a reference to the underlying legal contract template and associated parameters. */
override val legalContractReference: SecureHash = SecureHash.sha256("purchase order contract template and params")

}


(Alexandre Makiyama) #16

Hi @roger,

I’ve just pushed the code to the repository https://github.com/xandemaki/general_tests. Could you make your considerations about the Amendment implementation?

I’ve put some logs in the code. The issue I noticed is that the Amend verify function is being invoked twice, according to the logs:

[INFO ] 2017-01-16T14:48:48,968 [Node thread] PurchaseOrderContract - Amend clause invoked!
[INFO ] 2017-01-16T14:48:48,968 [Node thread] PurchaseOrderContract - Inputs: 1
[INFO ] 2017-01-16T14:48:48,968 [Node thread] PurchaseOrderContract - Outputs: 1
[INFO ] 2017-01-16T14:48:48,968 [Node thread] PurchaseOrderContract - Amend clause invoked!
[INFO ] 2017-01-16T14:48:48,968 [Node thread] PurchaseOrderContract - Inputs: 1
[INFO ] 2017-01-16T14:48:48,968 [Node thread] PurchaseOrderContract - Outputs: 1

Thank you for your support,
Alexandre


(Roger Willis) #17

I’ll have a look today - cheers!


(Alexandre Makiyama) #18

Hi @roger,

One thing I noticed in the code you’ve posted is that it doesn’t invoke the “verify” function. At which point the Approve clause would be executed in that case?

Thanks,
Alexandre


(Roger Willis) #19

Hi Alexandre, apologies not had a chance to look at this yet. Will set some time aside this afternoon! The Approve clause would be invoked when an Approve command in included in a transaction. The code: val command = tx.commands.requireSingleCommand<Commands.Approve>() inside the Approve command matches the command to the clause.


(Alexandre Makiyama) #20

Hi @roger. No problem, take your time.

Regarding your code, I thought the line below should be placed somewhere (based on the CordApp-template):

.toLedgerTransaction(serviceHub).verify()

Cheers,
Alexandre


(Loup Theron) #21

Hi Roger,

May I ask you why in the Flow you provided there is no “Acceptor” of the ApproveFlowResult ?
For me the other party must also sign this transaction and call FinalityFlow() to notarises the transaction and records it in each party’s vault, am I wrong ?

Thank you