mirror of
https://github.com/corda/corda.git
synced 2024-12-25 15:41:10 +00:00
parent
addd3b34c8
commit
ce8ea5cb50
@ -9,9 +9,11 @@ API: Contracts
|
||||
|
||||
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-contracts`.
|
||||
|
||||
All Corda contracts are JVM classes that implement ``net.corda.core.contracts.Contract``.
|
||||
.. contents::
|
||||
|
||||
The ``Contract`` interface is defined as follows:
|
||||
Contract
|
||||
--------
|
||||
Contracts are classes that implement the ``Contract`` interface. The ``Contract`` interface is defined as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -20,25 +22,27 @@ The ``Contract`` interface is defined as follows:
|
||||
:start-after: DOCSTART 5
|
||||
:end-before: DOCEND 5
|
||||
|
||||
Where:
|
||||
``Contract`` has a single method, ``verify``, which takes a ``LedgerTransaction`` as input and returns
|
||||
nothing. This function is used to check whether a transaction proposal is valid, as follows:
|
||||
|
||||
* ``verify(tx: LedgerTransaction)`` determines whether transactions involving states which reference this contract type are valid
|
||||
* We gather together the contracts of each of the transaction's input and output states
|
||||
* We call each contract's ``verify`` function, passing in the transaction as an input
|
||||
* The proposal is only valid if none of the ``verify`` calls throw an exception
|
||||
|
||||
verify()
|
||||
--------
|
||||
``verify`` is executed in a sandbox:
|
||||
|
||||
``verify()`` is a method that doesn't return anything and takes a ``LedgerTransaction`` as a parameter. It
|
||||
either throws an exception if the transaction is considered invalid, or returns normally if the transaction is
|
||||
considered valid.
|
||||
* It does not have access to the enclosing scope
|
||||
* The libraries available to it are whitelisted to disallow:
|
||||
* Network access
|
||||
* I/O such as disk or database access
|
||||
* Sources of randomness such as the current time or random number generators
|
||||
|
||||
``verify()`` is executed in a sandbox. It does not have access to the enclosing scope, and is not able to access
|
||||
the network or perform any other I/O. It only has access to the properties defined on ``LedgerTransaction`` when
|
||||
establishing whether a transaction is valid.
|
||||
This means that ``verify`` only has access to the properties defined on ``LedgerTransaction`` when deciding whether a
|
||||
transaction is valid.
|
||||
|
||||
The two simplest ``verify`` functions are the one that accepts all transactions, and the one that rejects all
|
||||
transactions.
|
||||
Here are the two simplest ``verify`` functions:
|
||||
|
||||
Here is the ``verify`` that accepts all transactions:
|
||||
* A ``verify`` that **accepts** all possible transactions:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -55,7 +59,7 @@ Here is the ``verify`` that accepts all transactions:
|
||||
// Always accepts!
|
||||
}
|
||||
|
||||
And here is the ``verify`` that rejects all transactions:
|
||||
* A ``verify`` that **rejects** all possible transactions:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -73,10 +77,8 @@ And here is the ``verify`` that rejects all transactions:
|
||||
}
|
||||
|
||||
LedgerTransaction
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``LedgerTransaction`` object passed into ``verify()`` represents the full set of information available to
|
||||
``verify()`` when deciding whether to accept or reject the transaction. It has the following properties:
|
||||
-----------------
|
||||
The ``LedgerTransaction`` object passed into ``verify`` has the following properties:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -87,18 +89,52 @@ The ``LedgerTransaction`` object passed into ``verify()`` represents the full se
|
||||
|
||||
Where:
|
||||
|
||||
* ``inputs`` is a list of the transaction's inputs'
|
||||
* ``outputs`` is a list of the transaction's outputs'
|
||||
* ``attachments`` is a list of the transaction's attachments'
|
||||
* ``commands`` is a list of the transaction's commands, and their associated signatures'
|
||||
* ``id`` is the transaction's Merkle root hash'
|
||||
* ``notary`` is the transaction's notary. If there are inputs these must have the same notary on their source transactions.
|
||||
* ``timeWindow`` is the transaction's timestamp and defines the acceptable delay for notarisation.
|
||||
* ``inputs`` are the transaction's inputs as ``List<StateAndRef<ContractState>>``
|
||||
* ``outputs`` are the transaction's outputs as ``List<TransactionState<ContractState>>``
|
||||
* ``commands`` are the transaction's commands and associated signers, as ``List<CommandWithParties<CommandData>>``
|
||||
* ``attachments`` are the transaction's attachments as ``List<Attachment>``
|
||||
* ``notary`` is the transaction's notary. This must match the notary of all the inputs
|
||||
* ``timeWindow`` defines the window during which the transaction can be notarised
|
||||
|
||||
requireThat()
|
||||
^^^^^^^^^^^^^
|
||||
``LedgerTransaction`` exposes a large number of utility methods to access the transaction's contents:
|
||||
|
||||
Instead of throwing exceptions manually to reject a transaction, we can use the ``requireThat`` DSL:
|
||||
* ``inputStates`` extracts the input ``ContractState`` objects from the list of ``StateAndRef``
|
||||
* ``getInput``/``getOutput``/``getCommand``/``getAttachment`` extracts a component by index
|
||||
* ``getAttachment`` extracts an attachment by ID
|
||||
* ``inputsOfType``/``inRefsOfType``/``outputsOfType``/``outRefsOfType``/``commandsOfType`` extracts components based on
|
||||
their generic type
|
||||
* ``filterInputs``/``filterInRefs``/``filterOutputs``/``filterOutRefs``/``filterCommands`` extracts components based on
|
||||
a predicate
|
||||
* ``findInput``/``findInRef``/``findOutput``/``findOutRef``/``findCommand`` extracts the single component that matches
|
||||
a predicate, or throws an exception if there are multiple matches
|
||||
|
||||
requireThat
|
||||
-----------
|
||||
``verify`` can be written to manually throw an exception for each constraint:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
if (tx.inputs.size > 0)
|
||||
throw IllegalArgumentException("No inputs should be consumed when issuing an X.")
|
||||
|
||||
if (tx.outputs.size != 1)
|
||||
throw IllegalArgumentException("Only one output state should be created.")
|
||||
}
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
public void verify(LedgerTransaction tx) {
|
||||
if (tx.getInputs().size() > 0)
|
||||
throw new IllegalArgumentException("No inputs should be consumed when issuing an X.");
|
||||
|
||||
if (tx.getOutputs().size() != 1)
|
||||
throw new IllegalArgumentException("Only one output state should be created.");
|
||||
}
|
||||
|
||||
However, this is verbose. To impose a series of constraints, we can use ``requireThat`` instead:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -130,11 +166,9 @@ For each <``String``, ``Boolean``> pair within ``requireThat``, if the boolean c
|
||||
exception will cause the transaction to be rejected.
|
||||
|
||||
Commands
|
||||
^^^^^^^^
|
||||
|
||||
``LedgerTransaction`` contains the commands as a list of ``CommandWithParties`` instances.
|
||||
``CommandWithParties`` pairs a command with a list of the entities that are required to sign a transaction
|
||||
where this command is present:
|
||||
--------
|
||||
``LedgerTransaction`` contains the commands as a list of ``CommandWithParties`` instances. ``CommandWithParties`` pairs
|
||||
a ``CommandData`` with a list of required signers for the transaction:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -149,19 +183,13 @@ Where:
|
||||
* ``signingParties`` is the list of the signer's identities, if known
|
||||
* ``value`` is the object being signed (a command, in this case)
|
||||
|
||||
Extracting commands
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
You can use the ``requireSingleCommand()`` helper method to extract commands.
|
||||
Branching verify with commands
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Generally, we will want to impose different constraints on a transaction based on its commands. For example, we will
|
||||
want to impose different constraints on a cash issuance transaction to on a cash transfer transaction.
|
||||
|
||||
``<C : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand(klass: Class<C>)`` asserts that
|
||||
the transaction contains exactly one command of type ``T``, and returns it. If there is not exactly one command of this
|
||||
type in the transaction, an exception is thrown, rejecting the transaction.
|
||||
|
||||
For ``requireSingleCommand`` to work, all the commands that we wish to match against must be grouped using the same
|
||||
marker interface.
|
||||
|
||||
Here is an example of using ``requireSingleCommand`` to extract a transaction's command and using it to fork the
|
||||
execution of ``verify()``:
|
||||
We can achieve this by extracting the command and using standard branching logic within ``verify``. Here, we extract
|
||||
the single command of type ``XContract.Commands`` from the transaction, and branch ``verify`` accordingly:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -174,9 +202,9 @@ execution of ``verify()``:
|
||||
}
|
||||
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
val command = tx.commands.requireSingleCommand<Commands>()
|
||||
val command = tx.findCommand<Commands> { true }
|
||||
|
||||
when (command.value) {
|
||||
when (command) {
|
||||
is Commands.Issue -> {
|
||||
// Issuance verification logic.
|
||||
}
|
||||
@ -197,98 +225,12 @@ execution of ``verify()``:
|
||||
|
||||
@Override
|
||||
public void verify(LedgerTransaction tx) {
|
||||
final CommandWithParties<Commands> command = requireSingleCommand(tx.getCommands(), Commands.class);
|
||||
final Command<Commands> command = tx.findCommand(Commands.class, cmd -> true);
|
||||
|
||||
if (command.getValue() instanceof Commands.Issue) {
|
||||
if (command instanceof Commands.Issue) {
|
||||
// Issuance verification logic.
|
||||
} else if (command.getValue() instanceof Commands.Transfer) {
|
||||
} else if (command instanceof Commands.Transfer) {
|
||||
// Transfer verification logic.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grouping states
|
||||
---------------
|
||||
Suppose we have the following transaction, where 15 USD is being exchanged for 10 GBP:
|
||||
|
||||
.. image:: resources/ungrouped-tx.png
|
||||
:scale: 20
|
||||
:align: center
|
||||
|
||||
We can imagine that we would like to verify the USD states and the GBP states separately:
|
||||
|
||||
.. image:: resources/grouped-tx.png
|
||||
:scale: 20
|
||||
:align: center
|
||||
|
||||
``LedgerTransaction`` provides a ``groupStates`` method to allow you to group states in this way:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 2
|
||||
:end-before: DOCEND 2
|
||||
|
||||
Where ``InOutGroup`` is defined as:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/contracts/TransactionVerification.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART 3
|
||||
:end-before: DOCEND 3
|
||||
|
||||
For example, we could group the states in the transaction above by currency (i.e. by ``amount.token``):
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
val groups: List<InOutGroup<Cash.State, Issued<Currency>>> = tx.groupStates(Cash.State::class.java) {
|
||||
it -> it.amount.token
|
||||
}
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
final List<InOutGroup<Cash.State, Issued<Currency>>> groups = tx.groupStates(
|
||||
Cash.State.class,
|
||||
it -> it.getAmount().getToken()
|
||||
);
|
||||
|
||||
This would produce the following InOutGroups:
|
||||
|
||||
.. image:: resources/in-out-groups.png
|
||||
|
||||
We can now verify these groups individually:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
for ((in_, out, key) in groups) {
|
||||
when (key) {
|
||||
is GBP -> {
|
||||
// GBP verification logic.
|
||||
}
|
||||
is USD -> {
|
||||
// USD verification logic.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
for (InOutGroup group : groups) {
|
||||
if (group.getGroupingKey() == USD) {
|
||||
// USD verification logic.
|
||||
} else if (group.getGroupingKey() == GBP) {
|
||||
// GBP verification logic.
|
||||
}
|
||||
}
|
||||
|
||||
Legal prose
|
||||
-----------
|
||||
|
||||
Currently, a ``Contract`` subtype may refer to the legal prose it implements via a ``LegalProseReference`` annotation.
|
||||
In the future, a contract's legal prose will be included as an attachment.
|
||||
}
|
Loading…
Reference in New Issue
Block a user