Contract Constraints
Contract constraints define the versions of a CorDapp can be used to provide the contract to a transaction. The constraints property is stored in each state.
Contract constraints also solve upgrade-related security problems. If an attacker were to force an upgrade to the CorDapp, they could add security vulnerabilities and create or edit states as they choose. By defining a contract constraint, states can only be affected by contracts in known versions of the CorDapp.
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:
- 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, 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 Release new CorDapp versions.
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
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.
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.
CompositeKey
s with up to 20 keys maximum.
This maximum limit is assuming keys that are either 2048-bit RSA
keys or 256-bit elliptic curve (EC
) keys.If a node receives a transaction that uses an attachment that it doesn’t trust, but there is another attachment present on the node with at least one common signature, then the node will trust the received attachment. This means that nodes are no longer required to have every version of a CorDapp uploaded to them in order to verify transactions running older versions of a CorDapp. Instead, it is sufficient to have any version of the CorDapp contract installed.
Below are two examples of possible scenarios around blacklisting signing keys. These statements are true for both:
Alice
hasContracts CorDapp
installed.Bob
has an upgraded version ofContracts CorDapp
(known asContracts CorDapp V2
) installed.- Both
Alice
andBob
have theWorkflows CorDapp
allowing them to transact with each other. Contracts CorDapp
is signed by bothAlice
andBob
.Contracts CorDapp V2
is signed by bothAlice
andBob
.
Example 1:
Alice
has not blacklisted any attachment signing keys.Bob
transacts withAlice
.Alice
receivesContracts CorDapp V2
and stores it.- When verifying the attachments loaded into the contract verification code,
Contracts CorDapp V2
is accepted and used. - The contract verification code in
Contracts CorDapp V2
is run.
Example 2:
Alice
blacklistsBob
’s attachment signing key.Bob
transacts withAlice
.Alice
receivesContracts CorDapp V2
and stores it.- When verifying the attachments loaded in the contract verification code,
Contracts CorDapp V2
is declined because it is signed byBob
’s blacklisted key. - The contract verification code in
Contracts CorDapp V2
is not run and the transaction fails.
Information on blacklisting attachment signing keys can be found in the node configuration documentation.
More information on how to sign an app directly from Gradle can be found in the CorDapp Jar signing 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 Using Contract Constraints in Transactions.
App versioning with Signature Constraints
Signed apps require a version number to be provided, see Versioning.
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.
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.
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:
@BelongsToContract(MyContract.class)
public class MyState implements ContractState {
// implementation goes here
}
@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:
public class MyContract implements Contract {
public static class MyState implements ContractState {
// state implementation goes here
}
// contract implementation goes here
}
class MyContract : Contract {
data class MyState(val value: Int) : ContractState {
// state implementation goes here
}
// contract implementation goes here
}
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
.
Using Contract Constraints in Transactions
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.
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.
By default the TransactionBuilder
will use Signature Constraints for any issuance transactions if the app attached to it is
signed.
To manually define the Contract Constraint of an output state, see the example below:
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);
// other transaction stuff
return transaction;
}
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
CorDapp JARs (see What is a CorDapp?) 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.
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 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
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 migration to Corda 4
Please read CorDapp constraints migration to understand how to consume and evolve pre-Corda 4 issued hash or CZ whitelisted constrained states using a Corda 4 signed CorDapp (using signature constraints).
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 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:
Driver.driver(DriverParameters(
cordappsForAllNodes = listOf(
TestCordapp.findCordapp("net.corda.finance.schemas"),
TestCordapp.findCordapp("net.corda.finance.flows")
)
) {
// Your test code goes here
})
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
}
);
Starting 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 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
).
Was this page helpful?
Thanks for your feedback!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.
We're sorry this page wasn't helpful. Let us know how we can make it better!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Create an issue
Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.