mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
CORDA-2477 Improve Signature Constraints documentation (#5041)
The Signature Constraint documentation in `api-contract-constraints` was very limited and referred to the design doc for most information. Information was extracted from the design doc and added to the main documentation.
This commit is contained in:
parent
6662d205f8
commit
cb85dd1e92
@ -108,7 +108,7 @@ object AutomaticPlaceholderConstraint : AttachmentConstraint {
|
||||
|
||||
/**
|
||||
* An [AttachmentConstraint] that verifies that the attachment has signers that fulfil the provided [PublicKey].
|
||||
* See: [Signature Constraints](https://docs.corda.net/design/data-model-upgrades/signature-constraints.html)
|
||||
* See: [Signature Constraints](https://docs.corda.net/api-contract-constraints.html#signature-constraints)
|
||||
*
|
||||
* @property key A [PublicKey] that must be fulfilled by the owning keys of the attachment's signing parties.
|
||||
*/
|
||||
|
@ -52,7 +52,7 @@ object JarSignatureCollector {
|
||||
"""
|
||||
Mismatch between signers ${firstSignerSet.toOrderedPublicKeys()} for file $firstFile
|
||||
and signers ${otherSignerSet.toOrderedPublicKeys()} for file ${otherFile}.
|
||||
See https://docs.corda.net/design/data-model-upgrades/signature-constraints.html for details of the
|
||||
See https://docs.corda.net/api-contract-constraints.html#signature-constraints for details of the
|
||||
constraints applied to attachment signatures.
|
||||
""".trimIndent().replace('\n', ' '))
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ API: Contract Constraints
|
||||
|
||||
.. contents::
|
||||
|
||||
Reasons for Contract Constraints
|
||||
--------------------------------
|
||||
|
||||
*Contract constraints* solve two problems faced by any decentralised ledger that supports evolution of data and code:
|
||||
|
||||
1. Controlling and agreeing upon upgrades
|
||||
@ -28,51 +31,127 @@ This constraint specifies which versions of an application can be used to provid
|
||||
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. Compatibility zone whitelisted (or CZ whitelisted) 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_upgrades:
|
||||
|
||||
**Implicit vs explicit.** Constraints are not the only way to manage upgrades to transactions. There are two ways of handling
|
||||
Implicit vs Explicit Contract upgrades
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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`.
|
||||
* **Implicit**: By pre-authorising multiple implementations of the contract ahead of time, using constraints.
|
||||
* **Explicit**: By creating a special *contract upgrade transaction* and getting all participants of a state to sign it using the
|
||||
contract upgrade flows.
|
||||
|
||||
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,
|
||||
anticipate a need to do so. But it requires everyone to sign, manually authorise the upgrade,
|
||||
consumes notary and ledger resources, and is just in general more complex.
|
||||
|
||||
This article focuses on the first approach. To learn about the second please see :doc:`upgrading-cordapps`.
|
||||
|
||||
.. _implicit_constraint_types:
|
||||
|
||||
Types of Contract Constraints
|
||||
-----------------------------
|
||||
|
||||
Corda supports several types of constraints to cover a wide set of client requirements:
|
||||
|
||||
* **Hash constraint**: Exactly one version of the app can be used with this state. This prevents the app from being upgraded in the future while still
|
||||
making use of the state created with the original version.
|
||||
* **Compatibility zone whitelisted (or CZ whitelisted) constraint**: The compatibility zone operator lists the hashes of the versions that can be used with a contract class name.
|
||||
* **Signature constraint**: Any version of the app signed by the given ``CompositeKey`` can be used. This allows app issuers to express the
|
||||
complex social and business relationships that arise around code ownership. For example, a Signature Constraint allows a new version of an
|
||||
app to be produced and applied to an existing state as long as it has been signed by the same key(s) as the original version.
|
||||
* **Always accept constraint**: Any version of the app can be used. This is insecure but convenient for testing.
|
||||
|
||||
.. _signature_constraints:
|
||||
|
||||
Signature Constraints
|
||||
---------------------
|
||||
|
||||
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 express complex social and business relationships while allowing
|
||||
smooth migration of existing data to new versions of your application.
|
||||
|
||||
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. Doing so allows the
|
||||
app to be upgraded and changed while still remaining valid for use with the previously issued states.
|
||||
|
||||
More complex policies can be expressed through Signature Constraints if required. Allowing policies where only a number of the possible
|
||||
signers must sign the new version of an app that is interacting with previously issued states. Accepting different versions of apps in this
|
||||
way makes it possible for multiple versions to be valid across the network as long as the majority (or possibly a minority) agree with the
|
||||
logic provided by the apps.
|
||||
|
||||
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.
|
||||
|
||||
Further information into the design of Signature Constraints can be found in its :doc:`design document <design/data-model-upgrades/signature-constraints>`.
|
||||
|
||||
Signing CorDapps for use with Signature Constraints
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Expanding on the previous section, for an app to use Signature Constraints, it must be signed by a ``CompositeKey`` or a simpler ``PublicKey``.
|
||||
The signers of the app can consist of a single organisation or multiple organisations. Once the app has been signed, it can be distributed
|
||||
across the nodes that intend to use it.
|
||||
|
||||
Each transaction received by a node will then verify that the apps attached to it have the correct signers as specified by its
|
||||
Signature Constraints. This ensures that the version of each app is acceptable to the transaction's input states.
|
||||
|
||||
More information on how to sign an app directly from Gradle can be found in the
|
||||
:ref:`CorDapp Jar signing <cordapp_build_system_signing_cordapp_jar_ref>` section of the documentation.
|
||||
|
||||
Using Signature Constraints in transactions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If the app is signed, Signature Constraints will be used by default (in most situations) by the ``TransactionBuilder`` when adding output states.
|
||||
This is expanded upon in :ref:`contract_constraints_in_transactions`.
|
||||
|
||||
.. note:: Signature Constraints are used by default except when a new transaction contains an input state with a Hash Constraint. In this
|
||||
situation the Hash Constraint is used.
|
||||
|
||||
.. _app_versioning_with_signature_constraints:
|
||||
|
||||
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.
|
||||
|
||||
Hash Constraints
|
||||
----------------
|
||||
|
||||
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 are sent to the flow hospital.
|
||||
From there, they are 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.
|
||||
|
||||
.. _relax_hash_constraints_checking_ref:
|
||||
|
||||
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 Java system property ``-Dnet.corda.node.disableHashConstraints="true"`` may be set to relax the hash constraint checking behaviour. For
|
||||
this to work, every participant of the network must set the property to the same value. Therefore, this mode should only be used upon
|
||||
"out of band" agreement by all participants in a network.
|
||||
|
||||
.. warning:: This flag should remain enabled until every hash constrained state is exited from the ledger.
|
||||
|
||||
.. _contract_state_agreement:
|
||||
|
||||
Contract/State Agreement
|
||||
------------------------
|
||||
|
||||
@ -133,39 +212,64 @@ CorDapp is 4 or greater, then transaction verification will fail with a ``Transa
|
||||
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:
|
||||
.. _contract_constraints_in_transactions:
|
||||
|
||||
App versioning with signature constraints
|
||||
-----------------------------------------
|
||||
Using Contract Constraints in Transactions
|
||||
------------------------------------------
|
||||
|
||||
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.
|
||||
The app version used by a transaction is defined by its attachments. The JAR containing the state and contract classes, and optionally its
|
||||
dependencies, are all attached to the transaction. Nodes will download this JAR from other nodes if they haven't seen it before,
|
||||
so it can be used for verification.
|
||||
|
||||
Issues when using the HashAttachmentConstraint
|
||||
----------------------------------------------
|
||||
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.
|
||||
|
||||
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.
|
||||
By default the ``TransactionBuilder`` will use :ref:`signature_constraints` for any issuance transactions if the app attached to it is
|
||||
signed.
|
||||
|
||||
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.
|
||||
To manually define the Contract Constraint of an output state, see the example below:
|
||||
|
||||
.. _relax_hash_constraints_checking_ref:
|
||||
.. container:: codeset
|
||||
|
||||
Hash constrained states in private networks
|
||||
-------------------------------------------
|
||||
.. sourcecode:: java
|
||||
|
||||
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.
|
||||
TransactionBuilder transaction() {
|
||||
TransactionBuilder transaction = new TransactionBuilder(notary());
|
||||
// Signature Constraint used if app is signed
|
||||
transaction.addOutputState(state);
|
||||
// Explicitly using a Signature Constraint
|
||||
transaction.addOutputState(state, CONTRACT_ID, new SignatureAttachmentConstraint(getOurIdentity().getOwningKey()));
|
||||
// Explicitly using a Hash Constraint
|
||||
transaction.addOutputState(state, CONTRACT_ID, new HashAttachmentConstraint(getServiceHub().getCordappProvider().getContractAttachmentID(CONTRACT_ID)));
|
||||
// Explicitly using a Whitelisted by Zone Constraint
|
||||
transaction.addOutputState(state, CONTRACT_ID, WhitelistedByZoneAttachmentConstraint.INSTANCE);
|
||||
// Explicitly using an Always Accept Constraint
|
||||
transaction.addOutputState(state, CONTRACT_ID, AlwaysAcceptAttachmentConstraint.INSTANCE);
|
||||
|
||||
The Java system property ``-Dnet.corda.node.disableHashConstraints="true"`` may be set to relax the hash constraint checking behaviour.
|
||||
// other transaction stuff
|
||||
return transaction;
|
||||
}
|
||||
|
||||
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.
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
private fun transaction(): TransactionBuilder {
|
||||
val transaction = TransactionBuilder(notary())
|
||||
// Signature Constraint used if app is signed
|
||||
transaction.addOutputState(state)
|
||||
// Explicitly using a Signature Constraint
|
||||
transaction.addOutputState(state, constraint = SignatureAttachmentConstraint(ourIdentity.owningKey))
|
||||
// Explicitly using a Hash Constraint
|
||||
transaction.addOutputState(state, constraint = HashAttachmentConstraint(serviceHub.cordappProvider.getContractAttachmentID(CONTRACT_ID)!!))
|
||||
// Explicitly using a Whitelisted by Zone Constraint
|
||||
transaction.addOutputState(state, constraint = WhitelistedByZoneAttachmentConstraint)
|
||||
// Explicitly using an Always Accept Constraint
|
||||
transaction.addOutputState(state, constraint = AlwaysAcceptAttachmentConstraint)
|
||||
|
||||
// other transaction stuff
|
||||
return transaction
|
||||
}
|
||||
|
||||
CorDapps as attachments
|
||||
-----------------------
|
||||
@ -183,12 +287,11 @@ node or will be automatically fetched over the network when receiving a transact
|
||||
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.
|
||||
to construct output states with a constraint of their 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
|
||||
|
@ -106,7 +106,7 @@ properly for future releases.
|
||||
future not hold true. You should know the platform version of the node releases you want to target.
|
||||
|
||||
The new ``versionId`` number is a version code for **your** app, and is unrelated to Corda's own versions.
|
||||
It is used to informative purposes only. See ":ref:`contract_downgrade_rule_ref`" for more information.
|
||||
It is used to informative purposes only. See ":ref:`app_versioning_with_signature_constraints`" for more information.
|
||||
|
||||
**Split your app into contract and workflow JARs.** The duplication between ``contract`` and ``workflow`` blocks exists because you should split your app into
|
||||
two separate JARs/modules, one that contains on-ledger validation code like states and contracts, and one
|
||||
@ -418,13 +418,13 @@ to be governed by a contract that is either:
|
||||
1. The outer class of the state class, if the state is an inner class of a contract. This is a common design pattern.
|
||||
2. Annotated with ``@BelongsToContract`` which specifies the contract class explicitly.
|
||||
|
||||
Learn more by reading ":ref:`implicit_constraint_types`". If an app targets Corda 3 or lower (i.e. does not specify a target version),
|
||||
Learn more by reading :ref:`contract_state_agreement`. If an app targets Corda 3 or lower (i.e. does not specify a target version),
|
||||
states that point to contracts outside their package will trigger a log warning but validation will proceed.
|
||||
|
||||
Step 9. Learn about signature constraints and JAR signing
|
||||
---------------------------------------------------------
|
||||
|
||||
:doc:`design/data-model-upgrades/signature-constraints` are a new data model feature introduced in Corda 4. They make it much easier to
|
||||
:ref:`signature_constraints` are a new data model feature introduced in Corda 4. They make it much easier to
|
||||
deploy application upgrades smoothly and in a decentralised manner. Signature constraints are the new default mode for CorDapps, and
|
||||
the act of upgrading your app to use the version 4 Gradle plugins will result in your app being automatically signed, and new states
|
||||
automatically using new signature constraints selected automatically based on these signing keys.
|
||||
|
@ -99,6 +99,6 @@ sophisticated or proprietary business logic, machine learning models, even user
|
||||
being Corda flows or services.
|
||||
|
||||
.. important:: The ``versionId`` specified for the JAR manifest is checked by the platform and is used for informative purposes only.
|
||||
See ":ref:`contract_downgrade_rule_ref`" for more information.
|
||||
See ":ref:`app_versioning_with_signature_constraints`" for more information.
|
||||
|
||||
.. note:: You can read the original design doc here: :doc:`design/targetversion/design`.
|
@ -110,7 +110,7 @@ class JarSignatureCollectorTest {
|
||||
"""
|
||||
Mismatch between signers [O=Alice Corp, L=Madrid, C=ES, O=Bob Plc, L=Rome, C=IT] for file _signable1
|
||||
and signers [O=Bob Plc, L=Rome, C=IT] for file _signable2.
|
||||
See https://docs.corda.net/design/data-model-upgrades/signature-constraints.html for details of the
|
||||
See https://docs.corda.net/api-contract-constraints.html#signature-constraints for details of the
|
||||
constraints applied to attachment signatures.
|
||||
""".trimIndent().replace('\n', ' ')
|
||||
) { dir.getJarSigners(FILENAME) }
|
||||
|
Loading…
Reference in New Issue
Block a user