mirror of
https://github.com/corda/corda.git
synced 2024-12-22 14:22:28 +00:00
c11f42fc05
* Document CorDapp upgradeability guarantees. * Incorporating feedback from RGB. * Incorporating feedback from MH. * Minor updates following re-review by RGB * Updates following review by MH. * Include new document in index. * Incorporating review feedback from MH.
293 lines
16 KiB
ReStructuredText
293 lines
16 KiB
ReStructuredText
.. highlight:: kotlin
|
|
|
|
API: Contract Constraints
|
|
=========================
|
|
|
|
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-contracts`.
|
|
|
|
.. contents::
|
|
|
|
*Contract constraints* solve two problems faced by any decentralised ledger that supports evolution of data and code:
|
|
|
|
1. Controlling and agreeing upon upgrades
|
|
2. Preventing attacks
|
|
|
|
Upgrades and security are intimately related because if an attacker can "upgrade" your data to a version of an app that gives them
|
|
a back door, they would be able to do things like print money or edit states in any way they want. That's why it's important for
|
|
participants of a state to agree on what kind of upgrades will be allowed.
|
|
|
|
Every state on the ledger contains the fully qualified class name of a ``Contract`` implementation, and also a *constraint*.
|
|
This constraint specifies which versions of an application can be used to provide the named class, when the transaction is built.
|
|
New versions released after a transaction is signed and finalised won't affect prior transactions because the old code is attached
|
|
to it.
|
|
|
|
There are several types of constraint:
|
|
|
|
1. Hash constraint: exactly one version of the app can be used with this state.
|
|
2. Zone whitelist constraint: the compatibility zone operator lists the hashes of the versions that can be used with this contract class name.
|
|
3. Signature constraint: any version of the app signed by the given ``CompositeKey`` can be used.
|
|
4. Always accept constraint: any app can be used at all. This is insecure but convenient for testing.
|
|
|
|
The actual app version used is defined by the attachments on a transaction that consumes a state: the JAR containing the state and contract classes, and optionally
|
|
its dependencies, are all attached to the transaction. Other nodes will download these JARs from a node if they haven't seen them before,
|
|
so they can be used for verification. The ``TransactionBuilder`` will manage the details of constraints for you, by selecting both constraints
|
|
and attachments to ensure they line up correctly. Therefore you only need to have a basic understanding of this topic unless you are
|
|
doing something sophisticated.
|
|
|
|
The best kind of constraint to use is the **signature constraint**. If you sign your application it will be used automatically.
|
|
We recommend signature constraints because they let you smoothly migrate existing data to new versions of your application.
|
|
Hash and zone whitelist constraints are left over from earlier Corda versions before signature constraints were
|
|
implemented. They make it harder to upgrade applications than when using signature constraints, so they're best avoided.
|
|
Signature constraints can specify flexible threshold policies, but if you use the automatic support then a state will
|
|
require the attached app to be signed by every key that the first attachment was signed by. Thus if the app that was used
|
|
to issue the states was signed by Alice and Bob, every transaction must use an attachment signed by Alice and Bob.
|
|
|
|
**Constraint propagation.** Constraints are picked when a state is created for the first time in an issuance transaction. Once created,
|
|
the constraint used by equivalent output states (i.e. output states that use the same contract class name) must match the
|
|
input state, so it can't be changed and you can't combine states with incompatible constraints together in the same transaction.
|
|
|
|
**Implicit vs explicit.** Constraints are not the only way to manage upgrades to transactions. There are two ways of handling
|
|
upgrades to a smart contract in Corda:
|
|
|
|
1. *Implicit:* By pre-authorising 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.
|
|
|
|
.. _implicit_constraint_types:
|
|
|
|
Contract/State Agreement
|
|
------------------------
|
|
|
|
Starting with Corda 4, a ``ContractState`` must explicitly indicate which ``Contract`` it belongs to. When a transaction is
|
|
verified, the contract bundled with each state in the transaction must be its "owning" contract, otherwise we cannot guarantee that
|
|
the transition of the ``ContractState`` will be verified against the business rules that should apply to it.
|
|
|
|
There are two mechanisms for indicating ownership. One is to annotate the ``ContractState`` with the ``BelongsToContract`` annotation,
|
|
indicating the ``Contract`` class to which it is tied:
|
|
|
|
.. sourcecode:: java
|
|
|
|
@BelongToContract(MyContract.class)
|
|
public class MyState implements ContractState {
|
|
// implementation goes here
|
|
}
|
|
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
@BelongsToContract(MyContract::class)
|
|
data class MyState(val value: Int) : ContractState {
|
|
// implementation goes here
|
|
}
|
|
|
|
|
|
The other is to define the ``ContractState`` class as an inner class of the ``Contract`` class
|
|
|
|
.. sourcecode:: java
|
|
|
|
public class MyContract implements Contract {
|
|
|
|
public static class MyState implements ContractState {
|
|
// state implementation goes here
|
|
}
|
|
|
|
// contract implementation goes here
|
|
}
|
|
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
class MyContract : Contract {
|
|
data class MyState(val value: Int) : ContractState
|
|
}
|
|
|
|
|
|
If a ``ContractState``'s owning ``Contract`` cannot be identified by either of these mechanisms, and the ``targetVersion`` of the
|
|
CorDapp is 4 or greater, then transaction verification will fail with a ``TransactionRequiredContractUnspecifiedException``. If
|
|
the owning ``Contract`` *can* be identified, but the ``ContractState`` has been bundled with a different contract, then
|
|
transaction verification will fail with a ``TransactionContractConflictException``.
|
|
|
|
.. _contract_downgrade_rule_ref:
|
|
|
|
App versioning with signature constraints
|
|
-----------------------------------------
|
|
|
|
Signed apps require a version number to be provided, see :doc:`versioning`. You can't import two different
|
|
JARs that claim to be the same version, provide the same contract classes and which are both signed. At runtime
|
|
the node will throw a ``DuplicateContractClassException`` exception if this condition is violated.
|
|
|
|
Issues when using the HashAttachmentConstraint
|
|
----------------------------------------------
|
|
|
|
When setting up a new network, it is possible to encounter errors when states are issued with the ``HashAttachmentConstraint``,
|
|
but not all nodes have that same version of the CorDapp installed locally.
|
|
|
|
In this case, flows will fail with a ``ContractConstraintRejection``, and the failed flow will 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 constraint violations means
|
|
adding the right cordapp jar to the ``cordapps`` folder.
|
|
|
|
Hash constrained states in private networks
|
|
-------------------------------------------
|
|
|
|
Where private networks started life using CorDapps with hash constrained states, we have introduced a mechanism to relax the checking of
|
|
these hash constrained states when upgrading to signed CorDapps using signature constraints.
|
|
|
|
The following java system property may be set to relax the hash constraint checking behaviour:
|
|
|
|
-Dnet.corda.node.disableHashConstraints="true"
|
|
|
|
This mode should only be used upon "out of band" agreement by all participants in a network.
|
|
|
|
Please also beware that this flag should remain enabled until every hash constrained state is exited from the ledger.
|
|
|
|
CorDapps as attachments
|
|
-----------------------
|
|
|
|
CorDapp JARs (see :doc:`cordapp-overview`) that contain classes implementing the ``Contract`` interface are automatically
|
|
loaded into the ``AttachmentStorage`` of a node, and made available as ``ContractAttachments``.
|
|
|
|
They are retrievable by hash using ``AttachmentStorage.openAttachment``. These JARs can either be installed on the
|
|
node or will be automatically fetched over the network when receiving a transaction.
|
|
|
|
.. warning:: 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`.
|
|
|
|
|
|
Constraints propagation
|
|
-----------------------
|
|
|
|
As was mentioned above, the ``TransactionBuilder`` API gives the CorDapp developer or even malicious node owner the possibility
|
|
to construct output states with a constraint of his choosing.
|
|
|
|
For the ledger to remain in a consistent state, the expected behavior is for output state to inherit the constraints of input states.
|
|
This guarantees that for example, a transaction can't output a state with the ``AlwaysAcceptAttachmentConstraint`` when the
|
|
corresponding input state was the ``SignatureAttachmentConstraint``. Translated, this means that if this rule is enforced, it ensures
|
|
that the output state will be spent under similar conditions as it was created.
|
|
|
|
Before version 4, the constraint propagation logic was expected to be enforced in the contract verify code, as it has access to the entire Transaction.
|
|
|
|
Starting with version 4 of Corda the constraint propagation logic has been implemented and enforced directly by the platform,
|
|
unless disabled by putting ``@NoConstraintPropagation`` on the ``Contract`` class which reverts to the previous behavior of expecting
|
|
apps to do this.
|
|
|
|
For contracts that are not annotated with ``@NoConstraintPropagation``, the platform implements a fairly simple constraint transition policy
|
|
to ensure security and also allow the possibility to transition to the new ``SignatureAttachmentConstraint``.
|
|
|
|
During transaction building the ``AutomaticPlaceholderConstraint`` for output states will be resolved and the best contract attachment versions
|
|
will be selected based on a variety of factors so that the above holds true. If it can't find attachments in storage or there are no
|
|
possible constraints, the ``TransactionBuilder`` will throw an exception.
|
|
|
|
|
|
.. _constraints_whitelist_to_signature_ref:
|
|
|
|
How to use the ``SignatureAttachmentConstraint`` if states were already created on the network with the ``WhitelistedByZoneAttachmentConstraint``
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
1. As the original developer of the corDapp, the first step is to sign the latest version of the JAR that was released (see :doc:`cordapp-build-systems`).
|
|
The key used for signing will be used to sign all subsequent releases, so it should be stored appropriately. The JAR can be signed by multiple keys owned
|
|
by different parties and it will be expressed as a ``CompositeKey`` in the ``SignatureAttachmentConstraint`` (See :doc:`api-core-types`).
|
|
Use `JAR signing and verification tool <https://docs.oracle.com/javase/tutorial/deployment/jar/verify.html>`_ to sign the existing JAR.
|
|
The signing capability of :ref:`corda-gradle-plugins <cordapp_build_system_signing_cordapp_jar_ref>` cannot be used in this context as it signs the JAR while building it from source.
|
|
|
|
2. Whitelist this newly signed JAR with the Zone operator. The Zone operator should check that the JAR is signed and not allow any
|
|
more versions of it to be whitelisted in the future. From now on the developer(s) who signed the JAR are responsible for new versions.
|
|
|
|
3. Any flows that build transactions using this Cordapp will have the responsibility of transitioning states to the ``SignatureAttachmentConstraint``.
|
|
This is done explicitly in the code by setting the constraint of the output states to signers of the latest version of the whitelisted jar.
|
|
In the near future we will make this transition automatic if we detect that the previous 2 steps were executed.
|
|
|
|
4. As a node operator you need to add the new signed version of the contracts cordapp to the "cordapps" folder together with the latest version of the flows jar
|
|
that will contain code like:
|
|
|
|
.. container:: codeset
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
// This will read the signers for the deployed cordapp.
|
|
val attachment = this.serviceHub.cordappProvider.getContractAttachmentID(contractClass)
|
|
val signers = this.serviceHub.attachments.openAttachment(attachment!!)!!.signerKeys
|
|
|
|
// Create the key that will have to pass for all future versions.
|
|
val ownersKey = signers.first()
|
|
|
|
val txBuilder = TransactionBuilder(notary)
|
|
// Set the Signature constraint on the new state to migrate away from the WhitelistConstraint.
|
|
.addOutputState(outputState, constraint = SignatureAttachmentConstraint(ownersKey))
|
|
...
|
|
|
|
.. sourcecode:: java
|
|
|
|
// This will read the signers for the deployed cordapp.
|
|
SecureHash attachment = this.getServiceHub().getCordappProvider().getContractAttachmentID(contractClass);
|
|
List<PublicKey> signers = this.getServiceHub().getAttachments().openAttachment(attachment).getSignerKeys();
|
|
|
|
// Create the key that will have to pass for all future versions.
|
|
PublicKey ownersKey = signers.get(0);
|
|
|
|
TransactionBuilder txBuilder = new TransactionBuilder(notary)
|
|
// Set the Signature constraint on the new state to migrate away from the WhitelistConstraint.
|
|
.addOutputState(outputState, myContract, new SignatureAttachmentConstraint(ownersKey))
|
|
...
|
|
|
|
|
|
Debugging
|
|
---------
|
|
If an attachment constraint cannot be resolved, a ``MissingContractAttachments`` exception is thrown. There are three 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.
|
|
When using ``MockNetwork`` ensure you have provided a package containing the contract class in ``MockNetworkParameters``. See :doc:`api-testing`.
|
|
|
|
Similarly package names need to be provided when testing using ``DriverDSl``. ``DriverParameters`` has a property ``cordappsForAllNodes`` (Kotlin)
|
|
or method ``withCordappsForAllNodes`` in Java. Pass the collection of ``TestCordapp`` created by utility method ``TestCordapp.findCordapp(String)``.
|
|
|
|
Example of creation of two Cordapps with Finance App Flows and Finance App Contracts in Kotlin:
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
Driver.driver(DriverParameters(cordappsForAllNodes = listOf(TestCordapp.findCordapp("net.corda.finance.schemas"),
|
|
TestCordapp.findCordapp("net.corda.finance.flows"))) {
|
|
// Your test code goes here
|
|
})
|
|
|
|
The same example in Java:
|
|
|
|
.. sourcecode:: java
|
|
|
|
Driver.driver(new DriverParameters()
|
|
.withCordappsForAllNodes(Arrays.asList(TestCordapp.findCordapp("net.corda.finance.schemas"),
|
|
TestCordapp.findCordapp("net.corda.finance.flows"))), dsl -> {
|
|
// Your test code goes here
|
|
});
|
|
|
|
|
|
Staring a node missing CorDapp(s)
|
|
*********************************
|
|
|
|
When running the Corda node ensure all CordDapp JARs are placed in ``cordapps`` directory of each node.
|
|
By default Gradle Cordform task ``deployNodes`` copies all JARs if CorDapps to deploy are specified.
|
|
See :doc:`generating-a-node` for detailed instructions.
|
|
|
|
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``). |