mirror of
https://github.com/corda/corda.git
synced 2024-12-19 13:08:04 +00:00
Merged in kstreich-clause-docs (pull request #552)
docs: Add clauses documentation.
This commit is contained in:
commit
0a95928e0c
278
docs/source/clauses.rst
Normal file
278
docs/source/clauses.rst
Normal file
@ -0,0 +1,278 @@
|
||||
Clauses key concepts
|
||||
====================
|
||||
|
||||
Basic clause structure
|
||||
----------------------
|
||||
|
||||
A clause is a small building block for assembling contract verification logic, reusable and ready to test in separation.
|
||||
To see clauses in action go to: :doc:`tutorial-contract-clauses`.
|
||||
Let's take a look at a simplified structure of the ``Clause`` class:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
abstract class Clause<in S : ContractState, C : CommandData, in K : Any> {
|
||||
|
||||
/** Determine whether this clause runs or not */
|
||||
open val requiredCommands: Set<Class<out CommandData>> = emptySet()
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
abstract fun verify(tx: TransactionForContract,
|
||||
inputs: List<S>,
|
||||
outputs: List<S>,
|
||||
commands: List<AuthenticatedObject<C>>,
|
||||
groupingKey: K?): Set<C>
|
||||
...
|
||||
}
|
||||
|
||||
Basic clause structure contains two important components: ``requiredCommands`` and ``verify`` function.
|
||||
A clause is triggered when all ``requiredCommands`` are present in transaction's command set (opposite inclusion doesn't have to hold).
|
||||
Then the ``verify`` function is run, which checks if transaction meets conditions specified by this clause. Verification
|
||||
is no different than normal contract verification but using clauses it's split into smaller generic code blocks with single verify method.
|
||||
|
||||
When writing a contract you need to override the contract's ``verify`` function which should call ``verifyClause``. See: :ref:`verify_ref`.
|
||||
|
||||
.. note:: A clause ``verify`` function returns the set of processed commands, at the end of ``verifyClause`` execution
|
||||
there is a check if all of transaction's commands were matched. If not then an exception is raised. This is done to
|
||||
enforce that spurious commands cannot be included in a transaction, ensuring that the transaction is as clear as
|
||||
possible. As an example imagine a transaction with two commands: ``Move`` and ``Issue`` included, with verification written
|
||||
using ``FirstComposition`` on clauses that require single command set. Thus only one of transaction's commands will match
|
||||
leaving the second unprocessed. It should raise an error - we want to ensure that commands set is minimal to simplify
|
||||
analysis of intent of a transaction.
|
||||
|
||||
An example ``verify`` from ``Obligation`` contract:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
override fun verify(tx: TransactionForContract) = verifyClause<Commands>(tx, FirstComposition<ContractState, Commands, Unit>(
|
||||
Clauses.Net<Commands, P>(),
|
||||
Clauses.Group<P>()
|
||||
), tx.commands.select<Obligation.Commands>())
|
||||
|
||||
It takes transaction to be verified, and passes it along with a top-level clause and commands to the ``verifyClause``
|
||||
function. As you can see above we have used ``FirstComposition`` which is a special type of clause, which extends the
|
||||
``CompositeClause`` abstract class (in that particular case, it ensures that either ``Net`` or ``Group`` will run - for explanation see `FirstComposition`_).
|
||||
It's a type of clause that adds support for encapsulating multiple clauses and defines common behaviour for that composition.
|
||||
There is also a ``GroupClauseVerifier`` special clause, which specifies how to group transaction input/output states
|
||||
together and passes them to adequate clause for further processing.
|
||||
|
||||
Composition clauses
|
||||
-------------------
|
||||
|
||||
One of the most important concepts of clauses - composition clauses which extend ``CompositeClause`` abstract class,
|
||||
providing a range of ways of assembling clauses together. They define a logic of verification execution specifying which clauses
|
||||
will be run.
|
||||
|
||||
AllComposition
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
**Description**
|
||||
|
||||
Composes a number of clauses, such that all of the clauses must run for verification to pass.
|
||||
|
||||
.. image:: resources/allCompositionChart.png
|
||||
|
||||
Short description:
|
||||
|
||||
- ``AllComposition`` holds clauses *Cl1,..,Cl5*.
|
||||
- Check if all clauses that compose ``AllComposition`` have associated commands in a command set - if not, verification fails.
|
||||
- After successful check runs verification logic specific for every clause *Cl1,..,Cl5* from that composition.
|
||||
|
||||
**Usage**
|
||||
|
||||
See code in `GroupClauseVerifier`_.
|
||||
|
||||
AnyComposition
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
**Description**
|
||||
|
||||
Composes a number of clauses, such that 0 or more of the clauses can be run.
|
||||
|
||||
.. image:: resources/anyCompositionChart.png
|
||||
|
||||
Short description:
|
||||
|
||||
- Checks if zero or more clauses that compose AnyComposition have associated commands in a command set.
|
||||
- After success runs verification logic specific for every *matched* (in this case *Cl2, Cl4, Cl5*) clause from composition.
|
||||
|
||||
**Usage**
|
||||
|
||||
Example from ``CommercialPaper.kt``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class Group : GroupClauseVerifier<State, Commands, Issued<Terms>>(
|
||||
AnyComposition(
|
||||
Redeem(),
|
||||
Move(),
|
||||
Issue())) {
|
||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, Issued<Terms>>>
|
||||
= tx.groupStates<State, Issued<Terms>> { it.token }
|
||||
}
|
||||
|
||||
FirstComposition
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
**Description**
|
||||
|
||||
Composes a number of clauses, such that the first match is run, and it errors if none is run.
|
||||
|
||||
.. image:: resources/firstCompositionChart.png
|
||||
|
||||
Short description:
|
||||
|
||||
- Takes first clause that matches and if none found throws an exception.
|
||||
- If successful runs verification on the clause that matched (in this case *Cl4*).
|
||||
|
||||
**Usage**
|
||||
|
||||
See code in `GroupClauseVerifier`_.
|
||||
|
||||
|
||||
Other types of clauses
|
||||
----------------------
|
||||
|
||||
There are certain types of clauses that are specialized in particular types of contracts (like ``AbstractIssue``) or generally
|
||||
should be used as helpers in building parts of logic (the most important one is ``GroupClauseVerifier``).
|
||||
|
||||
GroupClauseVerifier
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Description**
|
||||
|
||||
Groups input and output states according to ``groupStates`` function. Runs the top-level clause verification on each
|
||||
group in turn.
|
||||
|
||||
.. image:: resources/groupClauseVerifyChart.png
|
||||
|
||||
Short description:
|
||||
|
||||
``GroupClauseVerifier`` wraps clause *Cl1*. After grouping relevant states together with ``groupStates`` into three groups
|
||||
*Gr1, Gr2, Gr3* runs *Cl1.verify(Gr1), Cl1.verify(Gr2), Cl1.verify(Gr3)*.
|
||||
|
||||
For more detailed example head to :ref:`state_ref`.
|
||||
|
||||
**Usage**
|
||||
|
||||
You need to extend ``GroupClauseVerifier`` clause and define ``groupStates`` function which takes transaction and returns
|
||||
grouped input and output states with a grouping key used for each group. Example from ``Obligation.kt`` contract:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class Group<P> : GroupClauseVerifier<State<P>, Commands, Issued<Terms<P>>>(
|
||||
AllComposition(
|
||||
NoZeroSizedOutputs<State<P>, Commands, Terms<P>>(),
|
||||
FirstComposition(
|
||||
SetLifecycle<P>(),
|
||||
AllComposition(
|
||||
VerifyLifecycle<State<P>, Commands, Issued<Terms<P>>, P>(),
|
||||
FirstComposition(
|
||||
Settle<P>(),
|
||||
Issue(),
|
||||
ConserveAmount()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<Obligation.State<P>, Issued<Terms<P>>>>
|
||||
= tx.groupStates<Obligation.State<P>, Issued<Terms<P>>> { it.amount.token }
|
||||
}
|
||||
|
||||
Usually it's convenient to use ``groupStates`` function defined on ``TransactionForContract`` class. Which given a type and a
|
||||
selector function, that returns a grouping key, associates inputs and outputs together so that they can be processed as one.
|
||||
The grouping key is any arbitrary object that can act as a map key (so must implement equals and hashCode).
|
||||
|
||||
AbstractConserveAmount
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Description**
|
||||
|
||||
Standardised clause for checking input/output balances of fungible assets. Requires that a
|
||||
Move command is provided, and errors if absent. Conserve amount clause can only be used on grouped states.
|
||||
|
||||
**Usage**
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
/**
|
||||
* Generic move/exit clause for fungible assets
|
||||
*/
|
||||
class ConserveAmount<P> : AbstractConserveAmount<State<P>, Commands, Terms<P>>()
|
||||
|
||||
See code in `GroupClauseVerifier`_.
|
||||
|
||||
AbstractIssue
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
**Description**
|
||||
|
||||
Standard issue clause for contracts that issue fungible assets.
|
||||
|
||||
**Usage**
|
||||
|
||||
Example from ``CommercialPaper.kt``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class Issue : AbstractIssue<State, Commands, Terms>(
|
||||
{ map { Amount(it.faceValue.quantity, it.token) }.sumOrThrow() },
|
||||
{ token -> map { Amount(it.faceValue.quantity, it.token) }.sumOrZero(token) }) {
|
||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Issue::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Issued<Terms>?): Set<Commands> {
|
||||
val consumedCommands = super.verify(tx, inputs, outputs, commands, groupingKey)
|
||||
...
|
||||
|
||||
First function in constructor converts a list of states into an amount of the token. Must error if there are no states in the list.
|
||||
Second function converts a list of states into an amount of the token, and returns zero if there are no states in the list.
|
||||
Takes in an instance of the token definition for constructing the zero amount if needed.
|
||||
|
||||
NoZeroSizedOutputs
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Description**
|
||||
|
||||
Clause for fungible asset contracts, which enforces that no output state should have a balance of zero.
|
||||
|
||||
**Usage**
|
||||
|
||||
See code in `GroupClauseVerifier`_.
|
||||
|
||||
FilterOn
|
||||
~~~~~~~~
|
||||
|
||||
**Description**
|
||||
|
||||
Filter the states that are passed through to the wrapped clause, to restrict them to a specific type.
|
||||
|
||||
``FilterOn`` narrows the scope of the states being verified.
|
||||
Let's take a transaction with multiple cash states of different currencies, we want to run a clause that focuses
|
||||
on only GBP cash states rather than all cash states.
|
||||
|
||||
**Usage**
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
FilterOn(clause, { states -> states.filter { it.amount.token == GBP} })
|
||||
|
||||
|
||||
Takes ``filterStates`` function that limits states passed to ``clause`` verification.
|
@ -91,6 +91,7 @@ Read on to learn:
|
||||
|
||||
contract-catalogue
|
||||
contract-irs
|
||||
clauses
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
BIN
docs/source/resources/allCompositionChart.png
Normal file
BIN
docs/source/resources/allCompositionChart.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
docs/source/resources/anyCompositionChart.png
Normal file
BIN
docs/source/resources/anyCompositionChart.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
docs/source/resources/commPaperClauses.png
Normal file
BIN
docs/source/resources/commPaperClauses.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
docs/source/resources/commPaperExecution.png
Normal file
BIN
docs/source/resources/commPaperExecution.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
docs/source/resources/firstCompositionChart.png
Normal file
BIN
docs/source/resources/firstCompositionChart.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
docs/source/resources/groupClauseVerifyChart.png
Normal file
BIN
docs/source/resources/groupClauseVerifyChart.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -9,43 +9,63 @@ Writing a contract using clauses
|
||||
|
||||
This tutorial will take you through restructuring the commercial paper contract to use clauses. You should have
|
||||
already completed ":doc:`tutorial-contract`".
|
||||
As before, the example is focused on basic implementation of commercial paper, which is essentially a simpler version of a corporate
|
||||
bond. A company issues CP with a particular face value, say $100, but sells it for less, say $90. The paper can be redeemed
|
||||
for cash at a given date in the future. Thus this example would have a 10% interest rate with a single repayment.
|
||||
Whole Kotlin code can be found in ``CommercialPaper.kt``.
|
||||
|
||||
What are clauses and why to use them?
|
||||
-------------------------------------
|
||||
|
||||
Clauses are essentially micro-contracts which contain independent verification logic, and can be logically composed
|
||||
together to form a contract. Clauses are designed to enable re-use of common logic, for example issuing state objects
|
||||
together to form a contract. Clauses are designed to enable re-use of common verification parts, for example issuing state objects
|
||||
is generally the same for all fungible contracts, so a common issuance clause can be inherited for each contract's
|
||||
issue clause. This cuts down on scope for error, and improves consistency of behaviour. By splitting verification logic
|
||||
into smaller chunks, they can also be readily tested in isolation.
|
||||
|
||||
Clauses can be composed of subclauses, for example the ``AllClause`` or ``AnyClause`` clauses take list of clauses
|
||||
that they delegate to. Clauses can also change the scope of states and commands being verified, for example grouping
|
||||
together fungible state objects and running a clause against each distinct group.
|
||||
How clauses work?
|
||||
-----------------
|
||||
|
||||
The commercial paper contract has a ``Group`` outermost clause, which contains the ``Issue``, ``Move`` and ``Redeem``
|
||||
clauses. The result is a contract that looks something like this:
|
||||
We have different types of clauses, the most basic are the ones that define verification logic for particular command set.
|
||||
We will see them later as elementary building blocks that commercial paper consist of - ``Move``, ``Issue`` and ``Redeem``.
|
||||
As a developer you need to identify reusable parts of your contract and decide how they should be combined. It is where
|
||||
composite clauses become useful. They gather many clause subcomponents and resolve how and which of them should be checked.
|
||||
|
||||
1. Group input and output states together, and then apply the following clauses on each group:
|
||||
a. If an ``Issue`` command is present, run appropriate tests and end processing this group.
|
||||
b. If a ``Move`` command is present, run appropriate tests and end processing this group.
|
||||
c. If a ``Redeem`` command is present, run appropriate tests and end processing this group.
|
||||
For example, assume that we want to verify a transaction using all constraints defined in separate clauses. We need to
|
||||
wrap classes that define them into ``AllComposition`` composite clause. It assures that all clauses from that combination
|
||||
match with commands in a transaction - only then verification logic can be executed.
|
||||
It may be a little confusing, but composite clause is also a clause and you can even wrap it in the special grouping clause.
|
||||
In ``CommercialPaper`` it looks like that:
|
||||
|
||||
.. image:: resources/commPaperClauses.png
|
||||
|
||||
The most basic types of composite clauses are ``AllComposition``, ``AnyComposition`` and ``FirstComposition``.
|
||||
In this tutorial we will use ``GroupClauseVerifier`` and ``AnyComposition``. It's important to understand how they work.
|
||||
Charts showing execution and more detailed information can be found in :doc:`clauses`.
|
||||
|
||||
.. _verify_ref:
|
||||
|
||||
Commercial paper class
|
||||
----------------------
|
||||
|
||||
To use the clause verification logic, the contract needs to call the ``verifyClause`` function, passing in the
|
||||
transaction, a clause to verify, and a collection of commands the clauses are expected to handle all of. This list of
|
||||
commands is important because ``verifyClause`` checks that none of the commands are left unprocessed at the end, and
|
||||
raises an error if they are. The top level clause would normally be a composite clause (such as ``AnyComposition``,
|
||||
``AllComposition``, etc.) which contains further clauses. The following examples are trimmed to the modified class
|
||||
definition and added elements, for brevity:
|
||||
We start from defining ``CommercialPaper`` class. As in previous tutorial we need some elementary parts: ``Commands`` interface,
|
||||
``generateMove``, ``generateIssue``, ``generateRedeem`` - so far so good that stays the same. The new part is verification and
|
||||
``Clauses`` interface (you will see them later in code). Let's start from the basic structure:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class CommercialPaper : Contract {
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper")
|
||||
class CommercialPaper : Contract {
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper")
|
||||
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, Clauses.Group(), tx.commands.select<Commands>())
|
||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, Clauses.Group(), tx.commands.select<Commands>())
|
||||
|
||||
interface Commands : CommandData {
|
||||
data class Move(override val contractHash: SecureHash? = null) : FungibleAsset.Commands.Move, Commands
|
||||
class Redeem : TypeOnlyCommandData(), Commands
|
||||
data class Issue(override val nonce: Long = random63BitValue()) : IssueCommand, Commands
|
||||
}
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
@ -60,88 +80,135 @@ definition and added elements, for brevity:
|
||||
ClauseVerifier.verifyClause(tx, new Clauses.Group(), extractCommands(tx));
|
||||
}
|
||||
|
||||
Clauses
|
||||
-------
|
||||
public interface Commands extends CommandData {
|
||||
class Move implements Commands {
|
||||
@Override
|
||||
public boolean equals(Object obj) { return obj instanceof Move; }
|
||||
}
|
||||
|
||||
We'll tackle the inner clauses that contain the bulk of the verification logic, first, and the clause which handles
|
||||
grouping of input/output states later. The clauses must extend the ``Clause`` abstract class, which defines
|
||||
the ``verify`` function, and the ``requiredCommands`` property used to determine the conditions under which a clause
|
||||
is triggered. Composite clauses should extend the ``CompositeClause`` abstract class, which extends ``Clause`` to
|
||||
add support for wrapping around multiple clauses.
|
||||
class Redeem implements Commands {
|
||||
@Override
|
||||
public boolean equals(Object obj) { return obj instanceof Redeem; }
|
||||
}
|
||||
|
||||
The ``verify`` function defined in the ``Clause`` interface is similar to the conventional ``Contract`` verification
|
||||
function, although it adds new parameters and returns the set of commands which it has processed. Normally this returned
|
||||
set is identical to the ``requiredCommands`` used to trigger the clause, however in some cases the clause may process
|
||||
further optional commands which it needs to report that it has handled.
|
||||
class Issue implements Commands {
|
||||
@Override
|
||||
public boolean equals(Object obj) { return obj instanceof Issue; }
|
||||
}
|
||||
}
|
||||
|
||||
The ``Move`` clause for the commercial paper contract is relatively simple, so we will start there:
|
||||
As you can see we used ``verifyClause`` function with ``Clauses.Group()`` in place of previous verification.
|
||||
It's an entry point to running clause logic. ``verifyClause`` takes the transaction, a clause (usually a composite one)
|
||||
to verify, and a collection of commands the clause is expected to handle all of. This list of commands is important because
|
||||
``verifyClause`` checks that none of the commands are left unprocessed at the end, and raises an error if they are.
|
||||
|
||||
Simple Clauses
|
||||
--------------
|
||||
|
||||
Let's move to constructing contract logic in terms of clauses language. Commercial paper contract has three commands and
|
||||
three corresponding behaviours: ``Issue``, ``Move`` and ``Redeem``. Each of them has a specific set of requirements that must be satisfied -
|
||||
perfect material for defining clauses. For brevity we will show only ``Move`` clause, rest is constructed in similar manner
|
||||
and included in the ``CommercialPaper.kt`` code.
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class Move: Clause<State, Commands, Issued<Terms>>() {
|
||||
override val requiredCommands: Set<Class<out CommandData>>
|
||||
get() = setOf(Commands.Move::class.java)
|
||||
interface Clauses {
|
||||
class Move: Clause<State, Commands, Issued<Terms>>() {
|
||||
override val requiredCommands: Set<Class<out CommandData>>
|
||||
get() = setOf(Commands.Move::class.java)
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
commands: List<AuthenticatedObject<Commands>>,
|
||||
groupingKey: Issued<Terms>?): Set<Commands> {
|
||||
val command = commands.requireSingleCommand<Commands.Move>()
|
||||
val input = inputs.single()
|
||||
requireThat {
|
||||
"the transaction is signed by the owner of the CP" by (input.owner in command.signers)
|
||||
"the state is propagated" by (outputs.size == 1)
|
||||
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
|
||||
// the input ignoring the owner field due to the grouping.
|
||||
val command = commands.requireSingleCommand<Commands.Move>()
|
||||
val input = inputs.single()
|
||||
requireThat {
|
||||
"the transaction is signed by the owner of the CP" by (input.owner in command.signers)
|
||||
"the state is propagated" by (outputs.size == 1)
|
||||
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
|
||||
// the input ignoring the owner field due to the grouping.
|
||||
}
|
||||
return setOf(command.value)
|
||||
}
|
||||
return setOf(command.value)
|
||||
}
|
||||
}
|
||||
...
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
class Move extends Clause<State, Commands, State> {
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<Class<? extends CommandData>> getRequiredCommands() {
|
||||
return Collections.singleton(Commands.Move.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<Commands> verify(@NotNull TransactionForContract tx,
|
||||
@NotNull List<? extends State> inputs,
|
||||
@NotNull List<? extends State> outputs,
|
||||
@NotNull List<? extends AuthenticatedObject<? extends Commands>> commands,
|
||||
@NotNull State groupingKey) {
|
||||
AuthenticatedObject<Commands.Move> cmd = requireSingleCommand(tx.getCommands(), Commands.Move.class);
|
||||
// There should be only a single input due to aggregation above
|
||||
State input = single(inputs);
|
||||
|
||||
if (!cmd.getSigners().contains(input.getOwner()))
|
||||
throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP");
|
||||
|
||||
// Check the output CP state is the same as the input state, ignoring the owner field.
|
||||
if (outputs.size() != 1) {
|
||||
throw new IllegalStateException("the state is propagated");
|
||||
public interface Clauses {
|
||||
class Move extends Clause<State, Commands, State> {
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<Class<? extends CommandData>> getRequiredCommands() {
|
||||
return Collections.singleton(Commands.Move.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<Commands> verify(@NotNull TransactionForContract tx,
|
||||
@NotNull List<? extends State> inputs,
|
||||
@NotNull List<? extends State> outputs,
|
||||
@NotNull List<? extends AuthenticatedObject<? extends Commands>> commands,
|
||||
@NotNull State groupingKey) {
|
||||
AuthenticatedObject<Commands.Move> cmd = requireSingleCommand(tx.getCommands(), Commands.Move.class);
|
||||
// There should be only a single input due to aggregation above
|
||||
State input = single(inputs);
|
||||
|
||||
if (!cmd.getSigners().contains(input.getOwner()))
|
||||
throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP");
|
||||
|
||||
// Check the output CP state is the same as the input state, ignoring the owner field.
|
||||
if (outputs.size() != 1) {
|
||||
throw new IllegalStateException("the state is propagated");
|
||||
}
|
||||
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
|
||||
// the input ignoring the owner field due to the grouping.
|
||||
return Collections.singleton(cmd.getValue());
|
||||
}
|
||||
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
|
||||
// the input ignoring the owner field due to the grouping.
|
||||
return Collections.singleton(cmd.getValue());
|
||||
}
|
||||
}
|
||||
...
|
||||
|
||||
We took part of code for ``Command.Move`` verification from previous tutorial and put it into the verify function
|
||||
of ``Move`` class. Notice that this class must extend the ``Clause`` abstract class, which defines
|
||||
the ``verify`` function, and the ``requiredCommands`` property used to determine the conditions under which a clause
|
||||
is triggered. In the above example it means that the clause will run verification when the ``Commands.Move`` is present in a transaction.
|
||||
|
||||
.. note:: Notice that commands refer to all input and output states in a transaction. For clause to be executed, transaction has
|
||||
to include all commands from ``requiredCommands`` set.
|
||||
|
||||
Few important changes:
|
||||
|
||||
- ``verify`` function returns the set of commands which it has processed. Normally this returned set is identical to the
|
||||
``requiredCommands`` used to trigger the clause, however in some cases the clause may process further optional commands
|
||||
which it needs to report that it has handled.
|
||||
|
||||
- Verification takes new parameters. Usually inputs and outputs are some subset of the original transaction entries
|
||||
passed to the clause by outer composite or grouping clause. ``groupingKey`` is a key used to group original states.
|
||||
|
||||
As a simple example imagine input states:
|
||||
|
||||
1. 1000 GBP issued by Bank of England
|
||||
2. 500 GBP issued by Bank of England
|
||||
3. 1000 GBP issued by Bank of Scotland
|
||||
|
||||
We will group states by Issuer so in the first group we have inputs 1 and 2, in second group input number 3. Grouping keys are:
|
||||
'GBP issued by Bank of England' and 'GBP issued by Bank of Scotland'.
|
||||
|
||||
How the states can be grouped and passed in that form to the ``Move`` clause? That leads us to the concept of ``GroupClauseVerifier``.
|
||||
|
||||
Group clause
|
||||
------------
|
||||
|
||||
We need to wrap the move clause (as well as the issue and redeem clauses - see the relevant contract code for their
|
||||
full specifications) in an outer clause that understands how to group contract states and objects. For this we extend
|
||||
the standard ``GroupClauseVerifier`` and specify how to group input/output states, as well as the top-level to run on
|
||||
each group. As with the top level clause on a contract, this is normally a composite clause that delegates to subclauses.
|
||||
|
||||
We may have a transaction with similar but unrelated state evolutions which need to be validated independently. It
|
||||
makes sense to check ``Move`` command on groups of related inputs and outputs (see example above). Thus, we need to collect
|
||||
relevant states together.
|
||||
For this we extend the standard ``GroupClauseVerifier`` and specify how to group input/output states, as well as the top-level
|
||||
clause to run on each group. In our example a top-level is a composite clause - ``AnyCompostion`` that delegates verification to
|
||||
it's subclasses (wrapped move, issue, redeem). Any in this case means that it will take 0 or more clauses that match transaction commands.
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -174,17 +241,20 @@ each group. As with the top level clause on a contract, this is normally a compo
|
||||
}
|
||||
}
|
||||
|
||||
For the ``CommercialPaper`` contract, this is the top level clause for the contract, and is passed directly into
|
||||
``verifyClause`` (see the example code at the top of this tutorial).
|
||||
For the ``CommercialPaper`` contract, ``Group`` is the main clause for the contract, and is passed directly into
|
||||
``verifyClause`` (see the example code at the top of this tutorial). We used ``groupStates`` function here, it's worth reminding
|
||||
how it works: :ref:`state_ref`.
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
In summary the top level contract ``CommercialPaper`` specifies a single grouping clause of type
|
||||
``CommercialPaper.Clauses.Group`` which in turn specifies ``GroupClause`` implementations for each type of command
|
||||
(``Redeem``, ``Move`` and ``Issue``). This reflects the flow of verification: In order to verify a ``CommercialPaper``
|
||||
(``Redeem``, ``Move`` and ``Issue``). This reflects the flow of verification: in order to verify a ``CommercialPaper``
|
||||
we first group states, check which commands are specified, and run command-specific verification logic accordingly.
|
||||
|
||||
.. image:: resources/commPaperExecution.png
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
|
@ -321,6 +321,8 @@ The second line does what the code suggests: it searches for a command object th
|
||||
``CommercialPaper.Commands`` supertype, and either returns it, or throws an exception if there's zero or more than one
|
||||
such command.
|
||||
|
||||
.. _state_ref:
|
||||
|
||||
Using state groups
|
||||
------------------
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user