mirror of
https://github.com/corda/corda.git
synced 2025-06-11 20:01:46 +00:00
Joel upgrade docs (#3730)
* Instructions on how to perform H2 DB upgrades without starting the node. * Formatting error. Adds required subsections in H2 docs. * Merges duplicated information about H2 DB access. * Drops script references for now. * Clarifies upgrade notes. * Better title. * Inr * Addresses review comments. * Addresses review comments.
This commit is contained in:
@ -1,20 +1,37 @@
|
|||||||
Database access when running H2
|
Database access when running H2
|
||||||
===============================
|
===============================
|
||||||
When running a node using the H2 database, the node can be configured to expose its internal database over socket which
|
|
||||||
can be browsed using any tool that can use JDBC drivers.
|
|
||||||
The JDBC URL is printed during node startup to the log and will typically look like this:
|
|
||||||
|
|
||||||
``jdbc:h2:tcp://localhost:31339/node``
|
.. contents::
|
||||||
|
|
||||||
The username and password can be altered in the :doc:`corda-configuration-file` but default to username "sa" and a blank
|
Configuring the username and password
|
||||||
password.
|
-------------------------------------
|
||||||
|
|
||||||
Any database browsing tool that supports JDBC can be used, but if you have IntelliJ Ultimate edition then there is
|
The database (a file called ``persistence.mv.db``) is created when the node first starts up. By default, it has an
|
||||||
a tool integrated with your IDE. Just open the database window and add an H2 data source with the above details.
|
administrator user ``sa`` and a blank password. The node requires the user with administrator permissions in order to
|
||||||
You will now be able to browse the tables and row data within them.
|
creates tables upon the first startup or after deploying new CorDapps with their own tables. The database password is
|
||||||
|
required only when the H2 database is exposed on non-localhost address (which is disabled by default).
|
||||||
|
|
||||||
By default, the node's H2 database is not exposed. This behaviour can be overridden by specifying the full network
|
This username and password can be changed in node configuration:
|
||||||
address (interface and port), using the new ``h2Settings`` syntax in the node configuration.
|
|
||||||
|
.. sourcecode:: groovy
|
||||||
|
|
||||||
|
dataSourceProperties = {
|
||||||
|
dataSource.user = [USER]
|
||||||
|
dataSource.password = [PASSWORD]
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that changing the user/password for the existing node in ``node.conf`` will not update them in the H2 database.
|
||||||
|
You need to log into the database first to create a new user or change a user's password.
|
||||||
|
|
||||||
|
Connecting via a socket on a running node
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
Configuring the port
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Nodes backed by an H2 database will not expose this database by default. To configure the node to expose its internal
|
||||||
|
database over a socket which can be browsed using any tool that can use JDBC drivers, you must specify the full network
|
||||||
|
address (interface and port) using the ``h2Settings`` syntax in the node configuration.
|
||||||
|
|
||||||
The configuration below will restrict the H2 service to run on ``localhost``:
|
The configuration below will restrict the H2 service to run on ``localhost``:
|
||||||
|
|
||||||
@ -32,8 +49,8 @@ If you want H2 to auto-select a port (mimicking the old ``h2Port`` behaviour), y
|
|||||||
address: "localhost:0"
|
address: "localhost:0"
|
||||||
}
|
}
|
||||||
|
|
||||||
If remote access is required, the address can be changed to ``0.0.0.0``.
|
If remote access is required, the address can be changed to ``0.0.0.0`` to listen on all interfaces. A password must be
|
||||||
The node requires a database password to be set when the database is exposed on the network interface to listen on.
|
set for the database user before doing so.
|
||||||
|
|
||||||
.. sourcecode:: groovy
|
.. sourcecode:: groovy
|
||||||
|
|
||||||
@ -44,5 +61,44 @@ The node requires a database password to be set when the database is exposed on
|
|||||||
dataSource.password : "strongpassword"
|
dataSource.password : "strongpassword"
|
||||||
}
|
}
|
||||||
|
|
||||||
The previous ``h2Port`` syntax is now deprecated. ``h2Port`` will continue to work but the database
|
.. note:: The previous ``h2Port`` syntax is now deprecated. ``h2Port`` will continue to work but the database will only
|
||||||
will only be accessible on localhost.
|
be accessible on localhost.
|
||||||
|
|
||||||
|
Connecting to the database
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
The JDBC URL is printed during node startup to the log and will typically look like this:
|
||||||
|
|
||||||
|
``jdbc:h2:tcp://localhost:31339/node``
|
||||||
|
|
||||||
|
Any database browsing tool that supports JDBC can be used.
|
||||||
|
|
||||||
|
Connecting using the H2 Console
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* Download the **last stable** `h2 platform-independent zip <http://www.h2database.com/html/download.html>`_, unzip the
|
||||||
|
zip, and navigate in a terminal window to the unzipped folder
|
||||||
|
|
||||||
|
* Change directories to the bin folder: ``cd h2/bin``
|
||||||
|
|
||||||
|
* Run the following command to open the h2 web console in a web browser tab:
|
||||||
|
|
||||||
|
* Unix: ``sh h2.sh``
|
||||||
|
* Windows: ``h2.bat``
|
||||||
|
|
||||||
|
* Paste the node's JDBC URL into the JDBC URL field and click ``Connect``, using the default username (``sa``) and no
|
||||||
|
password (unless configured otherwise)
|
||||||
|
|
||||||
|
You will be presented with a web interface that shows the contents of your node's storage and vault, and provides an
|
||||||
|
interface for you to query them using SQL.
|
||||||
|
|
||||||
|
.. _h2_relative_path:
|
||||||
|
|
||||||
|
Connecting directly to the node's ``persistence.mv.db`` file
|
||||||
|
------------------------------------------------------------
|
||||||
|
|
||||||
|
You can also use the H2 Console to connect directly to the node's ``persistence.mv.db`` file. Ensure the node is off
|
||||||
|
before doing so, as access to the database file requires exclusive access. If the node is still running, the H2 Console
|
||||||
|
will return the following error:
|
||||||
|
``Database may be already in use: null. Possible solutions: close all other connection(s); use the server mode [90020-196]``.
|
||||||
|
|
||||||
|
``jdbc:h2:~/path/to/file/persistence``
|
||||||
|
@ -3,57 +3,7 @@ Node database
|
|||||||
|
|
||||||
Default in-memory database
|
Default in-memory database
|
||||||
--------------------------
|
--------------------------
|
||||||
By default, nodes store their data in an H2 database.
|
By default, nodes store their data in an H2 database. See :doc:`node-database-access-h2`.
|
||||||
The database (a file persistence.mv.db) is created at the first node startup with the administrator user 'sa' and a blank password.
|
|
||||||
The user name and password can be changed in node configuration:
|
|
||||||
|
|
||||||
.. sourcecode:: groovy
|
|
||||||
|
|
||||||
dataSourceProperties = {
|
|
||||||
dataSource.user = [USER]
|
|
||||||
dataSource.password = [PASSWORD]
|
|
||||||
}
|
|
||||||
|
|
||||||
Note, changing user/password for the existing node in node.conf will not update them in the H2 database,
|
|
||||||
you need to login to the database first to create new user or change the user password.
|
|
||||||
The database password is required only when the H2 database is exposed on non-localhost address (which is disabled by default).
|
|
||||||
The node requires the user with administrator permissions in order to creates tables upon the first startup
|
|
||||||
or after deplying new CorDapps with own tables.
|
|
||||||
|
|
||||||
You can connect directly to a running node's database to see its
|
|
||||||
stored states, transactions and attachments as follows:
|
|
||||||
|
|
||||||
* Enable the H2 database access in the node configuration using the following syntax:
|
|
||||||
|
|
||||||
.. sourcecode:: groovy
|
|
||||||
|
|
||||||
h2Settings {
|
|
||||||
address: "localhost:0"
|
|
||||||
}
|
|
||||||
|
|
||||||
* Download the **last stable** `h2 platform-independent zip <http://www.h2database.com/html/download.html>`_, unzip the zip, and
|
|
||||||
navigate in a terminal window to the unzipped folder
|
|
||||||
* Change directories to the bin folder: ``cd h2/bin``
|
|
||||||
|
|
||||||
* Run the following command to open the h2 web console in a web browser tab:
|
|
||||||
|
|
||||||
* Unix: ``sh h2.sh``
|
|
||||||
* Windows: ``h2.bat``
|
|
||||||
|
|
||||||
* Find the node's JDBC connection string. Each node outputs its connection string in the terminal
|
|
||||||
window as it starts up. In a terminal window where a node is running, look for the following string:
|
|
||||||
|
|
||||||
``Database connection URL is : jdbc:h2:tcp://10.18.0.150:56736/node``
|
|
||||||
|
|
||||||
* Paste this string into the JDBC URL field and click ``Connect``, using the default username (``sa``) and no password.
|
|
||||||
|
|
||||||
You will be presented with a web interface that shows the contents of your node's storage and vault, and provides an
|
|
||||||
interface for you to query them using SQL.
|
|
||||||
|
|
||||||
The default behaviour is to expose the H2 database on localhost. This can be overridden in the
|
|
||||||
node configuration using ``h2Settings.address`` and specifying the address of the network interface to listen on,
|
|
||||||
or simply using ``0.0.0.0:0`` to listen on all interfaces. The node requires a database password to be set when
|
|
||||||
the database is exposed on the network interface to listen on.
|
|
||||||
|
|
||||||
PostgreSQL
|
PostgreSQL
|
||||||
----------
|
----------
|
||||||
|
@ -9,24 +9,27 @@ first public Beta (:ref:`Milestone 12 <changelog_m12>`), to :ref:`V1.0 <changelo
|
|||||||
|
|
||||||
General rules
|
General rules
|
||||||
-------------
|
-------------
|
||||||
Always remember to update the version identifiers in your project gradle file:
|
* Always remember to update the version identifiers in your project gradle file:
|
||||||
|
|
||||||
.. sourcecode:: shell
|
.. sourcecode:: shell
|
||||||
|
|
||||||
ext.corda_release_version = '1.0.0'
|
ext.corda_release_version = 'x.y.0'
|
||||||
ext.corda_gradle_plugins_version = '1.0.0'
|
ext.corda_gradle_plugins_version = 'x.y.0'
|
||||||
|
|
||||||
It may be necessary to update the version of major dependencies:
|
* It may also be necessary to update the version of major dependencies:
|
||||||
|
|
||||||
.. sourcecode:: shell
|
.. sourcecode:: shell
|
||||||
|
|
||||||
ext.kotlin_version = '1.1.4'
|
ext.kotlin_version = 'x.y.z'
|
||||||
ext.quasar_version = '0.7.9'
|
ext.quasar_version = 'x.y.z'
|
||||||
|
|
||||||
Please consult the relevant release notes of the release in question. If not specified, you may assume the
|
* Please consult the relevant release notes of the release in question. If not specified, you may assume the
|
||||||
versions you are currently using are still in force.
|
versions you are currently using are still in force
|
||||||
|
|
||||||
We also strongly recommend cross referencing with the :doc:`changelog` to confirm changes.
|
* We also strongly recommend cross referencing with the :doc:`changelog` to confirm changes
|
||||||
|
|
||||||
|
* To run database upgrades against H2, you'll need to connect to the node's database without starting the node. You can
|
||||||
|
do this by connecting directly to the node's ``persistence.mv.db`` file. See :ref:`h2_relative_path`
|
||||||
|
|
||||||
UNRELEASED
|
UNRELEASED
|
||||||
----------
|
----------
|
||||||
|
@ -15,26 +15,8 @@ Upgrading a CorDapp (outside of platform version upgrades)
|
|||||||
CorDapp versioning
|
CorDapp versioning
|
||||||
------------------
|
------------------
|
||||||
The Corda platform does not mandate a version number on a per-CorDapp basis. Different elements of a CorDapp are
|
The Corda platform does not mandate a version number on a per-CorDapp basis. Different elements of a CorDapp are
|
||||||
allowed to evolve separately:
|
allowed to evolve separately. Sometimes, however, a change to one element will require changes to other elements. For
|
||||||
|
example, changing a shared data structure may require flow changes that are not backwards-compatible.
|
||||||
* States
|
|
||||||
* Contracts
|
|
||||||
* Services
|
|
||||||
* Flows
|
|
||||||
* Utilities and library functions
|
|
||||||
* All, or a subset, of the above
|
|
||||||
|
|
||||||
Sometimes, however, a change to one element will require changes to other elements. For example, changing a shared data
|
|
||||||
structure may require flow changes that are not backwards-compatible.
|
|
||||||
|
|
||||||
Areas of consideration
|
|
||||||
----------------------
|
|
||||||
This document will consider the following types of versioning:
|
|
||||||
|
|
||||||
* Flow versioning
|
|
||||||
* State and contract versioning
|
|
||||||
* State and state schema versioning
|
|
||||||
* Serialisation of custom types
|
|
||||||
|
|
||||||
Flow versioning
|
Flow versioning
|
||||||
---------------
|
---------------
|
||||||
@ -48,8 +30,8 @@ The ``version`` property, which defaults to 1, specifies the flow's version. Thi
|
|||||||
whenever there is a release of a flow which has changes that are not backwards-compatible. A non-backwards compatible
|
whenever there is a release of a flow which has changes that are not backwards-compatible. A non-backwards compatible
|
||||||
change is one that changes the interface of the flow.
|
change is one that changes the interface of the flow.
|
||||||
|
|
||||||
What defines the interface of a flow?
|
Defining a flow's interface
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
The flow interface is defined by the sequence of ``send`` and ``receive`` calls between an ``InitiatingFlow`` and an
|
The flow interface is defined by the sequence of ``send`` and ``receive`` calls between an ``InitiatingFlow`` and an
|
||||||
``InitiatedBy`` flow, including the types of the data sent and received. We can picture a flow's interface as follows:
|
``InitiatedBy`` flow, including the types of the data sent and received. We can picture a flow's interface as follows:
|
||||||
|
|
||||||
@ -75,8 +57,8 @@ As long as both the ``InitiatingFlow`` and the ``InitiatedBy`` flows conform to
|
|||||||
be implemented in any way you see fit (including adding proprietary business logic that is not shared with other
|
be implemented in any way you see fit (including adding proprietary business logic that is not shared with other
|
||||||
parties).
|
parties).
|
||||||
|
|
||||||
What constitutes a non-backwards compatible flow change?
|
Non-backwards compatible flow changes
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
A flow can become backwards-incompatible in two main ways:
|
A flow can become backwards-incompatible in two main ways:
|
||||||
|
|
||||||
* The sequence of ``send`` and ``receive`` calls changes:
|
* The sequence of ``send`` and ``receive`` calls changes:
|
||||||
@ -86,8 +68,8 @@ A flow can become backwards-incompatible in two main ways:
|
|||||||
|
|
||||||
* The types of the ``send`` and ``receive`` calls changes
|
* The types of the ``send`` and ``receive`` calls changes
|
||||||
|
|
||||||
What happens when running flows with incompatible versions?
|
Consequences of running flows with incompatible versions
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Pairs of ``InitiatingFlow`` flows and ``InitiatedBy`` flows that have incompatible interfaces are likely to exhibit the
|
Pairs of ``InitiatingFlow`` flows and ``InitiatedBy`` flows that have incompatible interfaces are likely to exhibit the
|
||||||
following behaviour:
|
following behaviour:
|
||||||
|
|
||||||
@ -98,24 +80,8 @@ following behaviour:
|
|||||||
* One of the flows ends with an exception: "Counterparty flow terminated early on the other side", because one flow
|
* One of the flows ends with an exception: "Counterparty flow terminated early on the other side", because one flow
|
||||||
sends some data to another flow, but the latter flow has already ended
|
sends some data to another flow, but the latter flow has already ended
|
||||||
|
|
||||||
How do I upgrade my flows?
|
Ensuring flow backwards-compatibility
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
1. Update the flow and test the changes. Increment the flow version number in the ``InitiatingFlow`` annotation.
|
|
||||||
2. Ensure that all versions of the existing flow have finished running and there are no pending ``SchedulableFlows`` on
|
|
||||||
any of the nodes on the business network. This can be done by *draining the node* (see below).
|
|
||||||
3. Shut down the node.
|
|
||||||
4. Replace the existing CorDapp JAR with the CorDapp JAR containing the new flow.
|
|
||||||
5. Start the node.
|
|
||||||
|
|
||||||
If you shut down all nodes and upgrade them all at the same time, any incompatible change can be made.
|
|
||||||
|
|
||||||
In situations where some nodes may still be using previous versions of a flow and thus new versions of your flow may
|
|
||||||
talk to old versions, the updated flows need to be backwards-compatible. This will be the case for almost any real
|
|
||||||
deployment in which you cannot easily coordinate the roll-out of new code across the network.
|
|
||||||
|
|
||||||
How do I ensure flow backwards-compatibility?
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
The ``InitiatingFlow`` version number is included in the flow session handshake and exposed to both parties via the
|
The ``InitiatingFlow`` version number is included in the flow session handshake and exposed to both parties via the
|
||||||
``FlowLogic.getFlowContext`` method. This method takes a ``Party`` and returns a ``FlowContext`` object which describes
|
``FlowLogic.getFlowContext`` method. This method takes a ``Party`` and returns a ``FlowContext`` object which describes
|
||||||
the flow running on the other side. In particular, it has a ``flowVersion`` property which can be used to
|
the flow running on the other side. In particular, it has a ``flowVersion`` property which can be used to
|
||||||
@ -159,8 +125,8 @@ This code shows a flow that in its first version expected to receive an Int, but
|
|||||||
expect a String. This flow is still able to communicate with parties that are running the older CorDapp containing
|
expect a String. This flow is still able to communicate with parties that are running the older CorDapp containing
|
||||||
the older flow.
|
the older flow.
|
||||||
|
|
||||||
How do I deal with interface changes to inlined subflows?
|
Handling interface changes to inlined subflows
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
Here is an example of an in-lined subflow:
|
Here is an example of an in-lined subflow:
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
@ -254,8 +220,26 @@ Flows which are not an ``InitiatingFlow`` or ``InitiatedBy`` flow, or inlined su
|
|||||||
``InitiatingFlow`` or ``InitiatedBy`` flow, can be updated without consideration of backwards-compatibility. Flows of
|
``InitiatingFlow`` or ``InitiatedBy`` flow, can be updated without consideration of backwards-compatibility. Flows of
|
||||||
this type include utility flows for querying the vault and flows for reaching out to external systems.
|
this type include utility flows for querying the vault and flows for reaching out to external systems.
|
||||||
|
|
||||||
Flow drains
|
Performing flow upgrades
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
1. Update the flow and test the changes. Increment the flow version number in the ``InitiatingFlow`` annotation
|
||||||
|
2. Ensure that all versions of the existing flow have finished running and there are no pending ``SchedulableFlows`` on
|
||||||
|
any of the nodes on the business network. This can be done by :ref:`draining_the_node`
|
||||||
|
3. Shut down the node
|
||||||
|
4. Replace the existing CorDapp JAR with the CorDapp JAR containing the new flow
|
||||||
|
5. Start the node
|
||||||
|
|
||||||
|
If you shut down all nodes and upgrade them all at the same time, any incompatible change can be made.
|
||||||
|
|
||||||
|
In situations where some nodes may still be using previous versions of a flow and thus new versions of your flow may
|
||||||
|
talk to old versions, the updated flows need to be backwards-compatible. This will be the case for almost any real
|
||||||
|
deployment in which you cannot easily coordinate the roll-out of new code across the network.
|
||||||
|
|
||||||
|
.. _draining_the_node:
|
||||||
|
|
||||||
|
Draining the node
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
A flow *checkpoint* is a serialised snapshot of the flow's stack frames and any objects reachable from the stack.
|
A flow *checkpoint* is a serialised snapshot of the flow's stack frames and any objects reachable from the stack.
|
||||||
Checkpoints are saved to the database automatically when a flow suspends or resumes, which typically happens when
|
Checkpoints are saved to the database automatically when a flow suspends or resumes, which typically happens when
|
||||||
@ -280,34 +264,36 @@ Contract and state versioning
|
|||||||
|
|
||||||
There are two types of contract/state upgrade:
|
There are two types of contract/state upgrade:
|
||||||
|
|
||||||
1. *Implicit:* By allowing multiple implementations of the contract ahead of time, using constraints. See :doc:`api-contract-constraints` to learn more.
|
1. *Implicit:* By allowing multiple implementations of the contract ahead of time, using constraints. See
|
||||||
2. *Explicit:* By creating a special *contract upgrade transaction* and getting all participants of a state to sign it using the
|
:doc:`api-contract-constraints` to learn more
|
||||||
contract upgrade flows.
|
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 section of the documentation focuses only on the *explicit* type of upgrade.
|
This section of the documentation focuses only on *explicit* upgrades.
|
||||||
|
|
||||||
In an explicit upgrade contracts and states can be changed in arbitrary ways, if and only if all of the state'
|
In an explicit upgrade, contracts and states can be changed in arbitrary ways, if and only if all of the state's
|
||||||
s participants agree to the proposed upgrade. The following combinations of upgrades are possible:
|
participants agree to the proposed upgrade. The following combinations of upgrades are possible:
|
||||||
|
|
||||||
* A contract is upgraded while the state definition remains the same.
|
* A contract is upgraded while the state definition remains the same
|
||||||
* A state is upgraded while the contract stays the same.
|
* A state is upgraded while the contract stays the same
|
||||||
* The state and the contract are updated simultaneously.
|
* The state and the contract are updated simultaneously
|
||||||
|
|
||||||
The procedure for updating a state or a contract using a flag-day approach is quite simple:
|
Performing contract and state upgrades
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
* Update and test the state or contract.
|
1. Preserve the existing state and contract definitions
|
||||||
* Produce a new CorDapp JAR file and distribute it to all the relevant parties.
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
* Each node operator stops their node, replaces the existing JAR with the new one, and restarts. They may wish to do
|
Currently, all nodes must **permanently** keep **all** old state and contract definitions on their node's classpath
|
||||||
a node drain first to avoid the definition of states or contracts changing whilst a flow is in progress.
|
|
||||||
* Run the contract upgrade authorisation flow for each state that requires updating on every node.
|
|
||||||
* For each state, one node should run the contract upgrade initiation flow, which will contact the rest.
|
|
||||||
|
|
||||||
Update Process
|
.. note:: Once the contract-code-as-an-attachment feature has been implemented, nodes will only be required to keep the
|
||||||
~~~~~~~~~~~~~~
|
old state and contract definitions on their node's classpath for the duration of the upgrade
|
||||||
|
|
||||||
Writing the new state and contract definitions
|
Changing a state or contract's package constitutes a definition change. If you want to move a state or contract
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
definition to a new package, you must also preserve the definition in the old package
|
||||||
Start by updating the contract and/or state definitions. There are no restrictions on how states are updated. However,
|
|
||||||
|
2. Write the new state and contract definitions
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Update the contract and/or state definitions. There are no restrictions on how states are updated. However,
|
||||||
upgraded contracts must implement the ``UpgradedContract`` interface. This interface is defined as:
|
upgraded contracts must implement the ``UpgradedContract`` interface. This interface is defined as:
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. sourcecode:: kotlin
|
||||||
@ -337,18 +323,29 @@ For example, in case of hash constraints the hash of the legacy JAR file should
|
|||||||
override val legacyContractConstraint: AttachmentConstraint
|
override val legacyContractConstraint: AttachmentConstraint
|
||||||
get() = HashAttachmentConstraint(SecureHash.parse("E02BD2B9B010BBCE49C0D7C35BECEF2C79BEB2EE80D902B54CC9231418A4FA0C"))
|
get() = HashAttachmentConstraint(SecureHash.parse("E02BD2B9B010BBCE49C0D7C35BECEF2C79BEB2EE80D902B54CC9231418A4FA0C"))
|
||||||
|
|
||||||
Authorising the upgrade
|
3. Create the new CorDapp JAR
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Once the new states and contracts are on the classpath for all the relevant nodes, the next step is for all nodes to
|
Produce a new CorDapp JAR file and distribute it to all the relevant nodes. This JAR file should contain all the old
|
||||||
run the ``ContractUpgradeFlow.Authorise`` flow. This flow takes a ``StateAndRef`` of the state to update as well as a
|
contract and state definitions, plus any new contract and state definitions.
|
||||||
reference to the new contract, which must implement the ``UpgradedContract`` interface.
|
|
||||||
|
4. Restart the nodes
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Have each node operator stop their node, replace the existing CorDapp JAR with the new CorDapp JAR, and restart their
|
||||||
|
node. They may wish to do a :ref:`node drain <draining_the_node>` first to avoid the definition of states or contracts
|
||||||
|
changing whilst a flow is in progress.
|
||||||
|
|
||||||
|
5. Authorise the upgrade
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Once the new states and contracts are on the classpath for all the relevant nodes, the nodes must all run the
|
||||||
|
``ContractUpgradeFlow.Authorise`` flow. This flow takes a ``StateAndRef`` of the state to update as well as a reference
|
||||||
|
to the new contract, which must implement the ``UpgradedContract`` interface.
|
||||||
|
|
||||||
At any point, a node administrator may de-authorise a contract upgrade by running the
|
At any point, a node administrator may de-authorise a contract upgrade by running the
|
||||||
``ContractUpgradeFlow.Deauthorise`` flow.
|
``ContractUpgradeFlow.Deauthorise`` flow.
|
||||||
|
|
||||||
Performing the upgrade
|
6. Perform the upgrade
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Once all nodes have performed the authorisation process, a participant must be chosen to initiate the upgrade via the
|
Once all nodes have performed the authorisation process, a **single** node must initiate the upgrade via the
|
||||||
``ContractUpgradeFlow.Initiate`` flow for each state object. This flow has the following signature:
|
``ContractUpgradeFlow.Initiate`` flow for each state object. This flow has the following signature:
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
.. sourcecode:: kotlin
|
||||||
@ -375,9 +372,6 @@ Capabilities of the contract upgrade flows
|
|||||||
* Equally, the state doesn't have to change at all
|
* Equally, the state doesn't have to change at all
|
||||||
* If a node has not yet run the contract upgrade authorisation flow, they will not be able to upgrade the contract
|
* If a node has not yet run the contract upgrade authorisation flow, they will not be able to upgrade the contract
|
||||||
and/or state objects
|
and/or state objects
|
||||||
* Upgrade authorisations can subsequently be deauthorised
|
|
||||||
* Upgrades do not have to happen immediately. For a period, the two parties can use the old states and contracts
|
|
||||||
side-by-side
|
|
||||||
* State schema changes are handled separately
|
* State schema changes are handled separately
|
||||||
|
|
||||||
Writing new states and contracts
|
Writing new states and contracts
|
||||||
@ -390,64 +384,19 @@ Writing new states and contracts
|
|||||||
value
|
value
|
||||||
* Updated state objects can use the old contract code as long as there is no requirement to update it
|
* Updated state objects can use the old contract code as long as there is no requirement to update it
|
||||||
|
|
||||||
Dealing with old contract code JAR files
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
* Currently, all parties **must** keep the old state and contract definitions on their node's classpath as they will
|
|
||||||
always be required to verify transactions involving previous versions of the state using previous versions of the
|
|
||||||
contract
|
|
||||||
|
|
||||||
* This will change when the contract code as an attachment feature has been fully implemented.
|
|
||||||
|
|
||||||
Permissioning
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
* Only node administrators are able to run the contract upgrade authorisation and deauthorisation flows
|
|
||||||
|
|
||||||
Logistics
|
Logistics
|
||||||
^^^^^^^^^
|
^^^^^^^^^
|
||||||
* All nodes need to run the contract upgrade authorisation flow
|
* All nodes need to run the contract upgrade authorisation flow to upgrade the contract and/or state objects
|
||||||
|
* Only node administrators are able to run the contract upgrade authorisation and deauthorisation flows
|
||||||
|
* Upgrade authorisations can subsequently be deauthorised
|
||||||
* Only one node should run the contract upgrade initiation flow. If multiple nodes run it for the same ``StateRef``, a
|
* Only one node should run the contract upgrade initiation flow. If multiple nodes run it for the same ``StateRef``, a
|
||||||
double-spend will occur for all but the first completed upgrade
|
double-spend will occur for all but the first completed upgrade
|
||||||
|
* Upgrades do not have to happen immediately. For a period, the two parties can use the old states and contracts
|
||||||
|
side-by-side
|
||||||
* The supplied upgrade flows upgrade one state object at a time
|
* The supplied upgrade flows upgrade one state object at a time
|
||||||
|
|
||||||
Serialisation
|
State schema versioning
|
||||||
-------------
|
-----------------------
|
||||||
|
|
||||||
Currently, the serialisation format for everything except flow checkpoints (which uses a Kryo-based format) is based
|
|
||||||
upon AMQP 1.0, a self-describing and controllable serialisation format. AMQP is desirable because it allows us to have
|
|
||||||
a schema describing what has been serialized alongside the data itself. This assists with versioning and deserialising
|
|
||||||
long-ago archived data, among other things.
|
|
||||||
|
|
||||||
Writing classes
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
Although not strictly related to versioning, AMQP serialisation dictates that we must write our classes in a particular way:
|
|
||||||
|
|
||||||
* Your class must have a constructor that takes all the properties that you wish to record in the serialized form. This
|
|
||||||
is required in order for the serialization framework to reconstruct an instance of your class
|
|
||||||
* If more than one constructor is provided, the serialization framework needs to know which one to use. The
|
|
||||||
``@ConstructorForDeserialization`` annotation can be used to indicate the chosen constructor. For a Kotlin class
|
|
||||||
without the ``@ConstructorForDeserialization`` annotation, the primary constructor is selected
|
|
||||||
* The class must be compiled with parameter names in the .class file. This is the default in Kotlin but must be turned
|
|
||||||
on in Java (using the ``-parameters`` command line option to ``javac``)
|
|
||||||
* Your class must provide a Java Bean getter for each of the properties in the constructor, with a matching name. For
|
|
||||||
example, if a class has the constructor parameter ``foo``, there must be a getter called ``getFoo()``. If ``foo`` is
|
|
||||||
a boolean, the getter may optionally be called ``isFoo()``. This is why the class must be compiled with parameter
|
|
||||||
names turned on
|
|
||||||
* The class must be annotated with ``@CordaSerializable``
|
|
||||||
* The declared types of constructor arguments/getters must be supported, and where generics are used the generic
|
|
||||||
parameter must be a supported type, an open wildcard (*), or a bounded wildcard which is currently widened to an open
|
|
||||||
wildcard
|
|
||||||
* Any superclass must adhere to the same rules, but can be abstract
|
|
||||||
* Object graph cycles are not supported, so an object cannot refer to itself, directly or indirectly
|
|
||||||
|
|
||||||
Writing enums
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
Elements cannot be added to enums in a new version of the code. Hence, enums are only a good fit for genuinely static
|
|
||||||
data that will never change (e.g. days of the week). A ``Buy`` or ``Sell`` flag is another. However, something like
|
|
||||||
``Trade Type`` or ``Currency Code`` will likely change. For those, it is preferable to choose another representation,
|
|
||||||
such as a string.
|
|
||||||
|
|
||||||
State schemas
|
|
||||||
-------------
|
|
||||||
By default, all state objects are serialised to the database as a string of bytes and referenced by their ``StateRef``.
|
By default, all state objects are serialised to the database as a string of bytes and referenced by their ``StateRef``.
|
||||||
However, it is also possible to define custom schemas for serialising particular properties or combinations of
|
However, it is also possible to define custom schemas for serialising particular properties or combinations of
|
||||||
properties, so that they can be queried from a source other than the Corda Vault. This is done by implementing the
|
properties, so that they can be queried from a source other than the Corda Vault. This is done by implementing the
|
||||||
@ -639,3 +588,42 @@ Then, in ``generateMappedObject``, add support for the new schema:
|
|||||||
|
|
||||||
With this approach, whenever the state object is stored in the vault, a representation of it will be stored in two
|
With this approach, whenever the state object is stored in the vault, a representation of it will be stored in two
|
||||||
separate database tables where possible - one for each supported schema.
|
separate database tables where possible - one for each supported schema.
|
||||||
|
|
||||||
|
Serialisation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The Corda serialisation format
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Currently, the serialisation format for everything except flow checkpoints (which uses a Kryo-based format) is based
|
||||||
|
on AMQP 1.0, a self-describing and controllable serialisation format. AMQP is desirable because it allows us to have
|
||||||
|
a schema describing what has been serialized alongside the data itself. This assists with versioning and deserialising
|
||||||
|
long-ago archived data, among other things.
|
||||||
|
|
||||||
|
Writing classes that meet the serialisation format requirements
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Although not strictly related to versioning, AMQP serialisation dictates that we must write our classes in a particular way:
|
||||||
|
|
||||||
|
* Your class must have a constructor that takes all the properties that you wish to record in the serialized form. This
|
||||||
|
is required in order for the serialization framework to reconstruct an instance of your class
|
||||||
|
* If more than one constructor is provided, the serialization framework needs to know which one to use. The
|
||||||
|
``@ConstructorForDeserialization`` annotation can be used to indicate the chosen constructor. For a Kotlin class
|
||||||
|
without the ``@ConstructorForDeserialization`` annotation, the primary constructor is selected
|
||||||
|
* The class must be compiled with parameter names in the .class file. This is the default in Kotlin but must be turned
|
||||||
|
on in Java (using the ``-parameters`` command line option to ``javac``)
|
||||||
|
* Your class must provide a Java Bean getter for each of the properties in the constructor, with a matching name. For
|
||||||
|
example, if a class has the constructor parameter ``foo``, there must be a getter called ``getFoo()``. If ``foo`` is
|
||||||
|
a boolean, the getter may optionally be called ``isFoo()``. This is why the class must be compiled with parameter
|
||||||
|
names turned on
|
||||||
|
* The class must be annotated with ``@CordaSerializable``
|
||||||
|
* The declared types of constructor arguments/getters must be supported, and where generics are used the generic
|
||||||
|
parameter must be a supported type, an open wildcard (*), or a bounded wildcard which is currently widened to an open
|
||||||
|
wildcard
|
||||||
|
* Any superclass must adhere to the same rules, but can be abstract
|
||||||
|
* Object graph cycles are not supported, so an object cannot refer to itself, directly or indirectly
|
||||||
|
|
||||||
|
Writing enums
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
Elements cannot be added to enums in a new version of the code. Hence, enums are only a good fit for genuinely static
|
||||||
|
data that will never change (e.g. days of the week). A ``Buy`` or ``Sell`` flag is another. However, something like
|
||||||
|
``Trade Type`` or ``Currency Code`` will likely change. For those, it is preferable to choose another representation,
|
||||||
|
such as a string.
|
Reference in New Issue
Block a user