mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
ebc9cacb53
* All Contract Upgrade functionality performed within a corresponding flow. Removed RPC API calls for contract upgrade authorisation / de-authorisation. Added persistence using AppendOnlyPersistentMap. * Changed to using a PersistentMap to ensure entries can be removed (was causing failing de-authorisation tests). Fixed all warnings. * Added mandatory @Suspendable annotations to flows. * Do not instantiate object unless overridden. * Updated changelog and CordaDocs. * Persistence simplification: only store upgrade contract class name (not serialized object) * Remove nullability from contract_class_name DB column.
126 lines
6.2 KiB
ReStructuredText
126 lines
6.2 KiB
ReStructuredText
.. highlight:: kotlin
|
|
.. raw:: html
|
|
|
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
|
|
Upgrading contracts
|
|
===================
|
|
|
|
While every care is taken in development of contract code,
|
|
inevitably upgrades will be required to fix bugs (in either design or implementation).
|
|
Upgrades can involve a substitution of one version of the contract code for another or changing
|
|
to a different contract that understands how to migrate the existing state objects. State objects
|
|
refer to the contract code (by hash) they are intended for, and even where state objects can be used
|
|
with different contract versions, changing this value requires issuing a new state object.
|
|
|
|
Workflow
|
|
--------
|
|
|
|
Here's the workflow for contract upgrades:
|
|
|
|
1. Two banks, A and B negotiate a trade, off-platform
|
|
|
|
2. Banks A and B execute a protocol to construct a state object representing the trade, using contract X, and include it in a transaction (which is then signed and sent to the consensus service).
|
|
|
|
3. Time passes.
|
|
|
|
4. The developer of contract X discovers a bug in the contract code, and releases a new version, contract Y. The developer will then notify all existing users (e.g. via a mailing list or CorDapp store) to stop their nodes from issuing further states with contract X.
|
|
|
|
5. Banks A and B review the new contract via standard change control processes and identify the contract states they agree to upgrade (they may decide not to upgrade some contract states as these might be needed for some other obligation contract).
|
|
|
|
6. Banks A and B instruct their Corda nodes (via RPC) to be willing to upgrade state objects of contract X, to state objects for contract Y using agreed upgrade path.
|
|
|
|
7. One of the parties initiates (``Initiator``) an upgrade of state objects referring to contract X, to a new state object referring to contract Y.
|
|
|
|
8. A proposed transaction ``Proposal``, taking in the old state and outputting the reissued version, is created and signed with the node's private key.
|
|
|
|
9. The ``Initiator`` node sends the proposed transaction, along with details of the new contract upgrade path its proposing, to all participants of the state object.
|
|
|
|
10. Each counterparty ``Acceptor`` verifies the proposal, signs or rejects the state reissuance accordingly, and sends a signature or rejection notification back to the initiating node.
|
|
|
|
11. If signatures are received from all parties, the initiating node assembles the complete signed transaction and sends it to the consensus service.
|
|
|
|
|
|
Authorising upgrade
|
|
-------------------
|
|
|
|
Each of the participants in the upgrading contract will have to instruct their node that they are willing to upgrade the state object before the upgrade.
|
|
The ``ContractUpgradeFlow`` is used to manage the authorisation records. The administrator can use RPC to trigger either an ``Authorise`` or ``Deauthorise`` flow.
|
|
|
|
.. container:: codeset
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
/**
|
|
* Authorise a contract state upgrade.
|
|
* This will store the upgrade authorisation in persistent store, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
|
|
* Invoking this flow indicates the node is willing to upgrade the [StateAndRef] using the [UpgradedContract] class.
|
|
* This method will NOT initiate the upgrade process. To start the upgrade process, see [Initiator].
|
|
*/
|
|
@StartableByRPC
|
|
class Authorise(
|
|
val stateAndRef: StateAndRef<*>,
|
|
private val upgradedContractClass: Class<out UpgradedContract<*, *>>
|
|
) : FlowLogic<Void?>()
|
|
|
|
/**
|
|
* Deauthorise a contract state upgrade.
|
|
* This will remove the upgrade authorisation from persistent store (and prevent any further upgrade)
|
|
*/
|
|
@StartableByRPC
|
|
class Deauthorise(
|
|
val stateRef: StateRef
|
|
) : FlowLogic< Void?>()
|
|
|
|
Proposing an upgrade
|
|
--------------------
|
|
|
|
After all parties have registered the intention of upgrading the contract state, one of the contract participants can initiate the upgrade process by triggering the ``Initiator`` contract upgrade flow.
|
|
The ``Initiator`` will create a new state and sent to each participant for signatures, each of the participants (Acceptor) will verify, sign the proposal and return to the initiator.
|
|
The transaction will be notarised and persisted once every participant verified and signed the upgrade proposal.
|
|
|
|
Examples
|
|
--------
|
|
|
|
Lets assume Bank A has entered into an agreement with Bank B, and the contract is translated into contract code ``DummyContract`` with state object ``DummyContractState``.
|
|
|
|
A few days after the exchange of contracts, the developer of the contract code discovered a bug/misrepresentation in the contract code.
|
|
Bank A and Bank B decided to upgrade the contract to ``DummyContractV2``
|
|
|
|
1. Developer will create a new contract extending the ``UpgradedContract`` class, and a new state object ``DummyContractV2.State`` referencing the new contract.
|
|
|
|
.. literalinclude:: /../../test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV2.kt
|
|
:language: kotlin
|
|
:start-after: DOCSTART 1
|
|
:end-before: DOCEND 1
|
|
|
|
2. Bank A will instruct its node to accept the contract upgrade to ``DummyContractV2`` for the contract state.
|
|
|
|
.. container:: codeset
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
val rpcClient : CordaRPCClient = << Bank A's Corda RPC Client >>
|
|
val rpcA = rpcClient.proxy()
|
|
rpcA.startFlow(ContractUpgradeFlow.Authorise(<<StateAndRef of the contract state>>, DummyContractV2::class.java))
|
|
|
|
3. Bank B now initiate the upgrade Flow, this will send a upgrade proposal to all contract participants.
|
|
Each of the participants of the contract state will sign and return the contract state upgrade proposal once they have validated and agreed with the upgrade.
|
|
The upgraded transaction state will be recorded in every participant's node at the end of the flow.
|
|
|
|
.. container:: codeset
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
val rpcClient : CordaRPCClient = << Bank B's Corda RPC Client >>
|
|
val rpcB = rpcClient.proxy()
|
|
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow(stateAndRef, upgrade) },
|
|
<<StateAndRef of the contract state>>,
|
|
DummyContractV2::class.java)
|
|
|
|
.. note:: See ``ContractUpgradeFlowTest`` for more detailed code examples.
|
|
|
|
|
|
|