diff --git a/docs/source/clauses.rst b/docs/source/clauses.rst deleted file mode 100644 index 93b6b097ad..0000000000 --- a/docs/source/clauses.rst +++ /dev/null @@ -1,278 +0,0 @@ -Clauses -======= - -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 { - - /** Determine whether this clause runs or not */ - open val requiredCommands: Set> = emptySet() - - @Throws(IllegalStateException::class) - abstract fun verify(tx: LedgerTransaction, - inputs: List, - outputs: List, - commands: List>, - groupingKey: K?): Set - ... - } - -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 ``FirstOf`` 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: LedgerTransaction) = verifyClause(tx, FirstOf( - Clauses.Net(), - Clauses.Group

() - ), tx.commands.select()) - -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 ``FirstOf`` 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 `FirstOf`_). -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. - -AllOf -~~~~~ - -**Description** - -Composes a number of clauses, such that all of the clauses must run for verification to pass. - -.. image:: resources/allOfChart.png - -Short description: - -- ``AllOf`` holds clauses *Cl1,..,Cl5*. -- Check if all clauses that compose ``AllOf`` 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`_. - -AnyOf -~~~~~ - -**Description** - -Composes a number of clauses, such that 1 or more of the clauses can be run. - -.. image:: resources/anyOfChart.png - -Short description: - -- Checks if one or more clauses that compose AnyOf 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>( - AnyOf( - Redeem(), - Move(), - Issue())) { - override fun groupStates(tx: LedgerTransaction): List>> - = tx.groupStates> { it.token } - } - -FirstOf -~~~~~~~ - -**Description** - -Composes a number of clauses, such that the first match is run, and it errors if none is run. - -.. image:: resources/firstOfChart.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

: GroupClauseVerifier, Commands, Issued>>( - AllOf( - NoZeroSizedOutputs, Commands, Terms

>(), - FirstOf( - SetLifecycle

(), - AllOf( - VerifyLifecycle, Commands, Issued>, P>(), - FirstOf( - Settle

(), - Issue(), - ConserveAmount() - ) - ) - ) - ) - ) { - override fun groupStates(tx: LedgerTransaction): List, Issued>>> - = tx.groupStates, Issued>> { it.amount.token } - } - -Usually it's convenient to use ``groupStates`` function defined on ``LedgerTransaction`` 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

: AbstractConserveAmount, Commands, Terms

>() - -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( - { map { Amount(it.faceValue.quantity, it.token) }.sumOrThrow() }, - { token -> map { Amount(it.faceValue.quantity, it.token) }.sumOrZero(token) }) { - override val requiredCommands: Set> = setOf(Commands.Issue::class.java) - - override fun verify(tx: LedgerTransaction, - inputs: List, - outputs: List, - commands: List>, - groupingKey: Issued?): Set { - 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. diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index 7e9763506e..05d5211fa6 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -5,8 +5,6 @@ Artemis The message queuing middleware used within Corda Attachment An attachment is a piece of data that can be referred to within a transaction but is never marked as used, i.e. can be referred to multiple times. -Clause - A clause is a reusable piece of code that performs transaction verification Command Used for directing a transaction, sometimes containing command data. For example, a Cash contract may include an Issue command, which signals that one of the purposes of the transaction is to issue cash on to the ledger (i.e. by creating one or more Cash outputs, without any corresponding inputs.) Composite Key diff --git a/docs/source/other-index.rst b/docs/source/other-index.rst index 5fd4d78554..d3971e9e8b 100644 --- a/docs/source/other-index.rst +++ b/docs/source/other-index.rst @@ -4,8 +4,6 @@ Other .. toctree:: :maxdepth: 1 - clauses - merkle-trees json secure-coding-guidelines corda-repo-layout diff --git a/docs/source/tutorial-contract-clauses.rst b/docs/source/tutorial-contract-clauses.rst deleted file mode 100644 index 6ddb5035af..0000000000 --- a/docs/source/tutorial-contract-clauses.rst +++ /dev/null @@ -1,267 +0,0 @@ -.. highlight:: kotlin -.. raw:: html - - - - -Writing a contract using clauses -================================ - -In this tutorial, we will restructure the commercial paper contract to use clauses. You should have -already completed ":doc:`tutorial-contract`". - -As before, this example is focused on a basic implementation of commercial paper (CP), which is essentially a simpler version of a corporate -bond. A company issues commercial paper with a particular face value, say $100, but sells it for less, say $90. The paper can be redeemed -for cash at a given future date. In our example, the commercial paper has a 10% interest rate, with a single repayment. -The full Kotlin code can be found in ``CommercialPaper.kt``. - -What are clauses and why use them? ----------------------------------- - -Clauses are essentially micro-contracts which contain independent verification logic, and can be logically composed -to form a complete 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 used 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, these can also be readily tested in isolation. - -How do clauses work? --------------------- - -There are different types of clauses. The most basic are those that define the verification logic for a single command -(e.g. ``Move``, ``Issue`` and ``Redeem``, in the case of commercial paper), or even run without any commands at all (e.g. ``Timestamp``). - -These basic clauses can then be combined using a ``CompositeClause``. The goal of composite clauses is to determine -which individual clauses need to be matched and verified for a given transaction -to be considered valid. We refer to a clause as being "matched" when the transaction has the required commands present for the clause -in question to trigger. Meanwhile, we talk about a clause "verifying" when its ``verify()`` function returns ``True``. - -As an example, let's say we want a transaction to be valid only when every single one of its clauses matches and verifies. We implement this -by wrapping the individual clauses into an ``AllOf`` composite clause, which ensures that a transaction is -only considered valid if all of its clauses are both matched and verify. - -There are two other basic composite clauses that you should be aware of: - - * ``AnyOf``, whereby 1 or more clauses may match, and every matched clause must verify - * ``FirstOf``, whereby at least one clause must match, and the first such clause must verify - -In turn, composite clauses are themselves ``Clause`` s, and can, for example, be wrapped in the special ``GroupClauseVerifier`` grouping clause. -For ``CommercialPaper``, this would look as follows: - -.. image:: resources/commPaperClauses.png - -For this tutorial, we will be using ``GroupClauseVerifier`` and ``AnyOf``. Since it's important to understand how these work, -charts showing their execution and other details can be found in :doc:`clauses`. - -.. _verify_ref: - -Commercial paper class ----------------------- - -We start by defining the ``CommercialPaper`` class. As in the previous tutorial, we need some elementary parts: a ``Commands`` interface, -``generateMove``, ``generateIssue``, ``generateRedeem``. So far, so good - these stay the same. The new part is verification and the -``Clauses`` interface (which we will see later in code). Let's start from the basic structure: - -.. container:: codeset - - .. sourcecode:: kotlin - - class CommercialPaper : Contract { - override fun verify(tx: LedgerTransaction) = verifyClause(tx, Clauses.Group(), tx.commands.select()) - - 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 - - public class CommercialPaper implements Contract { - @Override - public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException { - ClauseVerifier.verifyClause(tx, new Clauses.Group(), extractCommands(tx)); - } - - public interface Commands extends CommandData { - class Move implements Commands { - @Override - public boolean equals(Object obj) { return obj instanceof Move; } - } - - class Redeem implements Commands { - @Override - public boolean equals(Object obj) { return obj instanceof Redeem; } - } - - class Issue implements Commands { - @Override - public boolean equals(Object obj) { return obj instanceof Issue; } - } - } - -As you can see, we used ``verifyClause`` function with ``Clauses.Group()`` in place of our previous verification logic. -It's an entry point to running clause logic. ``verifyClause`` takes the transaction, a clause (usually a composite one) -to verify, and all of the commands the clause is expected to handle. This list of commands is important because -``verifyClause`` checks that none of the commands are left unprocessed at the end, raising an error if they are. - -Simple Clauses --------------- - -Let's move to constructing contract logic in terms of clauses. The 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 only show the ``Move`` clause. The rest is constructed in similar manner, -and is included in the ``CommercialPaper.kt`` code. - -.. container:: codeset - - .. sourcecode:: kotlin - - interface Clauses { - class Move: Clause>() { - override val requiredCommands: Set> - get() = setOf(Commands.Move::class.java) - - override fun verify(tx: LedgerTransaction, - inputs: List, - outputs: List, - commands: List>, - groupingKey: Issued?): Set { - val command = commands.requireSingleCommand() - val input = inputs.single() - requireThat { - "the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers) - "the state is propagated" using (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) - } - } - ... - - .. sourcecode:: java - - public interface Clauses { - class Move extends Clause { - @NotNull - @Override - public Set> getRequiredCommands() { - return Collections.singleton(Commands.Move.class); - } - - @NotNull - @Override - public Set verify(@NotNull LedgerTransaction tx, - @NotNull List inputs, - @NotNull List outputs, - @NotNull List> commands, - @NotNull State groupingKey) { - CommandWithParties 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().getOwningKey())) - 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()); - } - } - ... - -We took part of the code for ``Command.Move`` verification from the 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, this means that the clause will run its verification logic when ``Commands.Move`` is present in a transaction. - -.. note:: Notice that commands refer to all input and output states in a transaction. For a clause to be executed, the transaction has - to include all commands from the ``requiredCommands`` set. - -A few important changes: - -- The ``verify`` function returns the set of commands which it has processed. Normally this 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 the following 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, meaning that we have inputs 1 and 2 in one group, and input 3 in another group. The grouping keys are -'GBP issued by Bank of England' and 'GBP issued by Bank of Scotland'. - -How are the states grouped and passed in this form to the ``Move`` clause? Answering that question leads us to the concept of -``GroupClauseVerifier``. - -Group clause ------------- - -We may have a transaction with similar but unrelated state evolutions which need to be validated independently. It -makes sense to check the ``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, the top level is a composite clause - ``AnyCompostion`` - that delegates verification to -its subclauses (wrapped move, issue, redeem). "Any" in this case means that it will take 0 or more clauses that match the transaction commands. - -.. container:: codeset - - .. sourcecode:: kotlin - - class Group : GroupClauseVerifier>( - AnyOf( - Redeem(), - Move(), - Issue())) { - override fun groupStates(tx: LedgerTransaction): List>> - = tx.groupStates> { it.token } - } - - .. sourcecode:: java - - class Group extends GroupClauseVerifier { - public Group() { - super(new AnyOf<>( - new Clauses.Redeem(), - new Clauses.Move(), - new Clauses.Issue() - )); - } - - @NotNull - @Override - public List> groupStates(@NotNull LedgerTransaction tx) { - return tx.groupStates(State.class, State::withoutOwner); - } - } - -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 also used ``groupStates`` function here - it -may be worth reminding yourself how it works here: :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 verification flow: in order to verify ``CommercialPaper``, -we first group states, then we check which commands are specified, and finally we run command-specific verification logic accordingly. - -.. image:: resources/commPaperExecution.png - -Debugging ---------- - -Debugging clauses which have been composed together can be complicated due to the difficulty in knowing which clauses -have been matched, whether specific clauses failed to match or passed verification, etc. There is "trace" level -logging code in the clause verifier which evaluates which clauses will be matched and logs them, before actually -performing the validation. To enable this, ensure trace level logging is enabled on the ``Clause`` interface. diff --git a/docs/source/tutorial-contract.rst b/docs/source/tutorial-contract.rst index ffefcfb2fa..2aca2177f7 100644 --- a/docs/source/tutorial-contract.rst +++ b/docs/source/tutorial-contract.rst @@ -47,8 +47,7 @@ Starting the commercial paper class A smart contract is a class that implements the ``Contract`` interface. This can be either implemented directly, as done here, or by subclassing an abstract contract such as ``OnLedgerAsset``. The heart of any contract in Corda is the ``verify()`` function, which determined whether any given transaction is valid. This example shows how to write a -``verify()`` function from scratch. A later tutorial will introduce "clauses", which are reusable chunks of verification -logic, but first it's worth understanding how a contract is built without them. +``verify()`` function from scratch. You can see the full Kotlin version of this contract in the code as ``CommercialPaperLegacy``. The code in this tutorial is available in both Kotlin and Java. You can quickly switch between them to get a feeling for how @@ -816,12 +815,3 @@ the all future cash states stemming from this one. We will also consider marking states that are capable of being encumbrances as such. This will prevent states being used as encumbrances inadvertently. For example, the time-lock above would be usable as an encumbrance, but it makes no sense to be able to encumber a cash state with another one. - -Clauses -------- - -It is typical for slightly different contracts to have lots of common logic that can be shared. For example, the -concept of being issued, being exited and being upgraded are all usually required in any contract. Corda calls these -frequently needed chunks of logic "clauses", and they can simplify development considerably. - -Clauses and how to use them are addressed in the next tutorial, ":doc:`tutorial-contract-clauses`". diff --git a/docs/source/tutorials-index.rst b/docs/source/tutorials-index.rst index faaa498335..f9ffad2dd3 100644 --- a/docs/source/tutorials-index.rst +++ b/docs/source/tutorials-index.rst @@ -7,7 +7,6 @@ Tutorials hello-world-index tut-two-party-index tutorial-contract - tutorial-contract-clauses tutorial-test-dsl contract-upgrade tutorial-integration-testing