API: Contracts

Note

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

All Corda contracts are JVM classes that implement net.corda.core.contracts.Contract.

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)

    /**
     * Unparsed reference to the natural language contract that this code is supposed to express (usually a hash of
     * the contract's contents).
     */
    val legalContractReference: SecureHash
}

Where:

  • verify(tx: LedgerTransaction) determines whether transactions involving states which reference this

contract type are valid

  • legalContractReference is the hash of the legal prose contract that verify seeks to express in code

verify()

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.

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.

The two simplest verify functions are the one that accepts all transactions, and the one that rejects all transactions.

Here is the verify that accepts all transactions:

override fun verify(tx: LedgerTransaction) {
    // Always accepts!
}
@Override
public void verify(LedgerTransaction tx) {
    // Always accepts!
}

And here is the verify that rejects all 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() represents the full set of information available to verify() when deciding whether to accept or reject the transaction. It has the following properties:

@CordaSerializable
class LedgerTransaction(
        /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
        override val inputs: List<StateAndRef<*>>,
        outputs: List<TransactionState<ContractState>>,
        /** Arbitrary data passed to the program of each input state. */
        val commands: List<AuthenticatedObject<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,
        notary: Party?,
        signers: List<PublicKey>,
        timeWindow: TimeWindow?,
        type: TransactionType
) : BaseTransaction(inputs, outputs, notary, signers, type, timeWindow) {

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.
  • type is the class of Transaction. Normal ledger data transactions are TransactionType.General, but migration of states to a new notary uses TransactionType.NotaryChange.

requireThat()

Instead of throwing exceptions manually to reject a transaction, we can use the requireThat DSL:

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 AuthenticatedObject instances. AuthenticatedObject pairs an object with a list of signers. In this case, AuthenticatedObject pairs a command with a list of the entities that are required to sign a transaction where this command is present:

/** Wraps an object that was signed by a public key, which may be a well known/recognised institutional key. */
@CordaSerializable
data class AuthenticatedObject<out T : Any>(
        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)

Extracting commands

You can use the requireSingleCommand() helper method to extract commands.

<C : CommandData> Collection<AuthenticatedObject<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():

class XContract : Contract {
    interface Commands : CommandData {
        class Issue : TypeOnlyCommandData(), Commands
        class Transfer : TypeOnlyCommandData(), Commands
    }

    override fun verify(tx: LedgerTransaction) {
        val command = tx.commands.requireSingleCommand<Commands>()

        when (command.value) {
            is Commands.Issue -> {
                // Issuance verification logic.
            }
            is Commands.Transfer -> {
                // Transfer verification logic.
            }
        }
    }

    override val legalContractReference: SecureHash = SecureHash.sha256("X contract hash")
}
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 AuthenticatedObject<Commands> command = requireSingleCommand(tx.getCommands(), Commands.class);

        if (command.getValue() instanceof Commands.Issue) {
            // Issuance verification logic.
        } else if (command.getValue() instanceof Commands.Transfer) {
            // Transfer verification logic.
        }
    }

    private final SecureHash legalContractReference = SecureHash.sha256("X contract hash");
    @Override public final SecureHash getLegalContractReference() { return legalContractReference; }
}

Grouping states

Suppose we have the following transaction, where 15 USD is being exchanged for 10 GBP:

_images/ungrouped-tx.png

We can imagine that we would like to verify the USD states and the GBP states separately:

_images/grouped-tx.png

LedgerTransaction provides a groupStates method to allow you to group states in this way:

Where InOutGroup is defined as:

For example, we could group the states in the transaction above by currency (i.e. by amount.token):

val groups: List<InOutGroup<Cash.State, Issued<Currency>>> = tx.groupStates(Cash.State::class.java) {
        it -> it.amount.token
    }
final List<InOutGroup<Cash.State, Issued<Currency>>> groups = tx.groupStates(
    Cash.State.class,
    it -> it.getAmount().getToken()
);

This would produce the following InOutGroups:

_images/in-out-groups.png

We can now verify these groups individually:

for ((in_, out, key) in groups) {
    when (key) {
        is GBP -> {
            // GBP verification logic.
        }
        is USD -> {
            // USD verification logic.
        }
    }
}
for (InOutGroup group : groups) {
    if (group.getGroupingKey() == USD) {
        // USD verification logic.
    } else if (group.getGroupingKey() == GBP) {
        // GBP verification logic.
    }
}