Updated documentation: API contracts
9.0 KiB
API: Contracts
Note
Before reading this page, you should be familiar with the key concepts of key-concepts-contracts
.
Contract
Contracts are classes that implement the Contract
interface. The Contract
interface is defined as follows:
../../core/src/main/kotlin/net/corda/core/contracts/Structures.kt
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:
- 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
is executed in a sandbox:
- 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
This means that verify
only has access to the properties defined on LedgerTransaction
when deciding whether a transaction is valid.
Here are the two simplest verify
functions:
- A
verify
that accepts all possible transactions:
override fun verify(tx: LedgerTransaction) {
// Always accepts!
}
@Override
public void verify(LedgerTransaction tx) {
// Always accepts!
}
- A
verify
that rejects all possible transactions:
override fun verify(tx: LedgerTransaction) {
throw IllegalArgumentException("Always rejects!")
}
@Override
public void verify(LedgerTransaction tx) {
throw new IllegalArgumentException("Always rejects!");
}
LedgerTransaction
The LedgerTransaction
object passed into verify
has the following properties:
../../core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt
Where:
inputs
are the transaction's inputs asList<StateAndRef<ContractState>>
outputs
are the transaction's outputs asList<TransactionState<ContractState>>
commands
are the transaction's commands and associated signers, asList<CommandWithParties<CommandData>>
attachments
are the transaction's attachments asList<Attachment>
notary
is the transaction's notary. This must match the notary of all the inputstimeWindow
defines the window during which the transaction can be notarised
LedgerTransaction
exposes a large number of utility methods to access the transaction's contents:
inputStates
extracts the inputContractState
objects from the list ofStateAndRef
getInput
/getOutput
/getCommand
/getAttachment
extracts a component by indexgetAttachment
extracts an attachment by IDinputsOfType
/inRefsOfType
/outputsOfType
/outRefsOfType
/commandsOfType
extracts components based on their generic typefilterInputs
/filterInRefs
/filterOutputs
/filterOutRefs
/filterCommands
extracts components based on a predicatefindInput
/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:
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.")
}
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:
requireThat {"No inputs should be consumed when issuing an X." using (tx.inputs.isEmpty())
"Only one output state should be created." using (tx.outputs.size == 1)
val out = tx.outputs.single() as XState
"The sender and the recipient cannot be the same entity." using (out.sender != out.recipient)
"All of the participants must be signers." using (command.signers.containsAll(out.participants))
"The X's value must be non-negative." using (out.x.value > 0)
}
requireThat(require -> {
using("No inputs should be consumed when issuing an X.", tx.getInputs().isEmpty());
require.using("Only one output state should be created.", tx.getOutputs().size() == 1);
require.final XState out = (XState) tx.getOutputs().get(0);
using("The sender and the recipient cannot be the same entity.", out.getSender() != out.getRecipient());
require.using("All of the participants must be signers.", command.getSigners().containsAll(out.getParticipants()));
require.using("The X's value must be non-negative.", out.getX().getValue() > 0);
require.return null;
});
For each <String
, Boolean
> pair within requireThat
, if the boolean condition is false, an IllegalArgumentException
is thrown with the corresponding string as the exception message. In turn, this exception will cause the transaction to be rejected.
Commands
LedgerTransaction
contains the commands as a list of CommandWithParties
instances. CommandWithParties
pairs a CommandData
with a list of required signers for the transaction:
../../core/src/main/kotlin/net/corda/core/contracts/Structures.kt
Where:
signers
is the list of each signer'sPublicKey
signingParties
is the list of the signer's identities, if knownvalue
is the object being signed (a command, in this case)
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.
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:
class XContract : Contract {
interface Commands : CommandData {
class Issue : TypeOnlyCommandData(), Commands
class Transfer : TypeOnlyCommandData(), Commands
}
override fun verify(tx: LedgerTransaction) {
val command = tx.findCommand<Commands> { true }
when (command) {
is Commands.Issue -> {
// Issuance verification logic.
}is Commands.Transfer -> {
// Transfer verification logic.
}
}
} }
public class XContract implements Contract {
public interface Commands extends CommandData {
class Issue extends TypeOnlyCommandData implements Commands {}
class Transfer extends TypeOnlyCommandData implements Commands {}
}
@Override
public void verify(LedgerTransaction tx) {
final Command<Commands> command = tx.findCommand(Commands.class, cmd -> true);
if (command instanceof Commands.Issue) {
// Issuance verification logic.
else if (command instanceof Commands.Transfer) {
} // Transfer verification logic.
}
} }