1
votes

The commands webinar states (at around 3:10) that "input and output states are always grouped by type and that a command is required for each group." The narrator seems to imply that if a transaction consists of multiple commands then each command will be associated with a distinct subset of the state transitions proposed in the transaction.

However such a view of things does not seem to be captured in the structure of LedgerTransaction. It consists of completely independent lists of inputs, outputs and commands. There's nothing denoting an association between particular commands and particular input or output states.

In my contract code I can group states by type, e.g.:

override fun verify(tx: LedgerTransaction) {
    val fooStates = tx.inputsOfType<FooState>()
    val barStates = tx.inputsOfType<BarStates>()

But I'm just grouping by choice - I don't have to, and there's nothing tying these groups to particular commands.

So what is the webinar referring to when it says "a command is required for each group"?

The sense behind signatures being associated with commands would be clear if the relationship between commands and state transitions existed as described in the webinar. However in reality one doesn't sign off on particular transitions on a per command basis as the LedgerTransaction class does not capture such relationships.

In the key concepts section on commands one has a coupon command and a pay command and it makes sense that the set of people who have to sign off on the bond state transition may be different to those who need to sign off on the cash state transition. But in the code there's nothing tying a coupon command to the particular states that the signers associated with the command are agreeing to if they sign.

Is this stated requirement that each group must have an associated command just something that the developer should implement in their contract verify logic without being something that one tries to capture in the structure of transactions?

1

1 Answers

3
votes

Good question.

You touched on grouping within the contract, and that's correct it is down to the contract implementation, you just need to extend it further to enforce which parties are required to sign depending on the command in the transaction.

So, your verify function might look like the below simplified version of the contract within the Option CorDapp sample:

override fun verify(tx: LedgerTransaction) {

    val command = tx.commands.requireSingleCommand<Commands>()

    when (command.value) {
        is Commands.Issue -> {
            requireThat {
                val cashInputs = tx.inputsOfType<Cash.State>()
                val cashOutputs = tx.outputsOfType<Cash.State>()
                "Cash.State inputs are consumed" using (cashInputs.isNotEmpty())
                "Cash.State outputs are created" using (cashOutputs.isNotEmpty())

                val option = tx.outputsOfType<OptionState>().single()
                "The issue command requires the issuer's signature" using (option.issuer.owningKey in command.signers)
            }
        }

        is Commands.Trade -> {
            requireThat {
                val cashInputs = tx.inputsOfType<Cash.State>()
                val cashOutputs = tx.outputsOfType<Cash.State>()
                "Cash.State inputs are consumed" using (cashInputs.isNotEmpty())
                "Cash.State outputs are created" using (cashOutputs.isNotEmpty())

                val inputOption = tx.inputsOfType<OptionState>().single()
                val outputOption = tx.outputsOfType<OptionState>().single()
                "The transfer command requires the old owner's signature" using (inputOption.owner.owningKey in command.signers)
                "The transfer command requires the new owner's signature" using (outputOption.owner.owningKey in command.signers)
            }
        }

        else -> throw IllegalArgumentException("Unknown command.")
    }
}

We first pull the command (or commands) from the transaction and that gives us context.

Based on that we can then pull the states we're interested in from either the inputs or outputs e.g. cash/option and begin to check that our constraints are met.

You can find the full version of the above sample at https://github.com/CaisR3/cordapp-option and the contract code can be found in base\src\main\kotlin\net\corda\option\base\contract\OptionContract.kt