Merged in kstreich-clause-docs (pull request #552)

docs: Add clauses documentation.
This commit is contained in:
Katarzyna Streich 2016-11-29 12:01:27 +00:00
commit 0a95928e0c
10 changed files with 431 additions and 80 deletions

278
docs/source/clauses.rst Normal file
View 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.

View File

@ -91,6 +91,7 @@ Read on to learn:
contract-catalogue
contract-irs
clauses
.. toctree::
:maxdepth: 2

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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
---------

View File

@ -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
------------------