mirror of
https://github.com/corda/corda.git
synced 2024-12-23 23:02:29 +00:00
245 lines
15 KiB
ReStructuredText
245 lines
15 KiB
ReStructuredText
API: Contract Constraints
|
|
=========================
|
|
|
|
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-contracts`.
|
|
|
|
.. contents::
|
|
|
|
Contract constraints
|
|
--------------------
|
|
|
|
Corda separates verification of states from their definition. Whilst you might have expected the ``ContractState``
|
|
interface to define a verify method, or perhaps to do verification logic in the constructor, instead it is primarily
|
|
done by a method on a ``Contract`` class. This is because what we're actually checking is the
|
|
validity of a *transaction*, which is more than just whether the individual states are internally consistent.
|
|
The transition between two valid states may be invalid, if the rules of the application are not being respected.
|
|
For instance, two cash states of $100 and $200 may both be internally valid, but replacing the first with the second
|
|
isn't allowed unless you're a cash issuer - otherwise you could print money for free.
|
|
|
|
For a transaction to be valid, the ``verify`` function associated with each state must run successfully. However,
|
|
for this to be secure, it is not sufficient to specify the ``verify`` function by name as there may exist multiple
|
|
different implementations with the same method signature and enclosing class. This normally will happen as applications
|
|
evolve, but could also happen maliciously.
|
|
|
|
Contract constraints solve this problem by allowing a contract developer to constrain which ``verify`` functions out of
|
|
the universe of implementations can be used (i.e. the universe is everything that matches the signature and contract
|
|
constraints restrict this universe to a subset). Constraints are satisfied by attachments (JARs). You are not allowed to
|
|
attach two JARs that both define the same application due to the *no overlap rule*. This rule specifies that two
|
|
attachment JARs may not provide the same file path. If they do, the transaction is considered invalid. Because each
|
|
state specifies both a constraint over attachments *and* a Contract class name to use, the specified class must appear
|
|
in only one attachment.
|
|
|
|
So who picks the attachment to use? It is chosen by the creator of the transaction that has to satisfy input constraints.
|
|
The transaction creator also gets to pick the constraints used by any output states, but the contract logic itself may
|
|
have opinions about what those constraints are - a typical contract would require that the constraints are propagated,
|
|
that is, the contract will not just enforce the validity of the next transaction that uses a state, but *all successive
|
|
transactions as well*. The constraints mechanism creates a balance of power between the creator of data on
|
|
the ledger and the user who is trying to edit it, which can be very useful when managing upgrades to Corda applications.
|
|
|
|
There are two ways of handling upgrades to a smart contract in Corda:
|
|
|
|
1. *Implicit:* By allowing multiple implementations of the contract ahead of time, using constraints.
|
|
2. *Explicit:* By creating a special *contract upgrade transaction* and getting all participants of a state to sign it using the
|
|
contract upgrade flows.
|
|
|
|
This article focuses on the first approach. To learn about the second please see :doc:`upgrading-cordapps`.
|
|
|
|
The advantage of pre-authorising upgrades using constraints is that you don't need the heavyweight process of creating
|
|
upgrade transactions for every state on the ledger. The disadvantage is that you place more faith in third parties,
|
|
who could potentially change the app in ways you did not expect or agree with. The advantage of using the explicit
|
|
upgrade approach is that you can upgrade states regardless of their constraint, including in cases where you didn't
|
|
anticipate a need to do so. But it requires everyone to sign, requires everyone to manually authorise the upgrade,
|
|
consumes notary and ledger resources, and is just in general more complex.
|
|
|
|
How constraints work
|
|
--------------------
|
|
|
|
Starting from Corda 3 there are two types of constraint that can be used: hash and zone whitelist. In future
|
|
releases a third type will be added, the signature constraint.
|
|
|
|
**Hash constraints.** The behaviour provided by public blockchain systems like Bitcoin and Ethereum is that once data is placed on the ledger,
|
|
the program that controls it is fixed and cannot be changed. There is no support for upgrades at all. This implements a
|
|
form of "code is law", assuming you trust the community of that blockchain to not release a new version of the platform
|
|
that invalidates or changes the meaning of your program.
|
|
|
|
This is supported by Corda using a hash constraint. This specifies exactly one hash of a CorDapp JAR that contains the
|
|
contract and states any consuming transaction is allowed to use. Once such a state is created, other nodes will only
|
|
accept a transaction if it uses that exact JAR file as an attachment. By implication, any bugs in the contract code
|
|
or state definitions cannot be fixed except by using an explicit upgrade process via ``ContractUpgradeFlow``.
|
|
|
|
.. note:: Corda does not support any way to create states that can never be upgraded at all, but the same effect can be
|
|
obtained by using a hash constraint and then simply refusing to agree to any explicit upgrades. Hash
|
|
constraints put you in control by requiring an explicit agreement to any upgrade.
|
|
|
|
**Zone constraints.** Often a hash constraint will be too restrictive. You do want the ability to upgrade an app,
|
|
and you don't mind the upgrade taking effect "just in time" when a transaction happens to be required for other business
|
|
reasons. In this case you can use a zone constraint. This specifies that the network parameters of a compatibility zone
|
|
(see :doc:`network-map`) is expected to contain a map of class name to hashes of JARs that are allowed to provide that
|
|
class. The process for upgrading an app then involves asking the zone operator to add the hash of your new JAR to the
|
|
parameters file, and trigger the network parameters upgrade process. This involves each node operator running a shell
|
|
command to accept the new parameters file and then restarting the node. Node owners who do not restart their node in
|
|
time effectively stop being a part of the network.
|
|
|
|
**Signature constraints.** These are not yet supported, but once implemented they will allow a state to require a JAR
|
|
signed by a specified identity, via the regular Java ``jarsigner`` tool. This will be the most flexible type
|
|
and the smoothest to deploy: no restarts or contract upgrade transactions are needed.
|
|
|
|
**Defaults.** The default constraint type is either a zone constraint, if the network parameters in effect when the
|
|
transaction is built contain an entry for that contract class, or a hash constraint if not.
|
|
|
|
A ``TransactionState`` has a ``constraint`` field that represents that state's attachment constraint. When a party
|
|
constructs a ``TransactionState``, or adds a state using ``TransactionBuilder.addOutput(ContractState)`` without
|
|
specifying the constraint parameter, a default value (``AutomaticHashConstraint``) is used. This default will be
|
|
automatically resolved to a specific ``HashAttachmentConstraint`` or a ``WhitelistedByZoneAttachmentConstraint``.
|
|
This automatic resolution occurs when a ``TransactionBuilder`` is converted to a ``WireTransaction``. This reduces
|
|
the boilerplate that would otherwise be involved.
|
|
|
|
Finally, an ``AlwaysAcceptAttachmentConstraint`` can be used which accepts anything, though this is intended for
|
|
testing only.
|
|
|
|
Please note that the ``AttachmentConstraint`` interface is marked as ``@DoNotImplement``. You are not allowed to write
|
|
new constraint types. Only the platform may implement this interface. If you tried, other nodes would not understand
|
|
your constraint type and your transaction would not verify.
|
|
|
|
.. warning:: An AlwaysAccept constraint is effectively the same as disabling security for those states entirely.
|
|
Nothing stops you using this constraint in production, but that degrades Corda to being effectively a form
|
|
of distributed messaging with optional contract logic being useful only to catch mistakes, rather than potentially
|
|
malicious action. If you are deploying an app for which malicious actors aren't in your threat model, using an
|
|
AlwaysAccept constraint might simplify things operationally.
|
|
|
|
An example below shows how to construct a ``TransactionState`` with an explicitly specified hash constraint from within
|
|
a flow:
|
|
|
|
.. sourcecode:: java
|
|
|
|
// Constructing a transaction with a custom hash constraint on a state
|
|
TransactionBuilder tx = new TransactionBuilder();
|
|
|
|
Party notaryParty = ... // a notary party
|
|
DummyState contractState = new DummyState();
|
|
|
|
SecureHash myAttachmentHash = SecureHash.parse("2b4042aed7e0e39d312c4c477dca1d96ec5a878ddcfd5583251a8367edbd4a5f");
|
|
TransactionState transactionState = new TransactionState(contractState, DummyContract.Companion.getPROGRAMID(), notaryParty, new AttachmentHashConstraint(myAttachmentHash));
|
|
|
|
tx.addOutputState(transactionState);
|
|
WireTransaction wtx = tx.toWireTransaction(serviceHub); // This is where an automatic constraint would be resolved.
|
|
LedgerTransaction ltx = wtx.toLedgerTransaction(serviceHub);
|
|
ltx.verify(); // Verifies both the attachment constraints and contracts
|
|
|
|
Hard-coding the hash of your app in the code itself can be pretty awkward, so the API also offers the ``AutomaticHashConstraint``.
|
|
This isn't a real constraint that will appear in a transaction: it acts as a marker to the ``TransactionBuilder`` that
|
|
you require the hash of the node's installed app which supplies the specified contract to be used. In practice, when using
|
|
hash constraints, you almost always want "whatever the current code is" and not a hard-coded hash. So this automatic
|
|
constraint placeholder is useful.
|
|
|
|
FinalityFlow
|
|
------------
|
|
|
|
It's possible to encounter contract constraint issues when notarising transactions with the ``FinalityFlow`` on a network
|
|
containing multiple versions of the same CorDapp. This will happen when using hash constraints or with zone constraints
|
|
if the zone whitelist has missing CorDapp versions. If a participating party fails to validate the **notarised** transaction
|
|
then we have a scenario where the members of the network do not have a consistent view of the ledger.
|
|
|
|
Therefore, if the finality handler flow (which is run on the counter-party) errors for any reason it will always be sent to
|
|
the flow hospital. From there it's suspended waiting to be retried on node restart. This gives the node operator the opportunity
|
|
to recover from those errors, which in the case of contract constraint violations means either updating the CorDapp or
|
|
adding its hash to the zone whitelist.
|
|
|
|
.. note:: This is a temporary issue in the current version of Corda, until we implement some missing features which will
|
|
enable a seamless handling of differences in CorDapp versions.
|
|
|
|
CorDapps as attachments
|
|
-----------------------
|
|
|
|
CorDapp JARs (see :doc:`cordapp-overview`) that are installed to the node and contain classes implementing the ``Contract``
|
|
interface are automatically loaded into the ``AttachmentStorage`` of a node at startup.
|
|
|
|
After CorDapps are loaded into the attachment store the node creates a link between contract classes and the attachment
|
|
that they were loaded from. This makes it possible to find the attachment for any given contract. This is how the
|
|
automatic resolution of attachments is done by the ``TransactionBuilder`` and how, when verifying the constraints and
|
|
contracts, attachments are associated with their respective contracts.
|
|
|
|
.. note:: The obvious way to write a CorDapp is to put all you states, contracts, flows and support code into a single
|
|
Java module. This will work but it will effectively publish your entire app onto the ledger. That has two problems:
|
|
(1) it is inefficient, and (2) it means changes to your flows or other parts of the app will be seen by the ledger
|
|
as a "new app", which may end up requiring essentially unnecessary upgrade procedures. It's better to split your
|
|
app into multiple modules: one which contains just states, contracts and core data types. And another which contains
|
|
the rest of the app. See :ref:`cordapp-structure`.
|
|
|
|
Testing
|
|
-------
|
|
|
|
Since all tests involving transactions now require attachments it is also required to load the correct attachments
|
|
for tests. Unit test environments in JVM ecosystems tend to use class directories rather than JARs, and so CorDapp JARs
|
|
typically aren't built for testing. Requiring this would add significant complexity to the build systems of Corda
|
|
and CorDapps, so the test suite has a set of convenient functions to generate CorDapps from package names or
|
|
to specify JAR URLs in the case that the CorDapp(s) involved in testing already exist. You can also just use
|
|
``AlwaysAcceptAttachmentConstraint`` in your tests to disable the constraints mechanism.
|
|
|
|
MockNetwork/MockNode
|
|
********************
|
|
|
|
The simplest way to ensure that a vanilla instance of a MockNode generates the correct CorDapps is to use the
|
|
``cordappPackages`` constructor parameter (Kotlin) or the ``setCordappPackages`` method on ``MockNetworkParameters`` (Java)
|
|
when creating the MockNetwork. This will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files
|
|
within those packages will be zipped into a JAR and added to the attachment store and loaded as CorDapps by the
|
|
``CordappLoader``.
|
|
|
|
An example of this usage would be:
|
|
|
|
.. sourcecode:: java
|
|
|
|
class SomeTestClass {
|
|
MockNetwork network = null;
|
|
|
|
@Before
|
|
void setup() {
|
|
network = new MockNetwork(new MockNetworkParameters().setCordappPackages(Arrays.asList("com.domain.cordapp")))
|
|
}
|
|
|
|
... // Your tests go here
|
|
}
|
|
|
|
|
|
MockServices
|
|
************
|
|
|
|
If your test uses a ``MockServices`` directly you can instantiate it using a constructor that takes a list of packages
|
|
to use as CorDapps using the ``cordappPackages`` parameter.
|
|
|
|
.. sourcecode:: java
|
|
|
|
MockServices mockServices = new MockServices(Arrays.asList("com.domain.cordapp"))
|
|
|
|
However - there is an easier way! If your unit tests are in the same package as the contract code itself, then you
|
|
can use the no-args constructor of ``MockServices``. The package to be scanned for CorDapps will be the same as the
|
|
the package of the class that constructed the object. This is a convenient default.
|
|
|
|
Driver
|
|
******
|
|
|
|
The driver takes a parameter called ``extraCordappPackagesToScan`` which is a list of packages to use as CorDapps.
|
|
|
|
.. sourcecode:: java
|
|
|
|
driver(new DriverParameters().setExtraCordappPackagesToScan(Arrays.asList("com.domain.cordapp"))) ...
|
|
|
|
Full Nodes
|
|
**********
|
|
|
|
When testing against full nodes simply place your CorDapp into the cordapps directory of the node.
|
|
|
|
Debugging
|
|
---------
|
|
If an attachment constraint cannot be resolved, a ``MissingContractAttachments`` exception is thrown. There are two
|
|
common sources of ``MissingContractAttachments`` exceptions:
|
|
|
|
Not setting CorDapp packages in tests
|
|
*************************************
|
|
You are running a test and have not specified the CorDapp packages to scan. See the instructions above.
|
|
|
|
Wrong fully-qualified contract name
|
|
***********************************
|
|
You are specifying the fully-qualified name of the contract incorrectly. For example, you've defined ``MyContract`` in
|
|
the package ``com.mycompany.myapp.contracts``, but the fully-qualified contract name you pass to the
|
|
``TransactionBuilder`` is ``com.mycompany.myapp.MyContract`` (instead of ``com.mycompany.myapp.contracts.MyContract``). |