mirror of
https://github.com/corda/corda.git
synced 2024-12-25 23:51: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`.
|
.. 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
|
.. container:: codeset
|
||||||
|
|
||||||
@ -20,25 +22,27 @@ The ``Contract`` interface is defined as follows:
|
|||||||
:start-after: DOCSTART 5
|
:start-after: DOCSTART 5
|
||||||
:end-before: DOCEND 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
|
* It does not have access to the enclosing scope
|
||||||
either throws an exception if the transaction is considered invalid, or returns normally if the transaction is
|
* The libraries available to it are whitelisted to disallow:
|
||||||
considered valid.
|
* 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
|
This means that ``verify`` only has access to the properties defined on ``LedgerTransaction`` when deciding whether a
|
||||||
the network or perform any other I/O. It only has access to the properties defined on ``LedgerTransaction`` when
|
transaction is valid.
|
||||||
establishing whether a transaction is valid.
|
|
||||||
|
|
||||||
The two simplest ``verify`` functions are the one that accepts all transactions, and the one that rejects all
|
Here are the two simplest ``verify`` functions:
|
||||||
transactions.
|
|
||||||
|
|
||||||
Here is the ``verify`` that accepts all transactions:
|
* A ``verify`` that **accepts** all possible transactions:
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
@ -55,7 +59,7 @@ Here is the ``verify`` that accepts all transactions:
|
|||||||
// Always accepts!
|
// Always accepts!
|
||||||
}
|
}
|
||||||
|
|
||||||
And here is the ``verify`` that rejects all transactions:
|
* A ``verify`` that **rejects** all possible transactions:
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
@ -73,10 +77,8 @@ And here is the ``verify`` that rejects all transactions:
|
|||||||
}
|
}
|
||||||
|
|
||||||
LedgerTransaction
|
LedgerTransaction
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
-----------------
|
||||||
|
The ``LedgerTransaction`` object passed into ``verify`` has the following properties:
|
||||||
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:
|
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
@ -87,18 +89,52 @@ The ``LedgerTransaction`` object passed into ``verify()`` represents the full se
|
|||||||
|
|
||||||
Where:
|
Where:
|
||||||
|
|
||||||
* ``inputs`` is a list of the transaction's inputs'
|
* ``inputs`` are the transaction's inputs as ``List<StateAndRef<ContractState>>``
|
||||||
* ``outputs`` is a list of the transaction's outputs'
|
* ``outputs`` are the transaction's outputs as ``List<TransactionState<ContractState>>``
|
||||||
* ``attachments`` is a list of the transaction's attachments'
|
* ``commands`` are the transaction's commands and associated signers, as ``List<CommandWithParties<CommandData>>``
|
||||||
* ``commands`` is a list of the transaction's commands, and their associated signatures'
|
* ``attachments`` are the transaction's attachments as ``List<Attachment>``
|
||||||
* ``id`` is the transaction's Merkle root hash'
|
* ``notary`` is the transaction's notary. This must match the notary of all the inputs
|
||||||
* ``notary`` is the transaction's notary. If there are inputs these must have the same notary on their source transactions.
|
* ``timeWindow`` defines the window during which the transaction can be notarised
|
||||||
* ``timeWindow`` is the transaction's timestamp and defines the acceptable delay for notarisation.
|
|
||||||
|
|
||||||
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
|
.. 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.
|
exception will cause the transaction to be rejected.
|
||||||
|
|
||||||
Commands
|
Commands
|
||||||
^^^^^^^^
|
--------
|
||||||
|
``LedgerTransaction`` contains the commands as a list of ``CommandWithParties`` instances. ``CommandWithParties`` pairs
|
||||||
``LedgerTransaction`` contains the commands as a list of ``CommandWithParties`` instances.
|
a ``CommandData`` with a list of required signers for the transaction:
|
||||||
``CommandWithParties`` pairs a command with a list of the entities that are required to sign a transaction
|
|
||||||
where this command is present:
|
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
@ -149,19 +183,13 @@ Where:
|
|||||||
* ``signingParties`` is the list of the signer's identities, if known
|
* ``signingParties`` is the list of the signer's identities, if known
|
||||||
* ``value`` is the object being signed (a command, in this case)
|
* ``value`` is the object being signed (a command, in this case)
|
||||||
|
|
||||||
Extracting commands
|
Branching verify with commands
|
||||||
~~~~~~~~~~~~~~~~~~~
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
You can use the ``requireSingleCommand()`` helper method to extract 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
|
We can achieve this by extracting the command and using standard branching logic within ``verify``. Here, we extract
|
||||||
the transaction contains exactly one command of type ``T``, and returns it. If there is not exactly one command of this
|
the single command of type ``XContract.Commands`` from the transaction, and branch ``verify`` accordingly:
|
||||||
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()``:
|
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
@ -174,9 +202,9 @@ execution of ``verify()``:
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun verify(tx: LedgerTransaction) {
|
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 -> {
|
is Commands.Issue -> {
|
||||||
// Issuance verification logic.
|
// Issuance verification logic.
|
||||||
}
|
}
|
||||||
@ -197,98 +225,12 @@ execution of ``verify()``:
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verify(LedgerTransaction tx) {
|
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.
|
// Issuance verification logic.
|
||||||
} else if (command.getValue() instanceof Commands.Transfer) {
|
} else if (command instanceof Commands.Transfer) {
|
||||||
// Transfer verification logic.
|
// 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