API: Contracts

Note

Before reading this page, you should be familiar with the key concepts of Contracts.

Contract

Contracts are classes that implement the Contract interface. The Contract interface is defined as follows:

/**
 * Implemented by a program that implements business logic on the shared ledger. All participants run this code for
 * every [net.corda.core.transactions.LedgerTransaction] they see on the network, for every input and output state. All
 * contracts must accept the transaction for it to be accepted: failure of any aborts the entire thing. The time is taken
 * from a trusted time-window attached to the transaction itself i.e. it is NOT necessarily the current time.
 *
 * TODO: Contract serialization is likely to change, so the annotation is likely temporary.
 */
@CordaSerializable
interface Contract {
    /**
     * Takes an object that represents a state transition, and ensures the inputs/outputs/commands make sense.
     * Must throw an exception if there's a problem that should prevent state transition. Takes a single object
     * rather than an argument so that additional data can be added without breaking binary compatibility with
     * existing contract code.
     */
    @Throws(IllegalArgumentException::class)
    fun verify(tx: LedgerTransaction)
}

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:

@CordaSerializable
data class LedgerTransaction(
        /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
        override val inputs: List<StateAndRef<ContractState>>,
        override val outputs: List<TransactionState<ContractState>>,
        /** Arbitrary data passed to the program of each input state. */
        val commands: List<CommandWithParties<CommandData>>,
        /** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
        val attachments: List<Attachment>,
        /** The hash of the original serialised WireTransaction. */
        override val id: SecureHash,
        override val notary: Party?,
        val timeWindow: TimeWindow?,
        val privacySalt: PrivacySalt
) : FullTransaction() {

Where:

  • 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

LedgerTransaction exposes a large number of utility methods to access the transaction’s contents:

  • 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:

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 -> {
    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);
    final XState out = (XState) tx.getOutputs().get(0);
    require.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);
    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:

/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
@CordaSerializable
data class CommandWithParties<out T : CommandData>(
        val signers: List<PublicKey>,
        /** If any public keys were recognised, the looked up institutions are available here */
        val signingParties: List<Party>,
        val value: T
)

Where:

  • signers is the list of each signer’s PublicKey
  • signingParties is the list of the signer’s identities, if known
  • value 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.
        }
    }
}