mirror of
https://github.com/corda/corda.git
synced 2025-05-21 01:37:41 +00:00
Merge from R3 Corda 3.0 Dev Preview release branch to master
This commit is contained in:
parent
e7cc38cf1b
commit
729aa65e7f
10
build.gradle
10
build.gradle
@ -4,7 +4,9 @@ buildscript {
|
|||||||
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
|
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
|
||||||
|
|
||||||
// Our version: bump this on release.
|
// Our version: bump this on release.
|
||||||
ext.corda_release_version = "3.0-ENT-snapshot"
|
ext.corda_release_version = "R3.CORDA-3.0-SNAPSHOT" // "CORDA-3.0-SNAPSHOT" for Corda (Open Source)
|
||||||
|
ext.corda_release_distribution = "com.r3.corda" // "net.corda" for Corda (Open Source)
|
||||||
|
|
||||||
// Increment this on any release that changes public APIs anywhere in the Corda platform
|
// Increment this on any release that changes public APIs anywhere in the Corda platform
|
||||||
ext.corda_platform_version = constants.getProperty("platformVersion")
|
ext.corda_platform_version = constants.getProperty("platformVersion")
|
||||||
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
||||||
@ -154,7 +156,7 @@ allprojects {
|
|||||||
attributes('Corda-Release-Version': corda_release_version)
|
attributes('Corda-Release-Version': corda_release_version)
|
||||||
attributes('Corda-Platform-Version': corda_platform_version)
|
attributes('Corda-Platform-Version': corda_platform_version)
|
||||||
attributes('Corda-Revision': corda_revision)
|
attributes('Corda-Revision': corda_revision)
|
||||||
attributes('Corda-Vendor': 'Corda Enterprise Edition')
|
attributes('Corda-Vendor': 'R3 Corda Edition')
|
||||||
attributes('Automatic-Module-Name': "net.corda.${task.project.name.replaceAll('-', '.')}")
|
attributes('Automatic-Module-Name': "net.corda.${task.project.name.replaceAll('-', '.')}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,7 +193,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'com.r3.corda.enterprise'
|
group 'com.r3.corda'
|
||||||
version "$corda_release_version"
|
version "$corda_release_version"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@ -301,7 +303,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// User and key are commented out to prevent accidental pushes of Enterprise to public repos. DO NOT UNCOMMENT.
|
// User and key are commented out to prevent accidental pushes of R3 Corda to public repos. DO NOT UNCOMMENT.
|
||||||
bintrayConfig {
|
bintrayConfig {
|
||||||
// user = System.getenv('CORDA_BINTRAY_USER')
|
// user = System.getenv('CORDA_BINTRAY_USER')
|
||||||
// key = System.getenv('CORDA_BINTRAY_KEY')
|
// key = System.getenv('CORDA_BINTRAY_KEY')
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
gradlePluginsVersion=3.0.3
|
gradlePluginsVersion=3.0.3
|
||||||
kotlinVersion=1.1.60
|
kotlinVersion=1.1.60
|
||||||
platformVersion=1
|
platformVersion=3
|
||||||
guavaVersion=21.0
|
guavaVersion=21.0
|
||||||
bouncycastleVersion=1.57
|
bouncycastleVersion=1.57
|
||||||
typesafeConfigVersion=1.3.1
|
typesafeConfigVersion=1.3.1
|
||||||
|
@ -47,7 +47,8 @@ sealed class ByteSequence : Comparable<ByteSequence> {
|
|||||||
fun open() = ByteArrayInputStream(_bytes, offset, size)
|
fun open() = ByteArrayInputStream(_bytes, offset, size)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a sub-sequence backed by the same array.
|
* Create a sub-sequence of this sequence. A copy of the underlying array may be made, if a subclass overrides
|
||||||
|
* [bytes] to do so, as [OpaqueBytes] does.
|
||||||
*
|
*
|
||||||
* @param offset The offset within this sequence to start the new sequence. Note: not the offset within the backing array.
|
* @param offset The offset within this sequence to start the new sequence. Note: not the offset within the backing array.
|
||||||
* @param size The size of the intended sub sequence.
|
* @param size The size of the intended sub sequence.
|
||||||
@ -55,7 +56,8 @@ sealed class ByteSequence : Comparable<ByteSequence> {
|
|||||||
fun subSequence(offset: Int, size: Int): ByteSequence {
|
fun subSequence(offset: Int, size: Int): ByteSequence {
|
||||||
require(offset >= 0)
|
require(offset >= 0)
|
||||||
require(offset + size <= this.size)
|
require(offset + size <= this.size)
|
||||||
// Intentionally use bytes rather than _bytes, to mirror the copy-or-not behaviour of that property.
|
// Intentionally use bytes rather than _bytes, in case a subclass wants to prevent access to the original
|
||||||
|
// underlying array which could be revealed here (e.g. OpaqueBytes).
|
||||||
return if (offset == 0 && size == this.size) this else of(bytes, this.offset + offset, size)
|
return if (offset == 0 && size == this.size) this else of(bytes, this.offset + offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,3 @@
|
|||||||
{
|
{
|
||||||
"https://docs.corda.net/releases/release-M6.0": "M6.0",
|
"https://docs.corda.r3.com/releases/release-V3.0": "V3.0"
|
||||||
"https://docs.corda.net/releases/release-M7.0": "M7.0",
|
|
||||||
"https://docs.corda.net/releases/release-M8.2": "M8.2",
|
|
||||||
"https://docs.corda.net/releases/release-M9.2": "M9.2",
|
|
||||||
"https://docs.corda.net/releases/release-M10.1": "M10.1",
|
|
||||||
"https://docs.corda.net/releases/release-M11.2": "M11.2",
|
|
||||||
"https://docs.corda.net/releases/release-M12.1": "M12.1",
|
|
||||||
"https://docs.corda.net/releases/release-M13.0": "M13.0",
|
|
||||||
"https://docs.corda.net/releases/release-M14.0": "M14.0",
|
|
||||||
"https://docs.corda.net/releases/release-V1.0": "V1.0",
|
|
||||||
"https://docs.corda.net/releases/release-V2.0": "V2.0",
|
|
||||||
"https://docs.corda.net/head/": "Master"
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ API reference: <a href="api/kotlin/corda/index.html">Kotlin</a>/ <a href="api/ja
|
|||||||
{% block footer %}
|
{% block footer %}
|
||||||
<script>
|
<script>
|
||||||
// A synchronous request to retrieve all the Corda versions.
|
// A synchronous request to retrieve all the Corda versions.
|
||||||
$.getJSON("https://docs.corda.net/_static/versions", function(data) {
|
$.getJSON("https://docs.corda.r3.com/_static/versions", function(data) {
|
||||||
// Grab the current version.
|
// Grab the current version.
|
||||||
var version = $("#version").html();
|
var version = $("#version").html();
|
||||||
|
|
||||||
|
@ -145,6 +145,8 @@ which is then referenced within a custom flow:
|
|||||||
:start-after: DOCSTART TopupIssuer
|
:start-after: DOCSTART TopupIssuer
|
||||||
:end-before: DOCEND TopupIssuer
|
:end-before: DOCEND TopupIssuer
|
||||||
|
|
||||||
|
.. _database_migration_ref:
|
||||||
|
|
||||||
Database Migration
|
Database Migration
|
||||||
==================
|
==================
|
||||||
|
|
||||||
@ -232,7 +234,7 @@ Usage:
|
|||||||
Configurations:
|
Configurations:
|
||||||
|
|
||||||
- To enable migration at startup, set:
|
- To enable migration at startup, set:
|
||||||
- database.runMigration = true // false by default,
|
- ``database.runMigration = true`` // false by default,
|
||||||
|
|
||||||
Command line arguments:
|
Command line arguments:
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,8 @@ an ongoing stream of updates from the node. More detail on how to use this is pr
|
|||||||
|
|
||||||
For a brief tutorial on how one can use the RPC API see :doc:`tutorial-clientrpc-api`.
|
For a brief tutorial on how one can use the RPC API see :doc:`tutorial-clientrpc-api`.
|
||||||
|
|
||||||
|
.. _rpc_security_mgmt_ref:
|
||||||
|
|
||||||
RPC permissions
|
RPC permissions
|
||||||
---------------
|
---------------
|
||||||
If a node's owner needs to interact with their node via RPC (e.g. to read the contents of the node's storage), they
|
If a node's owner needs to interact with their node via RPC (e.g. to read the contents of the node's storage), they
|
||||||
@ -68,6 +70,8 @@ Fine grained permissions allow a user to invoke a specific RPC operation, or to
|
|||||||
- to invoke a RPC operation: ``InvokeRpc.<rpc method name>`` e.g., ``InvokeRpc.nodeInfo``.
|
- to invoke a RPC operation: ``InvokeRpc.<rpc method name>`` e.g., ``InvokeRpc.nodeInfo``.
|
||||||
.. note:: Permission ``InvokeRpc.startFlow`` allows a user to initiate all flows.
|
.. note:: Permission ``InvokeRpc.startFlow`` allows a user to initiate all flows.
|
||||||
|
|
||||||
|
.. _authentication_ref:
|
||||||
|
|
||||||
RPC security management
|
RPC security management
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
@ -54,11 +54,11 @@ author = u'R3 DLG'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = 'Master'
|
version = '3.0'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = 'Master'
|
release = 'R3 Corda V3.0 Developer Preview'
|
||||||
# The version for use in the dropdown html.
|
# The version for use in the dropdown html.
|
||||||
html_context = {'version': 'Master'}
|
html_context = {'version': 'V3.0'}
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
@ -68,28 +68,24 @@ path to the node's base directory.
|
|||||||
|
|
||||||
.. note:: Longer term these keys will be managed in secure hardware devices.
|
.. note:: Longer term these keys will be managed in secure hardware devices.
|
||||||
|
|
||||||
:database: Database configuration:
|
.. _database_properties_ref:
|
||||||
|
|
||||||
:transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in
|
:database: This section is used to configure JDBC and Hibernate related properties:
|
||||||
|
|
||||||
|
:transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in
|
||||||
``java.sql.Connection``, but without the "TRANSACTION_" prefix. Defaults to REPEATABLE_READ.
|
``java.sql.Connection``, but without the "TRANSACTION_" prefix. Defaults to REPEATABLE_READ.
|
||||||
:exportHibernateJMXStatistics: Whether to export Hibernate JMX statistics (caution: expensive run-time overhead)
|
|
||||||
|
:exportHibernateJMXStatistics: Whether to export Hibernate JMX statistics (caution: expensive run-time overhead)
|
||||||
|
|
||||||
|
:runMigration: Boolean on whether to run the database migration scripts. Defaults to true.
|
||||||
|
|
||||||
|
:schema: (optional) some database providers require a schema name when generating DDL and SQL statements.
|
||||||
|
(the value is passed to Hibernate property 'hibernate.default_schema').
|
||||||
|
|
||||||
:dataSourceProperties: This section is used to configure the jdbc connection and database driver used for the nodes persistence.
|
:dataSourceProperties: This section is used to configure the jdbc connection and database driver used for the nodes persistence.
|
||||||
Currently the defaults in ``/node/src/main/resources/reference.conf`` are as shown in the first example. This is currently
|
Currently the defaults in ``/node/src/main/resources/reference.conf`` are as shown in the first example. This is currently
|
||||||
the only configuration that has been tested, although in the future full support for other storage layers will be validated.
|
the only configuration that has been tested, although in the future full support for other storage layers will be validated.
|
||||||
|
|
||||||
:database: This section is used to configure JDBC and Hibernate related properties:
|
|
||||||
|
|
||||||
:transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in
|
|
||||||
``java.sql.Connection``, but without the "TRANSACTION_" prefix. Defaults to REPEATABLE_READ.
|
|
||||||
|
|
||||||
:exportHibernateJMXStatistics: Whether to export Hibernate JMX statistics (caution: expensive run-time overhead)
|
|
||||||
|
|
||||||
:runMigration: Boolean on whether to run the database migration scripts. Defaults to true.
|
|
||||||
|
|
||||||
:schema: (optional) some database providers require a schema name when generating DDL and SQL statements.
|
|
||||||
(the value is passed to Hibernate property 'hibernate.hbm2ddl.auto').
|
|
||||||
|
|
||||||
:messagingServerAddress: The address of the ArtemisMQ broker instance. If not provided the node will run one locally.
|
:messagingServerAddress: The address of the ArtemisMQ broker instance. If not provided the node will run one locally.
|
||||||
|
|
||||||
:p2pAddress: The host and port on which the node is available for protocol operations over ArtemisMQ.
|
:p2pAddress: The host and port on which the node is available for protocol operations over ArtemisMQ.
|
||||||
@ -196,5 +192,7 @@ path to the node's base directory.
|
|||||||
:exportJMXTo: If set to ``http``, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent.
|
:exportJMXTo: If set to ``http``, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent.
|
||||||
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
|
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
|
||||||
|
|
||||||
|
.. _config_amqp_bridge:
|
||||||
|
|
||||||
:useAMQPBridges: Optionally can be set to ``false`` to use Artemis CORE Bridges for peer-to-peer communications.
|
:useAMQPBridges: Optionally can be set to ``false`` to use Artemis CORE Bridges for peer-to-peer communications.
|
||||||
Otherwise, defaults to ``true`` and the AMQP 1.0 protocol will be used for message transfer between nodes.
|
Otherwise, defaults to ``true`` and the AMQP 1.0 protocol will be used for message transfer between nodes.
|
@ -28,14 +28,12 @@ Setting your dependencies
|
|||||||
|
|
||||||
Choosing your Corda version
|
Choosing your Corda version
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
``ext.corda_release_version`` and ``ext.corda_gradle_plugins_version`` are used in the ``build.gradle`` to define the
|
The following three lines of the ``build.gradle`` file define the Corda version and distribution used to build your CorDapp:
|
||||||
versions of Corda and the Corda Gradle Plugins that are used to build your CorDapp.
|
|
||||||
|
|
||||||
For example, to use version 1.0 of Corda and version 1.0 of the Corda gradle plugins, you'd write:
|
|
||||||
|
|
||||||
.. sourcecode:: groovy
|
.. sourcecode:: groovy
|
||||||
|
|
||||||
ext.corda_release_version = '1.0.0'
|
ext.corda_release_version = '1.0.0'
|
||||||
|
ext.corda_release_distribution = 'net.corda'
|
||||||
ext.corda_gradle_plugins_version = '1.0.0'
|
ext.corda_gradle_plugins_version = '1.0.0'
|
||||||
|
|
||||||
You can find the latest published version of both here: https://bintray.com/r3/corda.
|
You can find the latest published version of both here: https://bintray.com/r3/corda.
|
||||||
@ -94,21 +92,21 @@ is already correctly configured and this is for reference only;
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Corda integration dependencies
|
// Corda integration dependencies
|
||||||
cordaCompile "net.corda:corda-core:$corda_release_version"
|
cordaCompile "$corda_release_distribution:corda-core:$corda_release_version"
|
||||||
cordaCompile "net.corda:corda-finance:$corda_release_version"
|
cordaCompile "$corda_release_distribution:corda-finance:$corda_release_version"
|
||||||
cordaCompile "net.corda:corda-jackson:$corda_release_version"
|
cordaCompile "$corda_release_distribution:corda-jackson:$corda_release_version"
|
||||||
cordaCompile "net.corda:corda-rpc:$corda_release_version"
|
cordaCompile "$corda_release_distribution:corda-rpc:$corda_release_version"
|
||||||
cordaCompile "net.corda:corda-node-api:$corda_release_version"
|
cordaCompile "$corda_release_distribution:corda-node-api:$corda_release_version"
|
||||||
cordaCompile "net.corda:corda-webserver-impl:$corda_release_version"
|
cordaCompile "$corda_release_distribution:corda-webserver-impl:$corda_release_version"
|
||||||
cordaRuntime "net.corda:corda:$corda_release_version"
|
cordaRuntime "$corda_release_distribution:corda:$corda_release_version"
|
||||||
cordaRuntime "net.corda:corda-webserver:$corda_release_version"
|
cordaRuntime "$corda_release_distribution:corda-webserver:$corda_release_version"
|
||||||
testCompile "net.corda:corda-test-utils:$corda_release_version"
|
testCompile "$corda_release_distribution:corda-test-utils:$corda_release_version"
|
||||||
|
|
||||||
// Corda Plugins: dependent flows and services
|
// Corda Plugins: dependent flows and services
|
||||||
// Identifying a CorDapp by its module in the same project.
|
// Identifying a CorDapp by its module in the same project.
|
||||||
cordapp project(":cordapp-contracts-states")
|
cordapp project(":cordapp-contracts-states")
|
||||||
// Identifying a CorDapp by its fully-qualified name.
|
// Identifying a CorDapp by its fully-qualified name.
|
||||||
cordapp "net.corda:bank-of-corda-demo:1.0"
|
cordapp "$corda_release_distribution:bank-of-corda-demo:1.0"
|
||||||
|
|
||||||
// Some other dependencies
|
// Some other dependencies
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
@ -88,7 +88,7 @@ nodes. Here is an example ``Cordform`` task called ``deployNodes`` that creates
|
|||||||
// No webport property, so no webserver will be created.
|
// No webport property, so no webserver will be created.
|
||||||
h2Port 10004
|
h2Port 10004
|
||||||
// Includes the corda-finance CorDapp on our node.
|
// Includes the corda-finance CorDapp on our node.
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
cordapps = ["$corda_release_distribution:corda-finance:$corda_release_version"]
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "O=PartyA,L=London,C=GB"
|
name "O=PartyA,L=London,C=GB"
|
||||||
@ -96,7 +96,7 @@ nodes. Here is an example ``Cordform`` task called ``deployNodes`` that creates
|
|||||||
rpcPort 10006
|
rpcPort 10006
|
||||||
webPort 10007
|
webPort 10007
|
||||||
h2Port 10008
|
h2Port 10008
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
cordapps = ["$corda_release_distribution:corda-finance:$corda_release_version"]
|
||||||
// Grants user1 all RPC permissions.
|
// Grants user1 all RPC permissions.
|
||||||
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
|
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
|
||||||
}
|
}
|
||||||
@ -106,7 +106,7 @@ nodes. Here is an example ``Cordform`` task called ``deployNodes`` that creates
|
|||||||
rpcPort 10010
|
rpcPort 10010
|
||||||
webPort 10011
|
webPort 10011
|
||||||
h2Port 10012
|
h2Port 10012
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
cordapps = ["$corda_release_distribution:corda-finance:$corda_release_version"]
|
||||||
// Grants user1 the ability to start the MyFlow flow.
|
// Grants user1 the ability to start the MyFlow flow.
|
||||||
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["StartFlow.net.corda.flows.MyFlow"]]]
|
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["StartFlow.net.corda.flows.MyFlow"]]]
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,14 @@ service.
|
|||||||
notary = [validating : true]
|
notary = [validating : true]
|
||||||
p2pPort 10002
|
p2pPort 10002
|
||||||
rpcPort 10003
|
rpcPort 10003
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
cordapps = ["$corda_release_distribution:corda-finance:$corda_release_version"]
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "O=PartyA,L=London,C=GB"
|
name "O=PartyA,L=London,C=GB"
|
||||||
p2pPort 10005
|
p2pPort 10005
|
||||||
rpcPort 10006
|
rpcPort 10006
|
||||||
webPort 10007
|
webPort 10007
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
cordapps = ["$corda_release_distribution:corda-finance:$corda_release_version"]
|
||||||
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL]]]
|
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL]]]
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
@ -41,7 +41,7 @@ service.
|
|||||||
rpcPort 10009
|
rpcPort 10009
|
||||||
webPort 10010
|
webPort 10010
|
||||||
sshdPort 10024
|
sshdPort 10024
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
cordapps = ["$corda_release_distribution:corda-finance:$corda_release_version"]
|
||||||
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
|
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,84 +1,78 @@
|
|||||||
Network Map
|
Network Map
|
||||||
===========
|
===========
|
||||||
|
|
||||||
The network map stores a collection of ``NodeInfo`` objects, each representing another node with which the node can interact.
|
The network map is a collection of signed ``NodeInfo`` objects (signed by the node it represents and thus tamper-proof)
|
||||||
There are two sources from which a Corda node can retrieve ``NodeInfo`` objects:
|
forming the set of reachable nodes in a compatbility zone. A node can receive these objects from two sources:
|
||||||
|
|
||||||
1. the REST protocol with the network map service, which also provides a publishing API,
|
1. The HTTP network map service if the ``compatibilityZoneURL`` config key is specified.
|
||||||
|
2. The ``additional-node-infos`` directory within the node's directory.
|
||||||
|
|
||||||
2. the ``additional-node-infos`` directory.
|
HTTP network map service
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
If the node is configured with the ``compatibilityZoneURL`` config then it first uploads its own signed ``NodeInfo``
|
||||||
|
to the server (and each time it changes on startup) and then proceeds to download the entire network map. The network map
|
||||||
|
consists of a list of ``NodeInfo`` hashes. The node periodically polls for the network map (based on the HTTP cache expiry
|
||||||
|
header) and any new hash entries are downloaded and cached. Entries which no longer exist are deleted from the node's cache.
|
||||||
|
|
||||||
Protocol Design
|
The set of REST end-points for the network map service are as follows.
|
||||||
---------------
|
|
||||||
The node info publishing protocol:
|
|
||||||
|
|
||||||
* Create a ``NodeInfo`` object, and sign it to create a ``SignedNodeInfo`` object.
|
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||||
|
| Request method | Path | Description |
|
||||||
* Serialise the signed data and POST the data to the network map server.
|
+================+=========================================+==============================================================================================================================================+
|
||||||
|
| POST | /network-map/publish | For the node to upload its signed ``NodeInfo`` object to the network map. |
|
||||||
* The network map server validates the signature and acknowledges the registration with a HTTP 200 response, it will return HTTP 400 "Bad Request" if the data failed validation or if the public key wasn't registered with the network.
|
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||||
|
| GET | /network-map | Retrieve the current signed network map object. The entire object is signed with the network map certificate which is also attached. |
|
||||||
* The network map server will sign and distribute the new network map periodically.
|
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||||
|
| GET | /network-map/node-info/{hash} | Retrieve a signed ``NodeInfo`` as specified in the network map object. |
|
||||||
Node side network map update protocol:
|
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||||
|
| GET | /network-map/network-parameters/{hash} | Retrieve the signed network parameters (see below). The entire object is signed with the network map certificate which is also attached. |
|
||||||
* The Corda node will query the network map service periodically according to the ``Expires`` attribute in the HTTP header.
|
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
|
||||||
|
|
||||||
* The network map service returns a signed ``NetworkMap`` object which looks as follows:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
data class NetworkMap {
|
|
||||||
val nodeInfoHashes: List<SecureHash>,
|
|
||||||
val networkParametersHash: SecureHash
|
|
||||||
}
|
|
||||||
|
|
||||||
The object contains list of node info hashes and hash of the network parameters data structure (without the signatures).
|
|
||||||
|
|
||||||
* The node updates its local copy of ``NodeInfos`` if it is different from the newly downloaded ``NetworkMap``.
|
|
||||||
|
|
||||||
Network Map service REST API:
|
|
||||||
|
|
||||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
|
||||||
| Request method | Path | Description |
|
|
||||||
+================+===================================+========================================================================================================================================================+
|
|
||||||
| POST | /network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. |
|
|
||||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
|
||||||
| GET | /network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and ``NetworkParameters`` hash. |
|
|
||||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
|
||||||
| GET | /network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. |
|
|
||||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
|
||||||
| GET | /network-map/parameters/{hash} | Retrieve ``NetworkParameters`` object with the same hash. |
|
|
||||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
|
||||||
|
|
||||||
TODO: Access control of the network map will be added in the future.
|
|
||||||
|
|
||||||
|
|
||||||
The ``additional-node-infos`` directory
|
The ``additional-node-infos`` directory
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
Each Corda node reads, and continuously polls, the files contained in a directory named ``additional-node-infos`` inside the node base directory.
|
|
||||||
|
|
||||||
Nodes expect to find a serialized ``SignedNodeInfo`` object, the same object which is sent to network map server.
|
Alongside the HTTP network map service, or as a replacement if the node isn't connected to one, the node polls the
|
||||||
|
contents of the ``additional-node-infos`` directory located in its base directory. Each file is expected to be the same
|
||||||
Whenever a node starts it writes on disk a file containing its own ``NodeInfo``, this file is called ``nodeInfo-XXX`` where ``XXX`` is a long string.
|
signed ``NodeInfo`` object that the network map service vends. These are automtically added to the node's cache and can
|
||||||
|
be used to supplement or replace the HTTP network map. If the same node is advertised through both mechanisms then the
|
||||||
Hence if an operator wants node A to see node B they can pick B's ``NodeInfo`` file from B base directory and drop it into A's ``additional-node-infos`` directory.
|
latest one is taken.
|
||||||
|
|
||||||
|
On startup the node generates its own signed node info file, filename of the format ``nodeInfo-${hash}``. To create a simple
|
||||||
|
network without the HTTP network map service then simply place this file in the ``additional-node-infos`` directory
|
||||||
|
of every node that's part of this network.
|
||||||
|
|
||||||
Network parameters
|
Network parameters
|
||||||
------------------
|
------------------
|
||||||
Network parameters are constants that every node participating in the network needs to agree on and use for interop purposes.
|
|
||||||
The structure is distributed as a file containing serialized ``SignedData<NetworkParameters>`` with a signature from
|
|
||||||
a sub-key of the compatibility zone root cert. Network map advertises the hash of currently used network parameters.
|
|
||||||
The ``NetworkParameters`` structure contains:
|
|
||||||
* ``minimumPlatformVersion`` - minimum version of Corda platform that is required for nodes in the network.
|
|
||||||
* ``notaries`` - list of well known and trusted notary identities with information on validation type.
|
|
||||||
* ``maxMessageSize`` - maximum P2P message size sent over the wire in bytes.
|
|
||||||
* ``maxTransactionSize`` - maximum permitted transaction size in bytes.
|
|
||||||
* ``modifiedTime`` - the time the network parameters were created by the CZ operator.
|
|
||||||
* ``epoch`` - version number of the network parameters. Starting from 1, this will always increment on each new set of parameters.
|
|
||||||
|
|
||||||
The set of parameters is still under development and we may find the need to add additional fields.
|
Network parameters are a set of values that every node participating in the network needs to agree on and use to
|
||||||
|
correctly interoperate with each other. If the node is using the HTTP network map service then on first startup it will
|
||||||
|
download the signed network parameters, cache it in a ``network-parameters`` file and apply them on the node.
|
||||||
|
|
||||||
|
.. warning:: If the ``network-parameters`` file is changed and no longer matches what the network map service is advertising
|
||||||
|
then the node will automatically shutdown. Resolution to this is to delete the incorrect file and restart the node so
|
||||||
|
that the parameters can be downloaded again.
|
||||||
|
|
||||||
|
.. note:: A future release will support the notion of network parameters changes.
|
||||||
|
|
||||||
|
If the node isn't using a HTTP network map service then it's expected the signed file is provided by some other means.
|
||||||
|
For such a scenario there is the network bootstrapper tool which in addition to generating the network parameters file
|
||||||
|
also distributes the node info files to the node directories. More information can be found in :doc:`setting-up-a-corda-network`.
|
||||||
|
|
||||||
|
The current set of network parameters:
|
||||||
|
|
||||||
|
:minimumPlatformVersion: The minimum platform version that the nodes must be running. Any node which is below this will
|
||||||
|
not start.
|
||||||
|
:notaries: List of identity and validation type (either validating or non-validating) of the notaries which are permitted
|
||||||
|
in the compatibility zone.
|
||||||
|
:maxMessageSize: Maximum allowed P2P message size sent over the wire in bytes. Any message larger than this will be
|
||||||
|
split up.
|
||||||
|
:maxTransactionSize: Maximum permitted transaction size in bytes.
|
||||||
|
:modifiedTime: The time when the network parameters were last modified by the compatibility zone operator.
|
||||||
|
:epoch: Version number of the network parameters. Starting from 1, this will always increment whenever any of the
|
||||||
|
parameters change.
|
||||||
|
|
||||||
|
.. note:: ``maxTransactionSize`` is currently not enforced in the node, but will be in a later release.
|
||||||
|
|
||||||
|
More parameters may be added in future releases.
|
||||||
|
@ -73,6 +73,8 @@ Any database browsing tool that supports JDBC can be used, but if you have Intel
|
|||||||
a tool integrated with your IDE. Just open the database window and add an H2 data source with the above details.
|
a tool integrated with your IDE. Just open the database window and add an H2 data source with the above details.
|
||||||
You will now be able to browse the tables and row data within them.
|
You will now be able to browse the tables and row data within them.
|
||||||
|
|
||||||
|
.. _jolokia_ref:
|
||||||
|
|
||||||
Monitoring your node
|
Monitoring your node
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
@ -34,12 +34,14 @@ Standalone database
|
|||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
To run a node against a remote database modify node JDBC connection properties in `dataSourceProperties` entry
|
To run a node against a remote database modify node JDBC connection properties in `dataSourceProperties` entry
|
||||||
and Hibernate properties in `database` entry - see `:ref:`dataSourceProperties`.
|
and Hibernate properties in `database` entry - see :ref:`Node configuration <database_properties_ref>`.
|
||||||
|
|
||||||
|
.. _sql_server_ref:
|
||||||
|
|
||||||
SQL Azure and SQL Server
|
SQL Azure and SQL Server
|
||||||
````````````````````````
|
````````````````````````
|
||||||
Corda supports SQL Server 2017 (14.0.3006.16) and Azure SQL (12.0.2000.8).
|
Corda supports SQL Server 2017 (14.0.3006.16) and Azure SQL (12.0.2000.8).
|
||||||
The minimum transaction isolation level ``database.transactionIsolationLevel`` is 'readCommitted'.
|
The minimum transaction isolation level ``database.transactionIsolationLevel`` is 'READ_COMMITTED'.
|
||||||
The property ``database.schema`` is optional.
|
The property ``database.schema`` is optional.
|
||||||
Corda ships with Microsoft JDBC Driver 6.2 for SQLServer out-of-the-box.
|
Corda ships with Microsoft JDBC Driver 6.2 for SQLServer out-of-the-box.
|
||||||
|
|
||||||
@ -47,36 +49,40 @@ Example node configuration for SQL Azure:
|
|||||||
|
|
||||||
.. sourcecode:: none
|
.. sourcecode:: none
|
||||||
|
|
||||||
dataSourceProperties {
|
dataSourceProperties {
|
||||||
dataSourceClassName = "com.microsoft.sqlserver.jdbc.SQLServerDataSource"
|
dataSourceClassName = "com.microsoft.sqlserver.jdbc.SQLServerDataSource"
|
||||||
dataSourceProperties.dataSource.url = "jdbc:sqlserver://[DATABASE_SERVER].database.windows.net:1433;databaseName=[DATABASE];
|
dataSourceProperties.dataSource.url = "jdbc:sqlserver://[DATABASE_SERVER].database.windows.net:1433;databaseName=[DATABASE];
|
||||||
encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30"
|
encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30"
|
||||||
dataSourceProperties.dataSource.user = [USER]
|
dataSourceProperties.dataSource.user = [USER]
|
||||||
dataSourceProperties.dataSource.password = [PASSWORD]
|
dataSourceProperties.dataSource.password = [PASSWORD]
|
||||||
}
|
}
|
||||||
database {
|
database {
|
||||||
transactionIsolationLevel = "readCommitted"
|
transactionIsolationLevel = READ_COMMITTED
|
||||||
schema = [SCHEMA]
|
schema = [SCHEMA]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.. _postgres_ref:
|
||||||
|
|
||||||
PostgreSQL
|
PostgreSQL
|
||||||
````````````````````````
|
````````````````````````
|
||||||
Corda supports PostgreSQL 9.6 database.
|
|
||||||
The property ``database.schema`` is optional. The value of ``database.schema`` is automatically wrapped in double quotes
|
Corda supports PostgreSQL 9.6 database preliminarily.
|
||||||
to preserve case-sensitivity (e.g. `AliceCorp` becomes `"AliceCorp"`, without quotes PostgresSQL would treat the value as `alicecorp`).
|
The property ``database.schema`` is optional. Currently only lowercase value is supported,
|
||||||
|
please ensure your database setup uses lowercase schema names (Corda sends an unquoted schema name
|
||||||
|
and PostgresSQL interprets the value as a lowercase one e.g. `AliceCorp` becomes `alicecorp`).
|
||||||
Corda ships with PostgreSQL JDBC Driver 42.1.4 out-of-the-box.
|
Corda ships with PostgreSQL JDBC Driver 42.1.4 out-of-the-box.
|
||||||
|
|
||||||
Example node configuration for PostgreSQL:
|
Example node configuration for PostgreSQL:
|
||||||
|
|
||||||
.. sourcecode:: none
|
.. sourcecode:: none
|
||||||
|
|
||||||
dataSourceProperties = {
|
dataSourceProperties = {
|
||||||
dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource"
|
dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource"
|
||||||
dataSource.url = "jdbc:postgresql://[HOST]:[PORT]/postgres"
|
dataSource.url = "jdbc:postgresql://[HOST]:[PORT]/postgres"
|
||||||
dataSource.user = [USER]
|
dataSource.user = [USER]
|
||||||
dataSource.password = [PASSWORD]
|
dataSource.password = [PASSWORD]
|
||||||
}
|
}
|
||||||
database = {
|
database = {
|
||||||
transactionIsolationLevel = READ_COMMITTED
|
transactionIsolationLevel = READ_COMMITTED
|
||||||
schema = [SCHEMA]
|
schema = [SCHEMA]
|
||||||
}
|
}
|
@ -29,7 +29,7 @@ parties on the network or to exit cash (for itself only).
|
|||||||
|
|
||||||
When connected to a *Participant* node a user can only execute cash transaction commands to move cash to other parties on the network.
|
When connected to a *Participant* node a user can only execute cash transaction commands to move cash to other parties on the network.
|
||||||
|
|
||||||
The Demo Nodes can be started in one of two modes:
|
The Demo Nodes can be started in one of three modes:
|
||||||
|
|
||||||
1. Normal
|
1. Normal
|
||||||
|
|
||||||
@ -60,14 +60,30 @@ The Demo Nodes can be started in one of two modes:
|
|||||||
|
|
||||||
./gradlew tools:explorer:runSimulationNodes
|
./gradlew tools:explorer:runSimulationNodes
|
||||||
|
|
||||||
|
.. _flow_triage:
|
||||||
|
|
||||||
|
3. Flow triage
|
||||||
|
|
||||||
|
Once again, building on the demonstration Corda network topology described above, this scenario mode triggers
|
||||||
|
an exception within a flow which can then be visualized using the "Flow Triage" panel within the Explorer.
|
||||||
|
The "Flow Triage" panel will be enhanced in the future to enable operators to take corrective actions upon flow failures
|
||||||
|
(eg. retry, terminate, amend and replay)
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
|
||||||
|
gradlew.bat tools:explorer:runFlowTriageNodes
|
||||||
|
|
||||||
|
**Other:**
|
||||||
|
|
||||||
|
./gradlew tools:explorer:runFlowTriageNodes
|
||||||
|
|
||||||
.. note:: 5 Corda nodes will be created on the following port on localhost by default.
|
.. note:: 5 Corda nodes will be created on the following port on localhost by default.
|
||||||
|
|
||||||
* Notary -> 20003 (Does not accept logins)
|
* Notary -> 20001 (Does not accept logins)
|
||||||
* Alice -> 20006
|
* Alice -> 20004
|
||||||
* Bob -> 20009
|
* Bob -> 20007
|
||||||
* UK Bank Plc -> 20012 (*Issuer node*)
|
* UK Bank Plc -> 20010 (*Issuer node*)
|
||||||
* USA Bank Corp -> 20015 (*Issuer node*)
|
* USA Bank Corp -> 20013 (*Issuer node*)
|
||||||
|
|
||||||
Explorer login credentials to the Issuer nodes are defaulted to ``manager`` and ``test``.
|
Explorer login credentials to the Issuer nodes are defaulted to ``manager`` and ``test``.
|
||||||
Explorer login credentials to the Participants nodes are defaulted to ``user1`` and ``test``.
|
Explorer login credentials to the Participants nodes are defaulted to ``user1`` and ``test``.
|
||||||
|
@ -1,37 +1,164 @@
|
|||||||
Release notes
|
Release notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Here are release notes for each snapshot release from M9 onwards.
|
|
||||||
|
|
||||||
Unreleased
|
Unreleased
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
R3 Corda 3.0 Developer Preview
|
||||||
|
------------------------------
|
||||||
|
This Developer Preview takes us towards the launch of R3 Corda, R3's commercially supported enterprise blockchain platform.
|
||||||
|
|
||||||
|
Whilst the recent major releases of **Corda** ("Open Source") - V1.0 and V2.0 - have focused on providing API stability and
|
||||||
|
functionality, **R3 Corda** has been primarily focused on non-functional aspects of the platform: performance, scalability,
|
||||||
|
robustness, security, configurability, manageability, supportability and upgradeability.
|
||||||
|
|
||||||
|
Here is a summary of some of the key new features in this preview, many of which will also appear in the next open
|
||||||
|
source release:
|
||||||
|
|
||||||
|
- support for :ref:`Azure SQL and SQL Server 2017 <sql_server_ref>` databases.
|
||||||
|
- integrated :ref:`database schema management <database_migration_ref>` tooling using `Liquibase <http://www.liquibase.org/>`_
|
||||||
|
- completely re-designed :doc:`network-map` Service.
|
||||||
|
- enabled :ref:`AMQP serialization <amqp_ref>` for peer to peer messaging, and vault transaction storage.
|
||||||
|
- pluggable :ref:`user authentication <authentication_ref>` and :ref:`fine-grained access control <rpc_security_mgmt_ref>`.
|
||||||
|
- re-designed Flow Framework manager in preparation for fully multi-threaded implementation.
|
||||||
|
- improvements to the Doorman certificate issuance process (including integration with HSMs).
|
||||||
|
- additional JMX metrics exported via :ref:`Jolokia for monitoring <jolokia_ref>` and pro-active alert management.
|
||||||
|
- a secure, remotely accessible, :ref:`SSH server <ssh_server>` in the node with built-in authorization and permissioning to enable remote
|
||||||
|
node administration without requiring console access to the underlying operating system.
|
||||||
|
- re-architected and re-designed Corda bridge management for secure P2P connectivity between participants.
|
||||||
|
- enhanced Explorer tool with new :ref:`flow triage <flow_triage>` user interface panel to visualize all currently running flows.
|
||||||
|
- preliminary implementation of Business Networks concept (a private sub group within a Corda Compatibility Zone).
|
||||||
|
|
||||||
|
We also continue to make improvements in usability of our Test Framework APIs in preparation for declaring the test
|
||||||
|
framework API stable (in addition to the already-stabilised core APIs).
|
||||||
|
|
||||||
|
Significant changes implemented in reaching this Developer Preview include:
|
||||||
|
|
||||||
|
* **AMQP**:
|
||||||
|
AMQP Serialization is now enabled for both peer to peer communication and the writing of states to the vault. This
|
||||||
|
change brings a serialisation format that will allow us to deliver enhanced security and wire stability. It is a key
|
||||||
|
prerequisite to enabling different Corda node versions to coexist on the same network and to enable easier upgrades.
|
||||||
|
|
||||||
|
Details on the AMQP serialization framework can be found :ref:`here <amqp_ref>`. This provides an introduction and
|
||||||
|
overview of the framework whilst more specific details on object evolution as it relates to serialization is similarly
|
||||||
|
found in :doc:`serialization-default-evolution` and :doc:`serialization-enum-evolution` respectively.
|
||||||
|
|
||||||
|
.. note:: This release delivers the bulk of our transition from Kryo serialisation to AMQP serialisation. This means that many of the restrictions
|
||||||
|
that were documented in previous versions of Corda are now enforced.
|
||||||
|
|
||||||
|
In particular, you are advised to review the section titled :ref:`Custom Types <amqp_custom_types_ref>`.
|
||||||
|
To aid with the transition, we have included support in this release for default construction and instantiation of
|
||||||
|
objects with inaccessible private fields, but it is not guaranteed that this support will continue into future versions;
|
||||||
|
the restrictions documented at the link above are the canonical source.
|
||||||
|
|
||||||
|
* **New Network Map Service**:
|
||||||
|
This release introduces the new network map architecture. The network map service has been completely redesigned and
|
||||||
|
implemented to enable future increased network scalability and redundancy, reduced runtime operational overhead,
|
||||||
|
support for multiple notaries, and administration of network compatibility zones (CZ) and business networks.
|
||||||
|
|
||||||
|
A Corda Compatibility Zone (CZ) is defined as a grouping of participants and services (notaries, oracles,
|
||||||
|
doorman, network map server) configured within an operational Corda network to be interoperable and compatible with
|
||||||
|
each other.
|
||||||
|
|
||||||
|
We introduce the concept of network parameters, which will be used in a future version of Corda to specify precisely
|
||||||
|
the set of constants (or ranges of constants) upon which a set of nodes need to agree in order to be assured of seamless
|
||||||
|
inter-operation. Additional security controls ensure that all network map data is now signed, thus reducing the power
|
||||||
|
of the network operator to tamper with the map.
|
||||||
|
|
||||||
|
This release also adds Hardware Security Module (HSM) support to the doorman service (certificate authority).
|
||||||
|
By integrating with external HSMs, we have further strengthened the security of issuing network certificates and
|
||||||
|
signing of network map related data.
|
||||||
|
|
||||||
|
Further information can be found in the :doc:`changelog` and :doc:`network-map` documentation.
|
||||||
|
|
||||||
|
* **Third party database support**:
|
||||||
|
R3 Corda has been tested against Azure SQL and SQL Server 2017 databases (in addition to the existing default support
|
||||||
|
of H2 for development mode). This preview adds preliminary support for :ref:`PostgreSQL 9.6 <postgres_ref>`.
|
||||||
|
Support for Oracle 11g RC02 and Oracle 12c is currently under development. All required database settings can be
|
||||||
|
specified in the node configuration file. For configuration details see :doc:`node-database`.
|
||||||
|
|
||||||
|
* **Integrated database migration tooling**:
|
||||||
|
We have adopted and integrated `Liquibase <http://www.liquibase.org/>`_ , an open source database-independent library
|
||||||
|
for tracking, managing and applying database schema changes in order to ease the evolution (creation and migration) of
|
||||||
|
CorDapp custom contract schemas and facilitate the operational administration of a Corda nodes database.
|
||||||
|
We provide tooling to export DDL and data (as SQL statements) to a file to be inspected and/or manually applied by a DBA.
|
||||||
|
Please see :ref:`database migration <database_migration_ref>` for further details.
|
||||||
|
|
||||||
|
* **Pluggable user authentication and fine-grained access control**:
|
||||||
|
All RPC functions are now subject to permission checks (previously these only applied when starting flows).
|
||||||
|
We have also included experimental support for external user credentials data source and password encryption using the
|
||||||
|
`Apache Shiro <https://shiro.apache.org>`_ framework. Please see :ref:`RPC security management <rpc_security_mgmt_ref>` for further details.
|
||||||
|
|
||||||
|
* **Preliminary preview of new bridge management functionality**:
|
||||||
|
The bridge manager component is responsible for dynamically establishing remote connectivity with participant nodes
|
||||||
|
in a Corda peer to peer network. A new Bridge manager has been designed and implemented to be used integrally
|
||||||
|
within a :ref:`Corda node <config_amqp_bridge>` or deployed (in the final R3 Corda 3.0 release) as a standalone component in DMZ operational deployments,
|
||||||
|
where security concerns require separation of infrastructure messaging subsystems.
|
||||||
|
|
||||||
|
* **Preliminary preview of flow triage functionality**:
|
||||||
|
The explorer GUI was extended with a panel similar to the ``flow watch`` CRaSH shell command. It provides users with a view of all
|
||||||
|
flows currently executed on the node, with information about success/failure. The "Flow Triage" panel will be enhanced in the future
|
||||||
|
to enable operators to take corrective actions upon flow failures (eg. retry, terminate, amend and replay).
|
||||||
|
|
||||||
|
* **Experimental preview of a new operational Corda network grouping concept: Business Networks**:
|
||||||
|
Business Networks are introduced as a way to partition the global population of nodes (a Compatibility Zone) into
|
||||||
|
independent, possibly overlapping, groups. A Business Network operator (BNO) will have control over which nodes will
|
||||||
|
be admitted into a Business Network. Some nodes may choose not to register themselves in the global Network Map, and
|
||||||
|
will therefore remain invisible to nodes outside of their Business Network. Further documentation will be forthcoming
|
||||||
|
by the final R3 Corda 3.0 release.
|
||||||
|
|
||||||
|
See the "Business Network reference implementation" prototype example in the Explorer tool (instructions in README.md).
|
||||||
|
|
||||||
|
In addition to enhancements focused on non-functional capabilities, this release encompasses a number of functional
|
||||||
|
improvements, including:
|
||||||
|
|
||||||
|
* Doorman Service
|
||||||
|
In order to automate a node's network joining process, a new Doorman service has been introduced with this release.
|
||||||
|
The Doorman's main purpose is to restrict network access only to those nodes whose identity has been confirmed and their network joining request approved.
|
||||||
|
It issues node-level certificates which are then used by other nodes in the network to confirm a nodes identity and network permissions.
|
||||||
|
More information on Doorman and how to run it can be found in :doc:`running-doorman`.
|
||||||
|
|
||||||
|
* Hardware Security Module (HSM) for Doorman
|
||||||
|
To allow for increased security, R3 Corda introduces HSM integration. Doorman certificates (together with their keys)
|
||||||
|
can now be stored on secured hardware constraining the way those certificates are accessed. Any usage of those certificates
|
||||||
|
(e.g. data signing or node-level certificate generation) falls into a restrictive process that is automatically audited
|
||||||
|
and can be configured to involve human-in-the-loop in order to prevent unauthorised access. The HSM integration is embodied
|
||||||
|
in our new Signing Service. More on this in :doc:`signing-service`.
|
||||||
|
|
||||||
* X.509 certificates now have an extension that specifies the Corda role the certificate is used for, and the role
|
* X.509 certificates now have an extension that specifies the Corda role the certificate is used for, and the role
|
||||||
hierarchy is now enforced in the validation code. This only has impact on those developing integrations with external
|
hierarchy is now enforced in the validation code. This only has impact on those developing integrations with external
|
||||||
PKI solutions, in most cases it is managed transparently by Corda. A formal specification of the extension can be
|
PKI solutions. In most cases it is managed transparently by Corda. A formal specification of the extension can be
|
||||||
found at see :doc:`permissioning-certificate-specification`.
|
found at see :doc:`permissioning-certificate-specification`.
|
||||||
|
|
||||||
* **Enum Class Evolution**
|
* Custom Serializers
|
||||||
With the addition of AMQP serialization Corda now supports enum constant evolution.
|
To allow interop with third party libraries that cannot be recompiled we add functionality that allows custom serializers
|
||||||
|
to be written for those classes. If needed, a proxy object can be created as an interim step that allows Corda's internal
|
||||||
|
serializers to operate on those types. A good example of this is the SIMM valuation demo which has a number of such
|
||||||
|
serializers defined in the plugin/custom serializers package
|
||||||
|
|
||||||
That is the ability to alter an enum constant and, as long as certain rules are followed and the correct
|
Please refer to the :doc:`changelog` for detailed explanations of all new features.
|
||||||
annotations applied, have older and newer instances of that enumeration be understood.
|
|
||||||
|
|
||||||
* **AMQP Enabled**
|
Finally, please note that although this developer preview has not yet been security audited, it is currently being subjected
|
||||||
|
to a full external secure code review and penetration test.
|
||||||
|
|
||||||
AMQP Serialization is now enabled for both peer to peer communication and writing states to the vault. This change
|
As per previous major releases, we have provided a comprehensive upgrade notes (:doc:`upgrade-notes`) to ease the upgrade
|
||||||
brings a stable format Corda can support internally throughout it's lifetime that meets the needs of Corda and our
|
of CorDapps to R3 Corda 3.0 Developer Preview. In line with our commitment to API stability, code level changes
|
||||||
users.
|
are fairly minimal, and mostly related to improvements to our nearly API stable test framework.
|
||||||
|
|
||||||
* **Custom Serializers**
|
From a build perspective, switching CorDapps built using Corda (the "Open Source" code) to R3 Corda is mostly effortless,
|
||||||
|
and simply requires setting two gradle build file variables:
|
||||||
|
|
||||||
To allow interop with third party libraries that cannot be recompiled we add functionality that allows custom serializers
|
.. sourcecode:: shell
|
||||||
to be written for those classes. If needed, a proxy object can be created as an interim step that allows Corda's internal
|
|
||||||
serializers to operate on those types.
|
|
||||||
|
|
||||||
A good example of this is the SIMM valuation demo which has a number of such serializers defined in the plugin/customserializers package
|
ext.corda_release_version = 'R3.CORDA-3.0.0-DEV-PREVIEW'
|
||||||
|
ext.corda_release_distribution = 'com.r3.corda'
|
||||||
|
|
||||||
Release 2.0
|
Please note this release is distributed under license and should not be used in a Production environment yet.
|
||||||
----------
|
|
||||||
|
We look forward to hearing your feedback on this Developer Preview.
|
||||||
|
|
||||||
|
Corda 2.0
|
||||||
|
---------
|
||||||
Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates
|
Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates
|
||||||
a number of security updates for our dependent libraries alongside the reintroduction of the Observer node functionality.
|
a number of security updates for our dependent libraries alongside the reintroduction of the Observer node functionality.
|
||||||
This was absent from version 1 but based on user feedback its re-introduction removes the need for complicated "isRelevant()" checks.
|
This was absent from version 1 but based on user feedback its re-introduction removes the need for complicated "isRelevant()" checks.
|
||||||
@ -46,11 +173,11 @@ and via the versioning APIs.
|
|||||||
* **Observer Nodes**
|
* **Observer Nodes**
|
||||||
|
|
||||||
Adds the facility for transparent forwarding of transactions to some third party observer, such as a regulator. By having
|
Adds the facility for transparent forwarding of transactions to some third party observer, such as a regulator. By having
|
||||||
that entity simply run an Observer node they can simply recieve a stream of digitally signed, de-duplicated reports that
|
that entity simply run an Observer node they can simply receive a stream of digitally signed, de-duplicated reports that
|
||||||
can be used for reporting.
|
can be used for reporting.
|
||||||
|
|
||||||
Release 1.0
|
Corda 1.0
|
||||||
-----------
|
---------
|
||||||
Corda 1.0 is finally here!
|
Corda 1.0 is finally here!
|
||||||
|
|
||||||
This critical step in the Corda journey enables the developer community, clients, and partners to build on Corda with confidence.
|
This critical step in the Corda journey enables the developer community, clients, and partners to build on Corda with confidence.
|
||||||
@ -154,253 +281,3 @@ We have provided a comprehensive :doc:`upgrade-notes` to ease the transition of
|
|||||||
Upgrading to this release is strongly recommended, and you will be safe in the knowledge that core APIs will no longer break.
|
Upgrading to this release is strongly recommended, and you will be safe in the knowledge that core APIs will no longer break.
|
||||||
|
|
||||||
Thank you to all contributors for this release!
|
Thank you to all contributors for this release!
|
||||||
|
|
||||||
Milestone 14
|
|
||||||
------------
|
|
||||||
|
|
||||||
This release continues with the goal to improve API stability and developer friendliness. There have also been more
|
|
||||||
bug fixes and other improvements across the board.
|
|
||||||
|
|
||||||
The CorDapp template repository has been replaced with a specific repository for
|
|
||||||
`Java <https://github.com/corda/cordapp-template-java>`_ and `Kotlin <https://github.com/corda/cordapp-template-kotlin>`_
|
|
||||||
to improve the experience of starting a new project and to simplify the build system.
|
|
||||||
|
|
||||||
It is now possible to specify multiple IP addresses and legal identities for a single node, allowing node operators
|
|
||||||
more flexibility in setting up nodes.
|
|
||||||
|
|
||||||
A format has been introduced for CorDapp JARs that standardises the contents of CorDapps across nodes. This new format
|
|
||||||
now requires CorDapps to contain their own external dependencies. This paves the way for significantly improved
|
|
||||||
dependency management for CorDapps with the release of `Jigsaw (Java Modules) <http://openjdk.java.net/projects/jigsaw/>`_. For those using non-gradle build systems it is important
|
|
||||||
to read :doc:`cordapp-build-systems` to learn more. Those using our ``cordformation`` plugin simply need to update
|
|
||||||
to the latest version (``0.14.0``) to get the fixes.
|
|
||||||
|
|
||||||
We've now begun the process of demarcating which classes are part of our public API and which ones are internal.
|
|
||||||
Everything found in ``net.corda.core.internal`` and other packages in the ``net.corda`` namespace which has ``.internal`` in it are
|
|
||||||
considered internal and not for public use. In a future release any CorDapp using these packages will fail to load, and
|
|
||||||
when we migrate to Jigsaw these will not be exported.
|
|
||||||
|
|
||||||
The transaction finalisation flow (``FinalityFlow``) has had hooks added for alternative implementations, for example in
|
|
||||||
scenarios where no single participant in a transaction is aware of the well known identities of all parties.
|
|
||||||
|
|
||||||
DemoBench has a fix for a rare but inconvenient crash that can occur when sharing your display across multiple devices,
|
|
||||||
e.g. a projector while performing demonstrations in front of an audience.
|
|
||||||
|
|
||||||
Guava types are being removed because Guava does not have backwards compatibility across versions, which has serious
|
|
||||||
issues when multiple libraries depend on different versions of the library.
|
|
||||||
|
|
||||||
The identity service API has been tweaked, primarily so anonymous identity registration now takes in
|
|
||||||
AnonymousPartyAndPath rather than the individual components of the identity, as typically the caller will have
|
|
||||||
an AnonymousPartyAndPath instance. See change log for further detail.
|
|
||||||
|
|
||||||
Upgrading to this release is strongly recommended in order to keep up with the API changes, removal and additions.
|
|
||||||
|
|
||||||
Milestone 13
|
|
||||||
------------
|
|
||||||
|
|
||||||
Following our first public beta in M12, this release continues the work on API stability and user friendliness. Apart
|
|
||||||
from bug fixes and code refactoring, there are also significant improvements in the Vault Query and the
|
|
||||||
Identity Service (for more detailed information about what has changed, see :doc:`changelog`).
|
|
||||||
More specifically:
|
|
||||||
|
|
||||||
The long awaited new **Vault Query** service makes its debut in this release and provides advanced vault query
|
|
||||||
capabilities using criteria specifications (see ``QueryCriteria``), sorting, and pagination. Criteria specifications
|
|
||||||
enable selective filtering with and/or composition using multiple operator primitives on standard attributes stored in
|
|
||||||
Corda internal vault tables (eg. vault_states, vault_fungible_states, vault_linear_states), and also on custom contract
|
|
||||||
state schemas defined by CorDapp developers when modelling new contract types. Custom queries are specifiable using a
|
|
||||||
simple but sophisticated builder DSL (see ``QueryCriteriaUtils``). The new Vault Query service is usable by flows and by
|
|
||||||
RPC clients alike via two simple API functions: ``queryBy()`` and ``trackBy()``. The former provides point-in-time
|
|
||||||
snapshot queries whilst the later supplements the snapshot with dynamic streaming of updates.
|
|
||||||
See :doc:`api-vault-query` for full details.
|
|
||||||
|
|
||||||
We have written a comprehensive Hello, World! tutorial, showing developers how to build a CorDapp from start
|
|
||||||
to finish. The tutorial shows how the core elements of a CorDapp - states, contracts and flows - fit together
|
|
||||||
to allow your node to handle new business processes. It also explains how you can use our contract and
|
|
||||||
flow testing frameworks to massively reduce CorDapp development time.
|
|
||||||
|
|
||||||
Certificate checks have been enabled for much of the identity service. These are part of the confidential (anonymous)
|
|
||||||
identities work, and ensure that parties are actually who they claim to be by checking their certificate path back to
|
|
||||||
the network trust root (certificate authority).
|
|
||||||
|
|
||||||
To deal with anonymized keys, we've also implemented a deterministic key derivation function that combines logic
|
|
||||||
from the HMAC-based Extract-and-Expand Key Derivation Function (HKDF) protocol and the BIP32 hardened
|
|
||||||
parent-private-key -> child-private-key scheme. This function currently supports the following algorithms:
|
|
||||||
ECDSA secp256K1, ECDSA secpR1 (NIST P-256) and EdDSA ed25519. We are now very close to fully supporting anonymous
|
|
||||||
identities so as to increase privacy even against validating notaries.
|
|
||||||
|
|
||||||
We have further tightened the set of objects which Corda will attempt to serialise from the stack during flow
|
|
||||||
checkpointing. As flows are arbitrary code in which it is convenient to do many things, we ended up pulling in a lot of
|
|
||||||
objects that didn't make sense to put in a checkpoint, such as ``Thread`` and ``Connection``. To minimize serialization
|
|
||||||
cost and increase security by not allowing certain classes to be serialized, we now support class blacklisting
|
|
||||||
that will return an ``IllegalStateException`` if such a class is encountered during a checkpoint. Blacklisting supports
|
|
||||||
superclass and superinterface inheritance and always precedes ``@CordaSerializable`` annotation checking.
|
|
||||||
|
|
||||||
We've also started working on improving user experience when searching, by adding a new RPC to support fuzzy matching
|
|
||||||
of X.500 names.
|
|
||||||
|
|
||||||
Milestone 12 - First Public Beta
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
One of our busiest releases, lots of changes that take us closer to API stability (for more detailed information about
|
|
||||||
what has changed, see :doc:`changelog`). In this release we focused mainly on making developers' lives easier. Taking
|
|
||||||
into account feedback from numerous training courses and meet-ups, we decided to add ``CollectSignaturesFlow`` which
|
|
||||||
factors out a lot of code which CorDapp developers needed to write to get their transactions signed.
|
|
||||||
The improvement is up to 150 fewer lines of code in each flow! To have your transaction signed by different parties, you
|
|
||||||
need only now call a subflow which collects the parties' signatures for you.
|
|
||||||
|
|
||||||
Additionally we introduced classpath scanning to wire-up flows automatically. Writing CorDapps has been made simpler by
|
|
||||||
removing boiler-plate code that was previously required when registering flows. Writing services such as oracles has also been simplified.
|
|
||||||
|
|
||||||
We made substantial RPC performance improvements (please note that this is separate to node performance, we are focusing
|
|
||||||
on that area in future milestones):
|
|
||||||
|
|
||||||
- 15-30k requests per second for a single client/server RPC connection.
|
|
||||||
* 1Kb requests, 1Kb responses, server and client on same machine, parallelism 8, measured on a Dell XPS 17(i7-6700HQ, 16Gb RAM)
|
|
||||||
- The framework is now multithreaded on both client and server side.
|
|
||||||
- All remaining bottlenecks are in the messaging layer.
|
|
||||||
|
|
||||||
Security of the key management service has been improved by removing support for extracting private keys, in order that
|
|
||||||
it can support use of a hardware security module (HSM) for key storage. Instead it exposes functionality for signing data
|
|
||||||
(typically transactions). The service now also supports multiple signature schemes (not just EdDSA).
|
|
||||||
|
|
||||||
We've added the beginnings of flow versioning. Nodes now reject flow requests if the initiating side is not using the same
|
|
||||||
flow version. In a future milestone release will add the ability to support backwards compatibility.
|
|
||||||
|
|
||||||
As with the previous few releases we have continued work extending identity support. There are major changes to the ``Party``
|
|
||||||
class as part of confidential identities, and how parties and keys are stored in transaction state objects.
|
|
||||||
See :doc:`changelog` for full details.
|
|
||||||
|
|
||||||
Added new Byzantine fault tolerant (BFT) decentralised notary demo, based on the `BFT-SMaRT protocol <https://bft-smart.github.io/library/>`_
|
|
||||||
For how to run the demo see: :ref:`notary-demo`
|
|
||||||
|
|
||||||
We continued to work on tools that enable diagnostics on the node. The newest addition to Corda Shell is ``flow watch`` command which
|
|
||||||
lets the administrator see all flows currently running with result or error information as well as who is the flow initiator.
|
|
||||||
Here is the view from DemoBench:
|
|
||||||
|
|
||||||
.. image:: resources/flowWatchCmd.png
|
|
||||||
|
|
||||||
We also started work on the strategic wire format (not integrated).
|
|
||||||
|
|
||||||
Milestone 11
|
|
||||||
------------
|
|
||||||
|
|
||||||
Special thank you to `Gary Rowe <https://github.com/gary-rowe>`_ for his contribution to Corda's Contracts DSL in M11.
|
|
||||||
|
|
||||||
Work has continued on confidential identities, introducing code to enable the Java standard libraries to work with
|
|
||||||
composite key signatures. This will form the underlying basis of future work to standardise the public key and signature
|
|
||||||
formats to enable interoperability with other systems, as well as enabling the use of composite signatures on X.509
|
|
||||||
certificates to prove association between transaction keys and identity keys.
|
|
||||||
|
|
||||||
The identity work will require changes to existing code and configurations, to replace party names with full X.500
|
|
||||||
distinguished names (see RFC 1779 for details on the construction of distinguished names). Currently this is not
|
|
||||||
enforced, however it will be in a later milestone.
|
|
||||||
|
|
||||||
* "myLegalName" in node configurations will need to be replaced, for example "Bank A" is replaced with
|
|
||||||
"CN=Bank A,O=Bank A,L=London,C=GB". Obviously organisation, location and country ("O", "L" and "C" respectively)
|
|
||||||
must be given values which are appropriate to the node, do not just use these example values.
|
|
||||||
* "networkMap" in node configurations must be updated to match any change to the legal name of the network map.
|
|
||||||
* If you are using mock parties for testing, try to standardise on the ``DUMMY_NOTARY``, ``DUMMY_BANK_A``, etc. provided
|
|
||||||
in order to ensure consistency.
|
|
||||||
|
|
||||||
We anticipate enforcing the use of distinguished names in node configurations from M12, and across the network from M13.
|
|
||||||
|
|
||||||
We have increased the maximum message size that we can send to Corda over RPC from 100 KB to 10 MB.
|
|
||||||
|
|
||||||
The Corda node now disables any use of ObjectInputStream to prevent Java deserialisation within flows. This is a security fix,
|
|
||||||
and prevents the node from deserialising arbitrary objects.
|
|
||||||
|
|
||||||
We've introduced the concept of platform version which is a single integer value which increments by 1 if a release changes
|
|
||||||
any of the public APIs of the entire Corda platform. This includes the node's public APIs, the messaging protocol,
|
|
||||||
serialisation, etc. The node exposes the platform version it's on and we envision CorDapps will use this to be able to
|
|
||||||
run on older versions of the platform to the one they were compiled against. Platform version borrows heavily from Android's
|
|
||||||
API Level.
|
|
||||||
|
|
||||||
We have revamped the DemoBench user interface. DemoBench will now also be installed as "Corda DemoBench" for both Windows
|
|
||||||
and MacOSX. The original version was installed as just "DemoBench", and so will not be overwritten automatically by the
|
|
||||||
new version.
|
|
||||||
|
|
||||||
Milestone 10
|
|
||||||
------------
|
|
||||||
|
|
||||||
Special thank you to `Qian Hong <https://github.com/fracting>`_, `Marek Skocovsky <https://github.com/marekdapps>`_,
|
|
||||||
`Karel Hajek <https://github.com/polybioz>`_, and `Jonny Chiu <https://github.com/johnnyychiu>`_ for their contributions
|
|
||||||
to Corda in M10.
|
|
||||||
|
|
||||||
A new interactive **Corda Shell** has been added to the node. The shell lets developers and node administrators
|
|
||||||
easily command the node by running flows, RPCs and SQL queries. It also provides a variety of commands to monitor
|
|
||||||
the node. The Corda Shell is based on the popular `CRaSH project <http://www.crashub.org/>`_ and new commands can
|
|
||||||
be easily added to the node by simply dropping Groovy or Java files into the node's ``shell-commands`` directory.
|
|
||||||
We have many enhancements planned over time including SSH access, more commands and better tab completion.
|
|
||||||
|
|
||||||
The new "DemoBench" makes it easy to configure and launch local Corda nodes. It is a standalone desktop app that can be
|
|
||||||
bundled with its own JRE and packaged as either EXE (Windows), DMG (MacOS) or RPM (Linux-based). It has the following
|
|
||||||
features:
|
|
||||||
|
|
||||||
#. New nodes can be added at the click of a button. Clicking "Add node" creates a new tab that lets you edit the most
|
|
||||||
important configuration properties of the node before launch, such as its legal name and which CorDapps will be loaded.
|
|
||||||
#. Each tab contains a terminal emulator, attached to the pseudoterminal of the node. This lets you see console output.
|
|
||||||
#. You can launch an Corda Explorer instance for each node at the click of a button. Credentials are handed to the Corda
|
|
||||||
Explorer so it starts out logged in already.
|
|
||||||
#. Some basic statistics are shown about each node, informed via the RPC connection.
|
|
||||||
#. Another button launches a database viewer in the system browser.
|
|
||||||
#. The configurations of all running nodes can be saved into a single ``.profile`` file that can be reloaded later.
|
|
||||||
|
|
||||||
Soft Locking is a new feature implemented in the vault to prevent a node constructing transactions that attempt to use the
|
|
||||||
same input(s) simultaneously. Such transactions would result in naturally wasted effort when the notary rejects them as
|
|
||||||
double spend attempts. Soft locks are automatically applied to coin selection (eg. cash spending) to ensure that no two
|
|
||||||
transactions attempt to spend the same fungible states.
|
|
||||||
|
|
||||||
The basic Amount API has been upgraded to have support for advanced financial use cases and to better integrate with
|
|
||||||
currency reference data.
|
|
||||||
|
|
||||||
We have added optional out-of-process transaction verification. Any number of external verifier processes may be attached
|
|
||||||
to the node which can handle loadbalanced verification requests.
|
|
||||||
|
|
||||||
We have also delivered the long waited Kotlin 1.1 upgrade in M10! The new features in Kotlin allow us to write even more
|
|
||||||
clean and easy to manage code, which greatly increases our productivity.
|
|
||||||
|
|
||||||
This release contains a large number of improvements, new features, library upgrades and bug fixes. For a full list of
|
|
||||||
changes please see :doc:`changelog`.
|
|
||||||
|
|
||||||
Milestone 9
|
|
||||||
-----------
|
|
||||||
|
|
||||||
This release focuses on improvements to resiliency of the core infrastructure, with highlights including a Byzantine
|
|
||||||
fault tolerant (BFT) decentralised notary, based on the BFT-SMaRT protocol and isolating the web server from the
|
|
||||||
Corda node.
|
|
||||||
|
|
||||||
With thanks to open source contributor Thomas Schroeter for providing the BFT notary prototype, Corda can now resist
|
|
||||||
malicious attacks by members of a distributed notary service. If your notary service cluster has seven members, two can
|
|
||||||
become hacked or malicious simultaneously and the system continues unaffected! This work is still in development stage,
|
|
||||||
and more features are coming in the next snapshot!
|
|
||||||
|
|
||||||
The web server has been split out of the Corda node as part of our ongoing hardening of the node. We now provide a Jetty
|
|
||||||
servlet container pre-configured to contact a Corda node as a backend service out of the box, which means individual
|
|
||||||
webapps can have their REST APIs configured for the specific security environment of that app without affecting the
|
|
||||||
others, and without exposing the sensitive core of the node to malicious Javascript.
|
|
||||||
|
|
||||||
We have launched a global training programme, with two days of classes from the R3 team being hosted in London, New York
|
|
||||||
and Singapore. R3 members get 5 free places and seats are going fast, so sign up today.
|
|
||||||
|
|
||||||
We've started on support for confidential identities, based on the key randomisation techniques pioneered by the Bitcoin
|
|
||||||
and Ethereum communities. Identities may be either anonymous when a transaction is a part of a chain of custody, or fully
|
|
||||||
legally verified when a transaction is with a counterparty. Type safety is used to ensure the verification level of a
|
|
||||||
party is always clear and avoid mistakes. Future work will add support for generating new identity keys and providing a
|
|
||||||
certificate path to show ownership by the well known identity.
|
|
||||||
|
|
||||||
There are even more privacy improvements when a non-validating notary is used; the Merkle tree algorithm is used to hide
|
|
||||||
parts of the transaction that a non-validating notary doesn't need to see, whilst still allowing the decentralised
|
|
||||||
notary service to sign the entire transaction.
|
|
||||||
|
|
||||||
The serialisation API has been simplified and improved. Developers now only need to tag types that will be placed in
|
|
||||||
smart contracts or sent between parties with a single annotation... and sometimes even that isn't necessary!
|
|
||||||
|
|
||||||
Better permissioning in the cash CorDapp, to allow node users to be granted different permissions depending on whether
|
|
||||||
they manage the issuance, movement or ledger exit of cash tokens.
|
|
||||||
|
|
||||||
We've continued to improve error handling in flows, with information about errors being fed through to observing RPC
|
|
||||||
clients.
|
|
||||||
|
|
||||||
There have also been dozens of bug fixes, performance improvements and usability tweaks. Upgrading is definitely
|
|
||||||
worthwhile and will only take a few minutes for most apps.
|
|
||||||
|
|
||||||
For a full list of changes please see :doc:`changelog`.
|
|
||||||
|
@ -64,3 +64,13 @@ To enable remote debugging of the node, run the following from the terminal wind
|
|||||||
``java -Dcapsule.jvm.args="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" -jar corda.jar``
|
``java -Dcapsule.jvm.args="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" -jar corda.jar``
|
||||||
|
|
||||||
This command line will start the debugger on port 5005 and pause the process awaiting debugger attachment.
|
This command line will start the debugger on port 5005 and pause the process awaiting debugger attachment.
|
||||||
|
|
||||||
|
Starting a node with JMX monitoring enabled
|
||||||
|
-------------------------------------------
|
||||||
|
To enable export of JMX metrics over HTTP via `Jolokia <https://jolokia.org/>`_, run the following from the terminal window:
|
||||||
|
|
||||||
|
``java -Dcapsule.jvm.args="-javaagent:drivers/jolokia-jvm-1.3.7-agent.jar=port=7005" -jar corda.jar``
|
||||||
|
|
||||||
|
This command line will start the node with JMX metrics accessible via HTTP on port 7005.
|
||||||
|
|
||||||
|
See :ref:`Monitoring your node <jolokia_ref>` for further details.
|
@ -37,7 +37,7 @@ Allowed parameters are:
|
|||||||
|
|
||||||
:databaseProperties: database properties
|
:databaseProperties: database properties
|
||||||
|
|
||||||
:dataSourceProperties: datasoruce properties
|
:dataSourceProperties: datasource properties
|
||||||
|
|
||||||
:jiraConfig: The Jira configuration
|
:jiraConfig: The Jira configuration
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
Object serialization
|
Object serialization
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
|
||||||
What is serialization (and deserialization)?
|
What is serialization (and deserialization)?
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
@ -44,6 +46,8 @@ It's reproduced here as an example of both ways you can do this for a couple of
|
|||||||
.. warning:: We will be replacing the use of Kryo in the serialization framework and so additional changes here are
|
.. warning:: We will be replacing the use of Kryo in the serialization framework and so additional changes here are
|
||||||
likely.
|
likely.
|
||||||
|
|
||||||
|
.. _amqp_ref:
|
||||||
|
|
||||||
AMQP
|
AMQP
|
||||||
====
|
====
|
||||||
|
|
||||||
@ -76,7 +80,7 @@ Finally, for the checkpointing of flows Corda will continue to use the existing
|
|||||||
|
|
||||||
This separation of serialization schemes into different contexts allows us to use the most suitable framework for that context rather than
|
This separation of serialization schemes into different contexts allows us to use the most suitable framework for that context rather than
|
||||||
attempting to force a one size fits all approach. Where ``Kryo`` is more suited to the serialization of a programs stack frames, being more flexible
|
attempting to force a one size fits all approach. Where ``Kryo`` is more suited to the serialization of a programs stack frames, being more flexible
|
||||||
than our AMQP framework in what it can construct and serialize, that flexibility makes it exceptionally difficult to make secure. Conversly
|
than our AMQP framework in what it can construct and serialize, that flexibility makes it exceptionally difficult to make secure. Conversely
|
||||||
our AMQP framework allows us to concentrate on a robust a secure framework that can be reasoned about thus made safer with far fewer unforeseen
|
our AMQP framework allows us to concentrate on a robust a secure framework that can be reasoned about thus made safer with far fewer unforeseen
|
||||||
security holes.
|
security holes.
|
||||||
|
|
||||||
@ -218,6 +222,8 @@ checked exceptions, or ``CordaRuntimeException``, for unchecked exceptions. Any
|
|||||||
not conform to ``CordaThrowable`` will be converted to a ``CordaRuntimeException`` with the original exception type
|
not conform to ``CordaThrowable`` will be converted to a ``CordaRuntimeException`` with the original exception type
|
||||||
and other properties retained within it.
|
and other properties retained within it.
|
||||||
|
|
||||||
|
.. _amqp_custom_types_ref:
|
||||||
|
|
||||||
Custom Types
|
Custom Types
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@ -282,22 +288,6 @@ serialised form
|
|||||||
|
|
||||||
val e2 = e.serialize().deserialize() // e2.c will be 20, not 100!!!
|
val e2 = e.serialize().deserialize() // e2.c will be 20, not 100!!!
|
||||||
|
|
||||||
.. warning:: Private properties in Kotlin classes render the class unserializable *unless* a public
|
|
||||||
getter is manually defined. For example:
|
|
||||||
|
|
||||||
.. container:: codeset
|
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
|
||||||
|
|
||||||
data class C(val a: Int, private val b: Int) {
|
|
||||||
// Without this C cannot be serialized
|
|
||||||
public fun getB() = b
|
|
||||||
}
|
|
||||||
|
|
||||||
.. note:: This is particularly relevant as IDE's can often point out where they believe a
|
|
||||||
property can be made private without knowing this can break Corda serialization. Should
|
|
||||||
this happen then a run time warning will be generated when the class fails to serialize
|
|
||||||
|
|
||||||
Setter Instantiation
|
Setter Instantiation
|
||||||
''''''''''''''''''''
|
''''''''''''''''''''
|
||||||
|
|
||||||
@ -330,6 +320,75 @@ For example:
|
|||||||
public void setC(int c) { this.c = c; }
|
public void setC(int c) { this.c = c; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Inaccessible Private Properties
|
||||||
|
```````````````````````````````
|
||||||
|
|
||||||
|
Whilst the Corda AMQP serialization framework supports private object properties without publicly
|
||||||
|
accessible getter methods this development idiom is strongly discouraged.
|
||||||
|
|
||||||
|
For example.
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
Kotlin:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
data class C(val a: Int, private val b: Int)
|
||||||
|
|
||||||
|
Java:
|
||||||
|
|
||||||
|
.. sourcecode:: Java
|
||||||
|
|
||||||
|
class C {
|
||||||
|
public Integer a;
|
||||||
|
private Integer b;
|
||||||
|
|
||||||
|
C(Integer a, Integer b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
When designing stateful objects is should be remembered that they are not, despite appearances, traditional
|
||||||
|
programmatic constructs. They are signed over, transformed, serialised, and relationally mapped. As such,
|
||||||
|
all elements should be publicly accessible by design
|
||||||
|
|
||||||
|
.. warning:: IDEs will indiciate erroneously that properties can be given something other than public
|
||||||
|
visibility. Ignore this as whilst it will work, as discussed above there are many reasons why this isn't
|
||||||
|
a good idea and those are beyond the scope of the IDEs inference rules
|
||||||
|
|
||||||
|
Providing a public getter, as per the following example, is acceptable
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
Kotlin:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
data class C(val a: Int, private val b: Int) {
|
||||||
|
public fun getB() = b
|
||||||
|
}
|
||||||
|
|
||||||
|
Java:
|
||||||
|
|
||||||
|
.. sourcecode:: Java
|
||||||
|
|
||||||
|
class C {
|
||||||
|
public Integer a;
|
||||||
|
private Integer b;
|
||||||
|
|
||||||
|
C(Integer a, Integer b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getB() {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Enums
|
Enums
|
||||||
`````
|
`````
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@ The shell via the local terminal
|
|||||||
In development mode, the shell will display in the node's terminal window. It may be disabled by passing the
|
In development mode, the shell will display in the node's terminal window. It may be disabled by passing the
|
||||||
``--no-local-shell`` flag when running the node.
|
``--no-local-shell`` flag when running the node.
|
||||||
|
|
||||||
|
.. _ssh_server:
|
||||||
|
|
||||||
The shell via SSH
|
The shell via SSH
|
||||||
-----------------
|
-----------------
|
||||||
The shell is also accessible via SSH.
|
The shell is also accessible via SSH.
|
||||||
|
@ -109,15 +109,10 @@ See more on plugins in :doc:`running-a-node`.
|
|||||||
Security
|
Security
|
||||||
--------
|
--------
|
||||||
RPC credentials associated with a Client must match the permission set configured on the server node.
|
RPC credentials associated with a Client must match the permission set configured on the server node.
|
||||||
This refers to both authentication (username and password) and role-based authorisation (a permissioned set of RPC operations an
|
This refers to both authentication (username and password) and authorisation (a permissioned set of RPC operations an
|
||||||
authenticated user is entitled to run).
|
authenticated user is entitled to run).
|
||||||
|
|
||||||
.. note:: Permissions are represented as *String's* to allow RPC implementations to add their own permissioning. Currently
|
In the instructions below the server node permissions are configured programmatically in the driver code:
|
||||||
the only permission type defined is *StartFlow*, which defines a list of whitelisted flows an authenticated use may
|
|
||||||
execute. An administrator user (or a developer) may also be assigned the ``ALL`` permission, which grants access to
|
|
||||||
any flow.
|
|
||||||
|
|
||||||
In the instructions above the server node permissions are configured programmatically in the driver code:
|
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
@ -125,7 +120,9 @@ In the instructions above the server node permissions are configured programmati
|
|||||||
val user = User("user", "password", permissions = setOf(startFlow<CashFlow>()))
|
val user = User("user", "password", permissions = setOf(startFlow<CashFlow>()))
|
||||||
val node = startNode("CN=Alice Corp,O=Alice Corp,L=London,C=GB", rpcUsers = listOf(user)).get()
|
val node = startNode("CN=Alice Corp,O=Alice Corp,L=London,C=GB", rpcUsers = listOf(user)).get()
|
||||||
|
|
||||||
When starting a standalone node using a configuration file we must supply the RPC credentials as follows:
|
When starting a standalone node using a configuration file we must supply the RPC credentials in the node configuration,
|
||||||
|
like illustrated in the sample configuration below, or indicate an external database storing user accounts (see
|
||||||
|
:doc:`clientrpc` for more details):
|
||||||
|
|
||||||
.. code-block:: text
|
.. code-block:: text
|
||||||
|
|
||||||
@ -133,6 +130,7 @@ When starting a standalone node using a configuration file we must supply the RP
|
|||||||
{ username=user, password=password, permissions=[ StartFlow.net.corda.finance.flows.CashFlow ] }
|
{ username=user, password=password, permissions=[ StartFlow.net.corda.finance.flows.CashFlow ] }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
When using the gradle Cordformation plugin to configure and deploy a node you must supply the RPC credentials in a similar
|
When using the gradle Cordformation plugin to configure and deploy a node you must supply the RPC credentials in a similar
|
||||||
manner:
|
manner:
|
||||||
|
|
||||||
|
@ -9,38 +9,369 @@ 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. For example, Corda V1.0 uses:
|
||||||
|
|
||||||
.. sourcecode:: shell
|
.. sourcecode:: shell
|
||||||
|
|
||||||
ext.corda_release_version = '1.0.0'
|
ext.corda_release_version = '1.0.0'
|
||||||
|
ext.corda_release_distribution = 'net.corda'
|
||||||
ext.corda_gradle_plugins_version = '1.0.0'
|
ext.corda_gradle_plugins_version = '1.0.0'
|
||||||
|
|
||||||
It may be necessary to update the version of major dependencies:
|
It may be necessary to update the version of major dependencies. Corda V1.0 uses:
|
||||||
|
|
||||||
.. sourcecode:: shell
|
.. sourcecode:: shell
|
||||||
|
|
||||||
ext.kotlin_version = '1.1.4'
|
ext.kotlin_version = '1.1.4'
|
||||||
ext.quasar_version = '0.7.9'
|
ext.quasar_version = '0.7.9'
|
||||||
|
ext.junit_version = '4.12'
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
UNRELEASED
|
V1.0 and V2.0 to R3 Corda V3.0 Developer Preview
|
||||||
----------
|
------------------------------------------------
|
||||||
|
|
||||||
|
Build
|
||||||
|
^^^^^
|
||||||
|
|
||||||
|
* Update the version identifiers in your project gradle file(s):
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
ext.corda_release_version = 'R3.CORDA-3.0.0-DEV-PREVIEW' // "CORDA-3.0.0-DEV-PREVIEW" (Open Source)
|
||||||
|
ext.corda_gradle_plugins_version = '3.0.3'
|
||||||
|
|
||||||
|
* Add a new release identifier to specify the R3 release of corda:
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
ext.corda_release_distribution = 'com.r3.corda' // "net.corda" for Corda (Open Source)
|
||||||
|
|
||||||
|
* Add an additional repository entry to point to the location of the R3 Corda distribution:
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
credentials {
|
||||||
|
username "r3-corda-dev-preview"
|
||||||
|
password "XXXXX"
|
||||||
|
}
|
||||||
|
url 'https://ci-artifactory.corda.r3cev.com/artifactory/r3-corda-releases'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* Corda plugins have been modularised further so the following additional gradle entries are necessary:
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'net.corda.plugins.cordapp'
|
||||||
|
|
||||||
|
The plugin needs to be applied in all gradle build files where there is a dependency on Corda using any of:
|
||||||
|
cordaCompile, cordaRuntime, cordapp
|
||||||
|
|
||||||
|
* Corda Gradle plugins require Gradle version 4.1 or above
|
||||||
|
|
||||||
|
* All gradle compile, test, and run-time dependencies (except gradle plugins) to Corda artifacts should now use the
|
||||||
|
``corda_release_distribution`` variable (was previously hardcoded to use ``net.corda``):
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
// Corda integration dependencies
|
||||||
|
cordaCompile "$corda_release_distribution:corda-core:$corda_release_version"
|
||||||
|
cordaCompile "$corda_release_distribution:corda-finance:$corda_release_version"
|
||||||
|
cordaCompile "$corda_release_distribution:corda-jackson:$corda_release_version"
|
||||||
|
cordaCompile "$corda_release_distribution:corda-rpc:$corda_release_version"
|
||||||
|
cordaCompile "$corda_release_distribution:corda-node-api:$corda_release_version"
|
||||||
|
cordaCompile "$corda_release_distribution:corda-webserver-impl:$corda_release_version"
|
||||||
|
cordaRuntime "$corda_release_distribution:corda:$corda_release_version"
|
||||||
|
cordaRuntime "$corda_release_distribution:corda-webserver:$corda_release_version"
|
||||||
|
|
||||||
|
testCompile "$corda_release_distribution:corda-node-driver:$corda_release_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
* For existing contract ORM schemas that extend from `CommonSchemaV1.LinearState` or `CommonSchemaV1.FungibleState`,
|
||||||
|
you will need to explicitly map the `participants` collection to a database table. Previously this mapping was done in the
|
||||||
|
superclass, but that makes it impossible to properly configure the table name.
|
||||||
|
The required change is to add the ``override var participants: MutableSet<AbstractParty>? = null`` field to your class, and
|
||||||
|
add JPA mappings. For ex., see this example:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "cash_states_v2",
|
||||||
|
indexes = arrayOf(Index(name = "ccy_code_idx2", columnList = "ccy_code")))
|
||||||
|
class PersistentCashState(
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@Column(name = "participants")
|
||||||
|
@CollectionTable(name="cash_states_v2_participants", joinColumns = arrayOf(
|
||||||
|
JoinColumn(name = "output_index", referencedColumnName = "output_index"),
|
||||||
|
JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id")))
|
||||||
|
override var participants: MutableSet<AbstractParty>? = null,
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
Applies to both gradle deployNodes tasks and/or corda node configuration (node.conf).
|
||||||
|
|
||||||
|
* Remove any references to ``networkMap``.
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
networkMap "O=Agent,L=Dallas,C=US"
|
||||||
|
|
||||||
|
* Remove any references to ``advertisedServices`` (including notaries).
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
advertisedServices = ["corda.notary.validating"]
|
||||||
|
|
||||||
|
* Add an explicit notary definition in the Notary node configuration only:
|
||||||
|
|
||||||
|
.. sourcecode:: shell
|
||||||
|
|
||||||
|
notary = [validating : true]
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
~~~~~~~
|
^^^^^^^
|
||||||
|
|
||||||
|
Contract tests
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* You must now create a ``MockServices`` object.
|
||||||
|
|
||||||
|
``MockServices`` provides a mock identity, key and storage service. ``MockServices`` takes as its first argument a
|
||||||
|
list of the CorDapp packages to scan:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
private val ledgerServices = MockServices(listOf("net.corda.examples.obligation", "net.corda.testing.contracts"))
|
||||||
|
|
||||||
|
``MockServices`` replaces the use of ``setCordappPackages`` and ``unsetCordappPackages``.
|
||||||
|
|
||||||
|
* ``ledger`` is now defined as a ``MockServices`` method. This means that:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
ledger {
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
ledgerServices.ledger {
|
||||||
|
|
||||||
|
* Within a mock ledger transaction, ``ContractState`` instances are passed to ``input`` and ``output`` as objects
|
||||||
|
rather than lambdas. For example:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
ledgerServices.ledger {
|
||||||
|
transaction {
|
||||||
|
input(OBLIGATION_CONTRACT_ID, DummyState())
|
||||||
|
output(OBLIGATION_CONTRACT_ID, oneDollarObligation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* Within a mock ledger transaction, ``CommandData`` instances are passed to ``input`` and ``output`` as objects
|
||||||
|
rather than lambdas, and the public keys must be passed as a list if there is more than one. For example:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
ledgerServices.ledger {
|
||||||
|
transaction {
|
||||||
|
command(alice.publicKey, ObligationContract.Commands.Issue())
|
||||||
|
command(listOf(alice.publicKey, bob.publicKey), ObligationContract.Commands.Issue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* The predefined test identities (e.g. ``ALICE`` and ``MINI_CORP``) have been removed.
|
||||||
|
|
||||||
|
You must now define the test identities explicitly. For example:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
val alice = TestIdentity(CordaX500Name(organisation = "Alice", locality = "TestLand", country = "GB"))
|
||||||
|
|
||||||
|
``TestIdentity`` exposes methods to get the ``name``, ``keyPair``, ``publicKey``, ``party`` and ``identity`` of the
|
||||||
|
underlying ``TestIdentity``
|
||||||
|
|
||||||
|
* Explicit invocation of transaction transformation (ie. using ``TransactionBuilder``) requires serialization engine
|
||||||
|
to be initialized. In unit test this can be achieved by using the following jUnit rule:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
|
|
||||||
|
Flow tests
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
* The registration mechanism for CorDapps in ``MockNetwork`` unit tests has changed.
|
* The registration mechanism for CorDapps in ``MockNetwork`` unit tests has changed.
|
||||||
|
|
||||||
It is now done via the ``cordappPackages`` constructor parameter of MockNetwork.
|
It is now done via the ``cordappPackages`` constructor parameter of MockNetwork. This takes a list of ``String``
|
||||||
This takes a list of `String` values which should be the
|
values which should be the package names of the CorDapps containing the contract verification code you wish to load.
|
||||||
package names of the CorDapps containing the contract verification code you wish to load.
|
|
||||||
The ``unsetCordappPackages`` method is now redundant and has been removed.
|
The ``unsetCordappPackages`` method is now redundant and has been removed.
|
||||||
|
|
||||||
|
* Creation of Notaries in ``MockNetwork`` unit tests has changed.
|
||||||
|
|
||||||
|
Previously the API call ``createNotaryNode(legalName = CordaX500ame(...))`` would be used to create a notary:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
val notary = mockNetwork.createNotaryNode(legalName = CordaX500Name("Notary", "London", "UK"))
|
||||||
|
|
||||||
|
Notaries are now defined as part of ``MockNetwork`` creation using ``notarySpecs``, as in the following example:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
mockNetwork = MockNetwork(notarySpecs = listOf(MockNetwork.NotarySpec(CordaX500Name("Notary","London","UK"))))
|
||||||
|
|
||||||
|
* A notary is no longer specified when creating a standard node using the ``createPartyNode`` API call.
|
||||||
|
|
||||||
|
Previously:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
mockNetwork.createPartyNode(notary.network.myAddress, CordaX500Name("Node", "Madrid", "ES"))
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
mockNetwork.createPartyNode(CordaX500Name("Node", "Madrid", "ES"))
|
||||||
|
|
||||||
|
* Utility node creation API method ``createSomeNodes(...)`` has been removed, and nodes must be created individually.
|
||||||
|
|
||||||
|
Previously:
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
MockNetwork.BasketOfNodes nodes = net.createSomeNodes(3);
|
||||||
|
nodeA = nodes.getPartyNodes().get(0);
|
||||||
|
nodeB = nodes.getPartyNodes().get(1);
|
||||||
|
nodeC = nodes.getPartyNodes().get(2);
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
nodeA = net.createNode(new MockNodeParameters());
|
||||||
|
nodeB = net.createNode(new MockNodeParameters());
|
||||||
|
nodeC = net.createNode(new MockNodeParameters());
|
||||||
|
List<StartedNode<MockNode>> nodes = Arrays.asList(nodeA, nodeB, nodeC);
|
||||||
|
|
||||||
|
* Flow framework instantiation of a flow has a slight variation in start syntax:
|
||||||
|
|
||||||
|
Previously:
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
CordaFuture<SignedTransaction> future = nodeA.getServices().startFlow(flow).getResultFuture();
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
CordaFuture<SignedTransaction> future = startFlow(nodeA.getServices(), flow).getResultFuture();
|
||||||
|
|
||||||
|
* ``StartedNodeServices.startFlow`` must now be imported from ``net.corda.testing.node``
|
||||||
|
|
||||||
|
* Do not use ``node.internals`` to register flows:
|
||||||
|
|
||||||
|
Previous code would often look as follows:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
protected fun registerFlowsAndServices(node: StartedNode<MockNetwork.MockNode>) {
|
||||||
|
val mockNode = node.internals
|
||||||
|
mockNode.registerInitiatedFlow(MyCustomFlow::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
protected fun registerFlowsAndServices(mockNode: StartedNode<MockNetwork.MockNode>) {
|
||||||
|
mockNode.registerInitiatedFlow(MyCustomFlow::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
* Do not use ``node.internals`` to register Corda services
|
||||||
|
|
||||||
|
Previously:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
node.internals.installCordaService(CustomService::class.java)
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
node.services.cordaService(CustomService::class.java)
|
||||||
|
|
||||||
|
Better yet, use node factory to organize both register flows and services, for example, create class as follows:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
class PrimesOracleNode(args: MockNodeArgs) : MockNetwork.MockNode(args) {
|
||||||
|
override fun start() = super.start().apply {
|
||||||
|
registerInitiatedFlow(QueryHandler::class.java)
|
||||||
|
registerInitiatedFlow(SignHandler::class.java)
|
||||||
|
services.cordaService(net.corda.examples.oracle.service.service.Oracle::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
and then pass it to ``createNode``:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
val oracle = mockNet.createNode(MockNodeParameters(legalName = CordaX500Name("Oracle", "New York", "US")), ::PrimesOracleNode)
|
||||||
|
|
||||||
|
Node driver
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
* ``User`` has been moved from ``net.corda.nodeapi.User`` to ``net.corda.nodeapi.internal.config.User``
|
||||||
|
|
||||||
|
* Notaries are defined by passing a list of ``NotarySpec`` objects to ``driver`` using the ``notarySpecs`` argument,
|
||||||
|
instead of being defined manually in the driver block.
|
||||||
|
|
||||||
|
``notarySpecs`` defaults to providing a single validating notary
|
||||||
|
|
||||||
|
* The ``waitForAllNodesToFinish`` function has been removed. It has been replaced with a ``waitForAllNodesToFinish``
|
||||||
|
argument to ``driver``
|
||||||
|
|
||||||
|
* No longer specify advertised services to the ``DriverDSL`` when starting nodes:
|
||||||
|
|
||||||
|
Previously:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
driver {
|
||||||
|
startNode(providedName = CordaX500Name("Controller", "London", "GB"), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
driver {
|
||||||
|
startNode(providedName = CordaX500Name("Controller", "London", "GB")),
|
||||||
|
|
||||||
|
Finance
|
||||||
|
^^^^^^^
|
||||||
|
|
||||||
|
* ``CASH_PROGRAM_ID`` has been moved to ``Cash.PROGRAM_ID``, where ``Cash`` is defined in the
|
||||||
|
``import net.corda.finance.contracts.asset`` package
|
||||||
|
|
||||||
V1.0 to V2.0
|
V1.0 to V2.0
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@ -63,14 +394,14 @@ Build
|
|||||||
|
|
||||||
* MockNetwork has moved.
|
* MockNetwork has moved.
|
||||||
|
|
||||||
A new test driver module dependency needs to be including in your project: `corda-node-driver`. To continue using the
|
A new test driver module dependency needs to be including in your project: ``corda-node-driver``. To continue using the
|
||||||
mock network for testing, add the following entry to your gradle build file:
|
mock network for testing, add the following entry to your gradle build file:
|
||||||
|
|
||||||
.. sourcecode:: shell
|
.. sourcecode:: shell
|
||||||
|
|
||||||
testCompile "net.corda:corda-node-driver:$corda_release_version"
|
testCompile "$corda_release_distribution:corda-node-driver:$corda_release_version"
|
||||||
|
|
||||||
.. note:: you may only need `testCompile "net.corda:corda-test-utils:$corda_release_version"` if not using the Driver DSL.
|
.. note:: you may only need `testCompile "$corda_release_distribution:corda-test-utils:$corda_release_version"` if not using the Driver DSL.
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
@ -91,7 +422,7 @@ Use the automatic imports feature of IntelliJ to intelligently resolve the new i
|
|||||||
|
|
||||||
* Missing imports for contract types.
|
* Missing imports for contract types.
|
||||||
|
|
||||||
CommercialPaper and Cash are now contained within the `finance` module, as are associated helpers functions.
|
CommercialPaper and Cash are now contained within the ``finance`` module, as are associated helpers functions.
|
||||||
For example:
|
For example:
|
||||||
``import net.corda.contracts.ICommercialPaperState`` becomes ``import net.corda.finance.contracts.ICommercialPaperState``
|
``import net.corda.contracts.ICommercialPaperState`` becomes ``import net.corda.finance.contracts.ICommercialPaperState``
|
||||||
|
|
||||||
@ -105,7 +436,7 @@ Use the automatic imports feature of IntelliJ to intelligently resolve the new i
|
|||||||
|
|
||||||
* Missing imports for utility functions.
|
* Missing imports for utility functions.
|
||||||
|
|
||||||
Many common types and helper methods have been consolidated into `net.corda.core.utilities` package.
|
Many common types and helper methods have been consolidated into ``net.corda.core.utilities`` package.
|
||||||
For example:
|
For example:
|
||||||
``import net.corda.core.crypto.commonName`` becomes ``import net.corda.core.utilities.commonName``
|
``import net.corda.core.crypto.commonName`` becomes ``import net.corda.core.utilities.commonName``
|
||||||
|
|
||||||
@ -170,7 +501,7 @@ Flow framework
|
|||||||
Node services (ServiceHub)
|
Node services (ServiceHub)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* VaultQueryService: unresolved reference to `vaultQueryService`.
|
* VaultQueryService: unresolved reference to ``vaultQueryService``.
|
||||||
|
|
||||||
Replace all references to ``<services>.vaultQueryService`` with ``<services>.vaultService``.
|
Replace all references to ``<services>.vaultQueryService`` with ``<services>.vaultService``.
|
||||||
Previously there were two vault APIs. Now there is a single unified API with the same functions: ``VaultService``.
|
Previously there were two vault APIs. Now there is a single unified API with the same functions: ``VaultService``.
|
||||||
@ -191,7 +522,7 @@ Node services (ServiceHub)
|
|||||||
RPC Client
|
RPC Client
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
||||||
* Missing API methods on `CordaRPCOps` interface.
|
* Missing API methods on ``CordaRPCOps`` interface.
|
||||||
|
|
||||||
* Calls to ``verifiedTransactionsFeed()`` and ``verifiedTransactions()`` have been replaced with:
|
* Calls to ``verifiedTransactionsFeed()`` and ``verifiedTransactions()`` have been replaced with:
|
||||||
``internalVerifiedTransactionsSnapshot()`` and ``internalVerifiedTransactionsFeed()`` respectively
|
``internalVerifiedTransactionsSnapshot()`` and ``internalVerifiedTransactionsFeed()`` respectively
|
||||||
@ -199,7 +530,7 @@ RPC Client
|
|||||||
This is in preparation for the planned integration of Intel SGX™, which will encrypt the transactions feed.
|
This is in preparation for the planned integration of Intel SGX™, which will encrypt the transactions feed.
|
||||||
Apps that use this API will not work on encrypted ledgers: you should probably be using the vault query API instead.
|
Apps that use this API will not work on encrypted ledgers: you should probably be using the vault query API instead.
|
||||||
|
|
||||||
* Accessing the `networkMapCache` via ``services.nodeInfo().legalIdentities`` returns a list of identities.
|
* Accessing the ``networkMapCache`` via ``services.nodeInfo().legalIdentities`` returns a list of identities.
|
||||||
The first element in the list is the Party object referring to a node's single identity.
|
The first element in the list is the Party object referring to a node's single identity.
|
||||||
|
|
||||||
This is in preparation for allowing a node to host multiple separate identities in future.
|
This is in preparation for allowing a node to host multiple separate identities in future.
|
||||||
@ -207,15 +538,15 @@ RPC Client
|
|||||||
Testing
|
Testing
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
Please note that `Clauses` have been removed completely as of V1.0.
|
Please note that ``Clauses`` have been removed completely as of V1.0.
|
||||||
We will be revisiting this capability in a future release.
|
We will be revisiting this capability in a future release.
|
||||||
|
|
||||||
* CorDapps must be explicitly registered in ``MockNetwork`` unit tests.
|
* CorDapps must be explicitly registered in ``MockNetwork`` unit tests.
|
||||||
|
|
||||||
This is done by calling ``setCordappPackages``, an extension helper function in the ``net.corda.testing`` package,
|
This is done by calling ``setCordappPackages``, an extension helper function in the ``net.corda.testing`` package,
|
||||||
on the first line of your `@Before` method. This takes a variable number of `String` arguments which should be the
|
on the first line of your ``@Before`` method. This takes a variable number of ``String`` arguments which should be the
|
||||||
package names of the CorDapps containing the contract verification code you wish to load.
|
package names of the CorDapps containing the contract verification code you wish to load.
|
||||||
You should unset CorDapp packages in your `@After` method by using ``unsetCordappPackages()`` after `stopNodes()`.
|
You should unset CorDapp packages in your ``@After`` method by using ``unsetCordappPackages()`` after ``stopNodes()``.
|
||||||
|
|
||||||
* CorDapps must be explicitly registered in ``DriverDSL`` and ``RPCDriverDSL`` integration tests.
|
* CorDapps must be explicitly registered in ``DriverDSL`` and ``RPCDriverDSL`` integration tests.
|
||||||
|
|
||||||
@ -225,7 +556,7 @@ We will be revisiting this capability in a future release.
|
|||||||
Finance
|
Finance
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
* `FungibleAsset` interface simplification.
|
* ``FungibleAsset`` interface simplification.
|
||||||
|
|
||||||
The ``FungibleAsset`` interface has been made simpler. The ``Commands`` grouping interface
|
The ``FungibleAsset`` interface has been made simpler. The ``Commands`` grouping interface
|
||||||
that included the ``Move``, ``Issue`` and ``Exit`` interfaces have all been removed, while the ``move`` function has
|
that included the ``Move``, ``Issue`` and ``Exit`` interfaces have all been removed, while the ``move`` function has
|
||||||
@ -233,10 +564,10 @@ Finance
|
|||||||
|
|
||||||
The following errors may be reported:
|
The following errors may be reported:
|
||||||
|
|
||||||
* override nothing (FungibleAsset): `move`
|
* override nothing (FungibleAsset): ``move``
|
||||||
* not a subtype of overridden FungibleAsset: `withNewOwner`
|
* not a subtype of overridden FungibleAsset: ``withNewOwner``
|
||||||
* no longer need to override `override val contractHash: SecureHash? = null`
|
* no longer need to override ``override val contractHash: SecureHash? = null``
|
||||||
* need to override `override val contract: Class<out Contract>? = null`
|
* need to override ``override val contract: Class<out Contract>? = null``
|
||||||
|
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
@ -266,7 +597,7 @@ Gotchas
|
|||||||
Core data structures
|
Core data structures
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* `TransactionBuilder` changes.
|
* ``TransactionBuilder`` changes.
|
||||||
|
|
||||||
Use convenience class ``StateAndContract`` instead of ``TransactionBuilder.withItems()`` for passing
|
Use convenience class ``StateAndContract`` instead of ``TransactionBuilder.withItems()`` for passing
|
||||||
around a state and its contract.
|
around a state and its contract.
|
||||||
@ -274,7 +605,7 @@ Core data structures
|
|||||||
* Transaction building DSL changes:
|
* Transaction building DSL changes:
|
||||||
|
|
||||||
* now need to explicitly pass the ContractClassName into all inputs and outputs.
|
* now need to explicitly pass the ContractClassName into all inputs and outputs.
|
||||||
* `ContractClassName` refers to the class containing the “verifier” method.
|
* ``ContractClassName`` refers to the class containing the “verifier” method.
|
||||||
|
|
||||||
* Contract verify method signature change.
|
* Contract verify method signature change.
|
||||||
|
|
||||||
@ -344,15 +675,15 @@ Build
|
|||||||
|
|
||||||
* Gradle dependency reference changes.
|
* Gradle dependency reference changes.
|
||||||
|
|
||||||
Module name has changed to include `corda` in the artifacts jar name:
|
Module name has changed to include ``corda`` in the artifacts jar name:
|
||||||
|
|
||||||
.. sourcecode:: shell
|
.. sourcecode:: shell
|
||||||
|
|
||||||
compile "net.corda:core:$corda_release_version" -> compile "net.corda:corda-core:$corda_release_version"
|
compile "$corda_release_distribution:core:$corda_release_version" -> compile "$corda_release_distribution:corda-core:$corda_release_version"
|
||||||
compile "net.corda:finance:$corda_release_version" -> compile "net.corda:corda-finance:$corda_release_version"
|
compile "$corda_release_distribution:finance:$corda_release_version" -> compile "$corda_release_distribution:corda-finance:$corda_release_version"
|
||||||
compile "net.corda:jackson:$corda_release_version" -> compile "net.corda:corda-jackson:$corda_release_version"
|
compile "$corda_release_distribution:jackson:$corda_release_version" -> compile "$corda_release_distribution:corda-jackson:$corda_release_version"
|
||||||
compile "net.corda:node:$corda_release_version" -> compile "net.corda:corda-node:$corda_release_version"
|
compile "$corda_release_distribution:node:$corda_release_version" -> compile "$corda_release_distribution:corda-node:$corda_release_version"
|
||||||
compile "net.corda:rpc:$corda_release_version" -> compile "net.corda:corda-rpc:$corda_release_version"
|
compile "$corda_release_distribution:rpc:$corda_release_version" -> compile "$corda_release_distribution:corda-rpc:$corda_release_version"
|
||||||
|
|
||||||
Node services (ServiceHub)
|
Node services (ServiceHub)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -367,10 +698,10 @@ Finance
|
|||||||
|
|
||||||
* Financial asset contracts (Cash, CommercialPaper, Obligations) are now a standalone CorDapp within the **finance** module.
|
* Financial asset contracts (Cash, CommercialPaper, Obligations) are now a standalone CorDapp within the **finance** module.
|
||||||
|
|
||||||
Need to import from respective package within `finance` module:
|
Need to import from respective package within ``finance`` module:
|
||||||
eg. ``net.corda.finance.contracts.asset.Cash``
|
eg. ``net.corda.finance.contracts.asset.Cash``
|
||||||
|
|
||||||
Likewise, need to import associated asset flows from respective package within `finance` module:
|
Likewise, need to import associated asset flows from respective package within ``finance`` module:
|
||||||
eg. ``net.corda.finance.flows.CashIssueFlow``
|
eg. ``net.corda.finance.flows.CashIssueFlow``
|
||||||
``net.corda.finance.flows.CashIssueAndPaymentFlow``
|
``net.corda.finance.flows.CashIssueAndPaymentFlow``
|
||||||
``net.corda.finance.flows.CashExitFlow``
|
``net.corda.finance.flows.CashExitFlow``
|
||||||
@ -378,4 +709,4 @@ Finance
|
|||||||
* Moved ``finance`` gradle project files into a ``net.corda.finance`` package namespace.
|
* Moved ``finance`` gradle project files into a ``net.corda.finance`` package namespace.
|
||||||
|
|
||||||
This may require adjusting imports of Cash flow references and also of ``StartFlow`` permission in ``gradle.build`` files.
|
This may require adjusting imports of Cash flow references and also of ``StartFlow`` permission in ``gradle.build`` files.
|
||||||
Associated flows (`Cash*Flow`, `TwoPartyTradeFlow`, `TwoPartyDealFlow`) must now be imported from this package.
|
Associated flows (``Cash*Flow``, ``TwoPartyTradeFlow``, ``TwoPartyDealFlow``) must now be imported from this package.
|
||||||
|
@ -60,9 +60,9 @@ class CordappPlugin : Plugin<Project> {
|
|||||||
excludes.none { exclude -> (exclude["group"] == dep.group) && (exclude["name"] == dep.name) }
|
excludes.none { exclude -> (exclude["group"] == dep.group) && (exclude["name"] == dep.name) }
|
||||||
}
|
}
|
||||||
filteredDeps.forEach {
|
filteredDeps.forEach {
|
||||||
// net.corda or com.r3.corda.enterprise may be a core dependency which shouldn't be included in this cordapp so give a warning
|
// net.corda or com.r3.corda may be a core dependency which shouldn't be included in this cordapp so give a warning
|
||||||
val group = it.group?.toString() ?: ""
|
val group = it.group?.toString() ?: ""
|
||||||
if (group.startsWith("net.corda.") || group.startsWith("com.r3.corda.enterprise.")) {
|
if (group.startsWith("net.corda.") || group.startsWith("com.r3.corda.")) {
|
||||||
project.logger.warn("You appear to have included a Corda platform component ($it) using a 'compile' or 'runtime' dependency." +
|
project.logger.warn("You appear to have included a Corda platform component ($it) using a 'compile' or 'runtime' dependency." +
|
||||||
"This can cause node stability problems. Please use 'corda' instead." +
|
"This can cause node stability problems. Please use 'corda' instead." +
|
||||||
"See http://docs.corda.net/cordapp-build-systems.html")
|
"See http://docs.corda.net/cordapp-build-systems.html")
|
||||||
|
@ -55,7 +55,8 @@ class Cordformation : Plugin<Project> {
|
|||||||
override fun apply(project: Project) {
|
override fun apply(project: Project) {
|
||||||
Utils.createCompileConfiguration("cordapp", project)
|
Utils.createCompileConfiguration("cordapp", project)
|
||||||
Utils.createRuntimeConfiguration(CORDFORMATION_TYPE, project)
|
Utils.createRuntimeConfiguration(CORDFORMATION_TYPE, project)
|
||||||
val jolokiaVersion = project.rootProject.ext<String>("jolokia_version")
|
// TODO: improve how we re-use existing declared external variables from root gradle.build
|
||||||
|
val jolokiaVersion = try { project.rootProject.ext<String>("jolokia_version") } catch (e: Exception) { "1.3.7" }
|
||||||
project.dependencies.add(CORDFORMATION_TYPE, "org.jolokia:jolokia-jvm:$jolokiaVersion:agent")
|
project.dependencies.add(CORDFORMATION_TYPE, "org.jolokia:jolokia-jvm:$jolokiaVersion:agent")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,8 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
* Installs the jolokia monitoring agent JAR to the node/drivers directory
|
* Installs the jolokia monitoring agent JAR to the node/drivers directory
|
||||||
*/
|
*/
|
||||||
private fun installAgentJar() {
|
private fun installAgentJar() {
|
||||||
val jolokiaVersion = project.rootProject.ext<String>("jolokia_version")
|
// TODO: improve how we re-use existing declared external variables from root gradle.build
|
||||||
|
val jolokiaVersion = try { project.rootProject.ext<String>("jolokia_version") } catch (e: Exception) { "1.3.7" }
|
||||||
val agentJar = project.configuration("runtime").files {
|
val agentJar = project.configuration("runtime").files {
|
||||||
(it.group == "org.jolokia") &&
|
(it.group == "org.jolokia") &&
|
||||||
(it.name == "jolokia-jvm") &&
|
(it.name == "jolokia-jvm") &&
|
||||||
|
@ -28,12 +28,12 @@ To run the HSM signing server:
|
|||||||
|
|
||||||
```
|
```
|
||||||
cd network-management
|
cd network-management
|
||||||
java -jar capsule-hsm/build/libs/hsm-<version>.jar --config-file hsm.conf
|
java -jar capsule-hsm/build/libs/hsm-<version>.jar --configFile hsm.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
For a list of options the HSM signing server takes, run with the `--help` option:
|
For a list of options the HSM signing server takes, run with the `--help` option:
|
||||||
```
|
```
|
||||||
java -jar capsule-hsm/build/libs/hsm-3.0-<version>.jar --help
|
java -jar capsule-hsm/build/libs/hsm-<version>.jar --help
|
||||||
```
|
```
|
||||||
|
|
||||||
#Configuring network management service
|
#Configuring network management service
|
||||||
|
@ -145,7 +145,7 @@ class PersistentCertificateRequestStorage(private val database: CordaPersistence
|
|||||||
// Also, at the moment we assume that once the CSR is approved it cannot be rejected.
|
// Also, at the moment we assume that once the CSR is approved it cannot be rejected.
|
||||||
// What if we approved something by mistake.
|
// What if we approved something by mistake.
|
||||||
val duplicates = session.createQuery(query).resultList.filter {
|
val duplicates = session.createQuery(query).resultList.filter {
|
||||||
it.status != RequestStatus.REJECTED
|
it.status !=RequestStatus.REJECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
return Pair(legalName, if (duplicates.isEmpty()) null else "Duplicate legal name")
|
return Pair(legalName, if (duplicates.isEmpty()) null else "Duplicate legal name")
|
||||||
|
@ -41,7 +41,9 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
|
|||||||
|
|
||||||
private val networkMapCache: LoadingCache<Boolean, Pair<SignedNetworkMap?, NetworkParameters?>> = CacheBuilder.newBuilder()
|
private val networkMapCache: LoadingCache<Boolean, Pair<SignedNetworkMap?, NetworkParameters?>> = CacheBuilder.newBuilder()
|
||||||
.expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS)
|
.expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS)
|
||||||
.build(CacheLoader.from { _ -> Pair(networkMapStorage.getCurrentNetworkMap(), networkMapStorage.getCurrentNetworkParameters()) })
|
.build(CacheLoader.from { _ ->
|
||||||
|
Pair(networkMapStorage.getCurrentNetworkMap()
|
||||||
|
, networkMapStorage.getCurrentNetworkParameters()) })
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("publish")
|
@Path("publish")
|
||||||
@ -60,7 +62,7 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
|
|||||||
is NetworkMapNotInitialisedException -> status(Response.Status.SERVICE_UNAVAILABLE).entity(e.message)
|
is NetworkMapNotInitialisedException -> status(Response.Status.SERVICE_UNAVAILABLE).entity(e.message)
|
||||||
is InvalidPlatformVersionException -> status(Response.Status.BAD_REQUEST).entity(e.message)
|
is InvalidPlatformVersionException -> status(Response.Status.BAD_REQUEST).entity(e.message)
|
||||||
is IllegalArgumentException, is InvalidKeyException, is SignatureException -> status(Response.Status.UNAUTHORIZED).entity(e.message)
|
is IllegalArgumentException, is InvalidKeyException, is SignatureException -> status(Response.Status.UNAUTHORIZED).entity(e.message)
|
||||||
// Rethrow e if its not one of the expected exception, the server will return http 500 internal error.
|
// Rethrow e if its not one of the expected exception, the server will return http 500 internal error.
|
||||||
else -> throw e
|
else -> throw e
|
||||||
}
|
}
|
||||||
}.build()
|
}.build()
|
||||||
|
@ -61,7 +61,7 @@ data class Parameters(val dataSourceProperties: Properties,
|
|||||||
fun parseParameters(vararg args: String): Parameters {
|
fun parseParameters(vararg args: String): Parameters {
|
||||||
val argConfig = args.toConfigWithOptions {
|
val argConfig = args.toConfigWithOptions {
|
||||||
accepts("basedir", "Overriding configuration filepath, default to current directory.").withRequiredArg().defaultsTo(".").describedAs("filepath")
|
accepts("basedir", "Overriding configuration filepath, default to current directory.").withRequiredArg().defaultsTo(".").describedAs("filepath")
|
||||||
accepts("config-file", "Overriding configuration file.").withRequiredArg().describedAs("filepath")
|
accepts("configFile", "Overriding configuration file.").withRequiredArg().defaultsTo("node.conf").describedAs("filepath")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The config-file option is changed to configFile
|
// The config-file option is changed to configFile
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
</column>
|
</column>
|
||||||
<column name="certificate_path_bytes" type="BLOB"/>
|
<column name="certificate_path_bytes" type="BLOB"/>
|
||||||
|
|
||||||
<column name="certificate_status" type="INT"/>
|
<column name="certificate_status" type="INT"/>
|
||||||
<column name="public_key_hash" type="NVARCHAR(64)"/>
|
<column name="public_key_hash" type="NVARCHAR(64)"/>
|
||||||
<column name="certificate_signing_request" type="NVARCHAR(64)">
|
<column name="certificate_signing_request" type="NVARCHAR(64)">
|
||||||
|
@ -12,6 +12,7 @@ import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
|||||||
import net.corda.nodeapi.internal.network.NetworkMap
|
import net.corda.nodeapi.internal.network.NetworkMap
|
||||||
import net.corda.nodeapi.internal.network.SignedNetworkMap
|
import net.corda.nodeapi.internal.network.SignedNetworkMap
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -7,8 +7,6 @@ import net.corda.core.crypto.random63BitValue
|
|||||||
import net.corda.core.internal.CertRole
|
import net.corda.core.internal.CertRole
|
||||||
import net.corda.core.internal.reader
|
import net.corda.core.internal.reader
|
||||||
import net.corda.core.internal.writer
|
import net.corda.core.internal.writer
|
||||||
import net.corda.core.identity.CordaX500Name
|
|
||||||
import net.corda.core.internal.*
|
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.core.utilities.millis
|
import net.corda.core.utilities.millis
|
||||||
import org.bouncycastle.asn1.*
|
import org.bouncycastle.asn1.*
|
||||||
|
@ -96,7 +96,7 @@ class EvolutionSerializer(
|
|||||||
old.fields.forEach {
|
old.fields.forEach {
|
||||||
val returnType = it.getTypeAsClass(factory.classloader)
|
val returnType = it.getTypeAsClass(factory.classloader)
|
||||||
oldArgs[it.name] = OldParam(
|
oldArgs[it.name] = OldParam(
|
||||||
returnType, idx++, PropertySerializer.make(it.name, null, returnType, factory))
|
returnType, idx++, PropertySerializer.make(it.name, PublicPropertyReader(null), returnType, factory))
|
||||||
}
|
}
|
||||||
|
|
||||||
val readers = constructor.parameters.map {
|
val readers = constructor.parameters.map {
|
||||||
|
@ -26,6 +26,8 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
|
|||||||
propertiesForSerialization(kotlinConstructor, clazz, factory)
|
propertiesForSerialization(kotlinConstructor, clazz, factory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPropertySerializers() = propertySerializers
|
||||||
|
|
||||||
private val typeName = nameForType(clazz)
|
private val typeName = nameForType(clazz)
|
||||||
|
|
||||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
|
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
|
||||||
|
@ -1,17 +1,74 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.loggerFor
|
||||||
import org.apache.qpid.proton.amqp.Binary
|
import org.apache.qpid.proton.amqp.Binary
|
||||||
import org.apache.qpid.proton.codec.Data
|
import org.apache.qpid.proton.codec.Data
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
|
import java.lang.reflect.Field
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.full.memberProperties
|
||||||
import kotlin.reflect.jvm.javaGetter
|
import kotlin.reflect.jvm.javaGetter
|
||||||
|
import kotlin.reflect.jvm.kotlinProperty
|
||||||
|
|
||||||
|
abstract class PropertyReader {
|
||||||
|
abstract fun read(obj: Any?): Any?
|
||||||
|
abstract fun isNullable(): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() {
|
||||||
|
init {
|
||||||
|
readMethod?.isAccessible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Method.returnsNullable(): Boolean {
|
||||||
|
try {
|
||||||
|
val returnTypeString = this.declaringClass.kotlin.memberProperties.firstOrNull { it.javaGetter == this }?.returnType?.toString() ?: "?"
|
||||||
|
return returnTypeString.endsWith('?') || returnTypeString.endsWith('!')
|
||||||
|
} catch (e: kotlin.reflect.jvm.internal.KotlinReflectionInternalError) {
|
||||||
|
// This might happen for some types, e.g. kotlin.Throwable? - the root cause of the issue is: https://youtrack.jetbrains.com/issue/KT-13077
|
||||||
|
// TODO: Revisit this when Kotlin issue is fixed.
|
||||||
|
|
||||||
|
loggerFor<PropertySerializer>().error("Unexpected internal Kotlin error", e)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(obj: Any?): Any? {
|
||||||
|
return readMethod!!.invoke(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isNullable(): Boolean = readMethod?.returnsNullable() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrivatePropertyReader(val field: Field, parentType: Type) : PropertyReader() {
|
||||||
|
init {
|
||||||
|
loggerFor<PropertySerializer>().warn("Create property Serializer for private property '${field.name}' not "
|
||||||
|
+ "exposed by a getter on class '$parentType'\n"
|
||||||
|
+ "\tNOTE: This behaviour will be deprecated at some point in the future and a getter required")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(obj: Any?): Any? {
|
||||||
|
field.isAccessible = true
|
||||||
|
val rtn = field.get(obj)
|
||||||
|
field.isAccessible = false
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isNullable() = try {
|
||||||
|
field.kotlinProperty?.returnType?.isMarkedNullable ?: false
|
||||||
|
} catch (e: kotlin.reflect.jvm.internal.KotlinReflectionInternalError) {
|
||||||
|
// This might happen for some types, e.g. kotlin.Throwable? - the root cause of the issue is: https://youtrack.jetbrains.com/issue/KT-13077
|
||||||
|
// TODO: Revisit this when Kotlin issue is fixed.
|
||||||
|
loggerFor<PropertySerializer>().error("Unexpected internal Kotlin error", e)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for serialization of a property of an object.
|
* Base class for serialization of a property of an object.
|
||||||
*/
|
*/
|
||||||
sealed class PropertySerializer(val name: String, val readMethod: Method?, val resolvedType: Type) {
|
sealed class PropertySerializer(val name: String, val propertyReader: PropertyReader, val resolvedType: Type) {
|
||||||
abstract fun writeClassInfo(output: SerializationOutput)
|
abstract fun writeClassInfo(output: SerializationOutput)
|
||||||
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput)
|
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput)
|
||||||
abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any?
|
abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any?
|
||||||
@ -44,25 +101,11 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun generateMandatory(): Boolean {
|
private fun generateMandatory(): Boolean {
|
||||||
return isJVMPrimitive || readMethod?.returnsNullable() == false
|
return isJVMPrimitive || !(propertyReader.isNullable())
|
||||||
}
|
|
||||||
|
|
||||||
private fun Method.returnsNullable(): Boolean {
|
|
||||||
try {
|
|
||||||
val returnTypeString = this.declaringClass.kotlin.memberProperties.firstOrNull { it.javaGetter == this }?.returnType?.toString() ?: "?"
|
|
||||||
return returnTypeString.endsWith('?') || returnTypeString.endsWith('!')
|
|
||||||
} catch (e: kotlin.reflect.jvm.internal.KotlinReflectionInternalError) {
|
|
||||||
// This might happen for some types, e.g. kotlin.Throwable? - the root cause of the issue is: https://youtrack.jetbrains.com/issue/KT-13077
|
|
||||||
// TODO: Revisit this when Kotlin issue is fixed.
|
|
||||||
logger.error("Unexpected internal Kotlin error", e)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = contextLogger()
|
fun make(name: String, readMethod: PropertyReader, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
|
||||||
fun make(name: String, readMethod: Method?, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
|
|
||||||
readMethod?.isAccessible = true
|
|
||||||
if (SerializerFactory.isPrimitive(resolvedType)) {
|
if (SerializerFactory.isPrimitive(resolvedType)) {
|
||||||
return when (resolvedType) {
|
return when (resolvedType) {
|
||||||
Char::class.java, Character::class.java -> AMQPCharPropertySerializer(name, readMethod)
|
Char::class.java, Character::class.java -> AMQPCharPropertySerializer(name, readMethod)
|
||||||
@ -78,7 +121,8 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
* A property serializer for a complex type (another object).
|
* A property serializer for a complex type (another object).
|
||||||
*/
|
*/
|
||||||
class DescribedTypePropertySerializer(
|
class DescribedTypePropertySerializer(
|
||||||
name: String, readMethod: Method?,
|
name: String,
|
||||||
|
readMethod: PropertyReader,
|
||||||
resolvedType: Type,
|
resolvedType: Type,
|
||||||
private val lazyTypeSerializer: () -> AMQPSerializer<*>) : PropertySerializer(name, readMethod, resolvedType) {
|
private val lazyTypeSerializer: () -> AMQPSerializer<*>) : PropertySerializer(name, readMethod, resolvedType) {
|
||||||
// This is lazy so we don't get an infinite loop when a method returns an instance of the class.
|
// This is lazy so we don't get an infinite loop when a method returns an instance of the class.
|
||||||
@ -90,12 +134,15 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? = ifThrowsAppend({ nameForDebug }) {
|
override fun readProperty(
|
||||||
|
obj: Any?,
|
||||||
|
schemas: SerializationSchemas,
|
||||||
|
input: DeserializationInput): Any? = ifThrowsAppend({ nameForDebug }) {
|
||||||
input.readObjectOrNull(obj, schemas, resolvedType)
|
input.readObjectOrNull(obj, schemas, resolvedType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) = ifThrowsAppend({ nameForDebug }) {
|
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) = ifThrowsAppend({ nameForDebug }) {
|
||||||
output.writeObjectOrNull(readMethod!!.invoke(obj), data, resolvedType)
|
output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val nameForDebug = "$name(${resolvedType.typeName})"
|
private val nameForDebug = "$name(${resolvedType.typeName})"
|
||||||
@ -104,7 +151,10 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
/**
|
/**
|
||||||
* A property serializer for most AMQP primitive type (Int, String, etc).
|
* A property serializer for most AMQP primitive type (Int, String, etc).
|
||||||
*/
|
*/
|
||||||
class AMQPPrimitivePropertySerializer(name: String, readMethod: Method?, resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) {
|
class AMQPPrimitivePropertySerializer(
|
||||||
|
name: String,
|
||||||
|
readMethod: PropertyReader,
|
||||||
|
resolvedType: Type) : PropertySerializer(name, readMethod, resolvedType) {
|
||||||
override fun writeClassInfo(output: SerializationOutput) {}
|
override fun writeClassInfo(output: SerializationOutput) {}
|
||||||
|
|
||||||
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? {
|
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? {
|
||||||
@ -112,7 +162,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
|
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
|
||||||
val value = readMethod!!.invoke(obj)
|
val value = propertyReader.read(obj)
|
||||||
if (value is ByteArray) {
|
if (value is ByteArray) {
|
||||||
data.putObject(Binary(value))
|
data.putObject(Binary(value))
|
||||||
} else {
|
} else {
|
||||||
@ -126,7 +176,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
* value of the character is stored in numeric UTF-16 form and on deserialisation requires explicit
|
* value of the character is stored in numeric UTF-16 form and on deserialisation requires explicit
|
||||||
* casting back to a char otherwise it's treated as an Integer and a TypeMismatch occurs
|
* casting back to a char otherwise it's treated as an Integer and a TypeMismatch occurs
|
||||||
*/
|
*/
|
||||||
class AMQPCharPropertySerializer(name: String, readMethod: Method?) :
|
class AMQPCharPropertySerializer(name: String, readMethod: PropertyReader) :
|
||||||
PropertySerializer(name, readMethod, Character::class.java) {
|
PropertySerializer(name, readMethod, Character::class.java) {
|
||||||
override fun writeClassInfo(output: SerializationOutput) {}
|
override fun writeClassInfo(output: SerializationOutput) {}
|
||||||
|
|
||||||
@ -135,7 +185,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
|
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) {
|
||||||
val input = readMethod!!.invoke(obj)
|
val input = propertyReader.read(obj)
|
||||||
if (input != null) data.putShort((input as Char).toShort()) else data.putNull()
|
if (input != null) data.putShort((input as Char).toShort()) else data.putNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import kotlin.reflect.full.findAnnotation
|
|||||||
import kotlin.reflect.full.primaryConstructor
|
import kotlin.reflect.full.primaryConstructor
|
||||||
import kotlin.reflect.jvm.isAccessible
|
import kotlin.reflect.jvm.isAccessible
|
||||||
import kotlin.reflect.jvm.javaType
|
import kotlin.reflect.jvm.javaType
|
||||||
|
import kotlin.reflect.jvm.kotlinProperty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation indicating a constructor to be used to reconstruct instances of a class during deserialization.
|
* Annotation indicating a constructor to be used to reconstruct instances of a class during deserialization.
|
||||||
@ -29,8 +30,8 @@ import kotlin.reflect.jvm.javaType
|
|||||||
annotation class ConstructorForDeserialization
|
annotation class ConstructorForDeserialization
|
||||||
|
|
||||||
data class ConstructorDestructorMethods(
|
data class ConstructorDestructorMethods(
|
||||||
val getters : Collection<PropertySerializer>,
|
val getters: Collection<PropertySerializer>,
|
||||||
val setters : Collection<Method?>)
|
val setters: Collection<Method?>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Code for finding the constructor we will use for deserialization.
|
* Code for finding the constructor we will use for deserialization.
|
||||||
@ -100,26 +101,31 @@ private fun <T : Any> propertiesForSerializationFromConstructor(
|
|||||||
for (param in kotlinConstructor.parameters) {
|
for (param in kotlinConstructor.parameters) {
|
||||||
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
|
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
|
||||||
|
|
||||||
val matchingProperty = properties[name] ?:
|
if (name in properties) {
|
||||||
try {
|
val matchingProperty = properties[name]!!
|
||||||
clazz.getDeclaredField(param.name)
|
|
||||||
throw NotSerializableException("Property '$name' or its getter is non public, this renders class '$clazz' unserializable")
|
|
||||||
} catch (e: NoSuchFieldException) {
|
|
||||||
throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'. " +
|
|
||||||
"If using Java, check that you have the -parameters option specified in the Java compiler. " +
|
|
||||||
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the method has a getter in java.
|
// Check that the method has a getter in java.
|
||||||
val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz. " +
|
val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz. " +
|
||||||
"If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler." +
|
"If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler." +
|
||||||
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
||||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||||
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
|
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
|
||||||
rc += PropertySerializer.make(name, getter, returnType, factory)
|
rc += PropertySerializer.make(name, PublicPropertyReader(getter), returnType, factory)
|
||||||
|
} else {
|
||||||
|
throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw NotSerializableException("Property type $returnType for $name of $clazz differs from constructor parameter type ${param.type.javaType}")
|
try {
|
||||||
|
val field = (clazz.getDeclaredField(param.name))
|
||||||
|
|
||||||
|
rc += PropertySerializer.make(name, PrivatePropertyReader(field, type), field.genericType, factory)
|
||||||
|
} catch (e: NoSuchFieldException) {
|
||||||
|
throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'. " +
|
||||||
|
"If using Java, check that you have the -parameters option specified in the Java compiler. " +
|
||||||
|
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConstructorDestructorMethods(rc, emptyList())
|
return ConstructorDestructorMethods(rc, emptyList())
|
||||||
@ -130,11 +136,11 @@ private fun <T : Any> propertiesForSerializationFromConstructor(
|
|||||||
* and use those
|
* and use those
|
||||||
*/
|
*/
|
||||||
private fun propertiesForSerializationFromSetters(
|
private fun propertiesForSerializationFromSetters(
|
||||||
properties : Map<String, PropertyDescriptor>,
|
properties: Map<String, PropertyDescriptor>,
|
||||||
type: Type,
|
type: Type,
|
||||||
factory: SerializerFactory): ConstructorDestructorMethods {
|
factory: SerializerFactory): ConstructorDestructorMethods {
|
||||||
val getters : MutableList<PropertySerializer> = ArrayList(properties.size)
|
val getters: MutableList<PropertySerializer> = ArrayList(properties.size)
|
||||||
val setters : MutableList<Method?> = ArrayList(properties.size)
|
val setters: MutableList<Method?> = ArrayList(properties.size)
|
||||||
|
|
||||||
properties.forEach { property ->
|
properties.forEach { property ->
|
||||||
val getter: Method? = property.value.readMethod
|
val getter: Method? = property.value.readMethod
|
||||||
@ -146,8 +152,8 @@ private fun propertiesForSerializationFromSetters(
|
|||||||
// the getter / setter vs property as if there is a difference then that property isn't reported
|
// the getter / setter vs property as if there is a difference then that property isn't reported
|
||||||
// by the BEAN inspector and thus we don't consider that case here
|
// by the BEAN inspector and thus we don't consider that case here
|
||||||
|
|
||||||
getters += PropertySerializer.make(property.key, getter, resolveTypeVariables(getter.genericReturnType, type),
|
getters += PropertySerializer.make(property.key, PublicPropertyReader(getter),
|
||||||
factory)
|
resolveTypeVariables(getter.genericReturnType, type), factory)
|
||||||
setters += setter
|
setters += setter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,15 +165,22 @@ private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawG
|
|||||||
return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType)
|
return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun propertiesForSerializationFromAbstract(clazz: Class<*>, type: Type, factory: SerializerFactory): ConstructorDestructorMethods {
|
private fun propertiesForSerializationFromAbstract(
|
||||||
|
clazz: Class<*>,
|
||||||
|
type: Type,
|
||||||
|
factory: SerializerFactory): ConstructorDestructorMethods {
|
||||||
// Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans.
|
// Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans.
|
||||||
val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.sortedBy { it.name }.filterNot { it is IndexedPropertyDescriptor }
|
val properties = Introspector.getBeanInfo(clazz).propertyDescriptors
|
||||||
|
.filter { it.name != "class" }
|
||||||
|
.sortedBy { it.name }
|
||||||
|
.filterNot { it is IndexedPropertyDescriptor }
|
||||||
val rc: MutableList<PropertySerializer> = ArrayList(properties.size)
|
val rc: MutableList<PropertySerializer> = ArrayList(properties.size)
|
||||||
for (property in properties) {
|
for (property in properties) {
|
||||||
// Check that the method has a getter in java.
|
// Check that the method has a getter in java.
|
||||||
val getter = property.readMethod ?: throw NotSerializableException("Property has no getter method for ${property.name} of $clazz.")
|
val getter = property.readMethod ?: throw NotSerializableException(
|
||||||
|
"Property has no getter method for ${property.name} of $clazz.")
|
||||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||||
rc += PropertySerializer.make(property.name, getter, returnType, factory)
|
rc += PropertySerializer.make(property.name, PublicPropertyReader(getter), returnType, factory)
|
||||||
}
|
}
|
||||||
return ConstructorDestructorMethods(rc, emptyList())
|
return ConstructorDestructorMethods(rc, emptyList())
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||||
|
|
||||||
open val classCarpenter = ClassCarpenter(cl, whitelist)
|
open val classCarpenter = ClassCarpenter(cl, whitelist)
|
||||||
|
|
||||||
val classloader: ClassLoader
|
val classloader: ClassLoader
|
||||||
get() = classCarpenter.classloader
|
get() = classCarpenter.classloader
|
||||||
|
|
||||||
@ -381,3 +382,4 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
|||||||
override fun toString(): String = "?"
|
override fun toString(): String = "?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
|
|||||||
val constructor = constructorForDeserialization(obj.javaClass)
|
val constructor = constructorForDeserialization(obj.javaClass)
|
||||||
val props = propertiesForSerialization(constructor, obj.javaClass, factory)
|
val props = propertiesForSerialization(constructor, obj.javaClass, factory)
|
||||||
for (prop in props.getters) {
|
for (prop in props.getters) {
|
||||||
extraProperties[prop.name] = prop.readMethod!!.invoke(obj)
|
extraProperties[prop.name] = prop.propertyReader.read(obj)
|
||||||
}
|
}
|
||||||
} catch (e: NotSerializableException) {
|
} catch (e: NotSerializableException) {
|
||||||
logger.warn("Unexpected exception", e)
|
logger.warn("Unexpected exception", e)
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp;
|
||||||
|
|
||||||
|
import net.corda.nodeapi.internal.serialization.AllWhitelist;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.NotSerializableException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class JavaPrivatePropertyTests {
|
||||||
|
static class C {
|
||||||
|
private String a;
|
||||||
|
|
||||||
|
C(String a) { this.a = a; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static class C2 {
|
||||||
|
private String a;
|
||||||
|
|
||||||
|
C2(String a) { this.a = a; }
|
||||||
|
|
||||||
|
public String getA() { return a; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||||
|
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
||||||
|
SerializationOutput ser = new SerializationOutput(factory);
|
||||||
|
DeserializationInput des = new DeserializationInput(factory);
|
||||||
|
|
||||||
|
C c = new C("dripping taps");
|
||||||
|
C c2 = des.deserialize(ser.serialize(c), C.class);
|
||||||
|
|
||||||
|
assertEquals (c.a, c2.a);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Now ensure we actually got a private property serializer
|
||||||
|
//
|
||||||
|
Field f = SerializerFactory.class.getDeclaredField("serializersByDescriptor");
|
||||||
|
f.setAccessible(true);
|
||||||
|
|
||||||
|
ConcurrentHashMap<Object, AMQPSerializer<Object>> serializersByDescriptor =
|
||||||
|
(ConcurrentHashMap<Object, AMQPSerializer<Object>>) f.get(factory);
|
||||||
|
|
||||||
|
assertEquals(1, serializersByDescriptor.size());
|
||||||
|
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
|
||||||
|
assertEquals(1, cSerializer.getPropertySerializers().component1().size());
|
||||||
|
Object[] propertyReaders = cSerializer.getPropertySerializers().component1().toArray();
|
||||||
|
assertTrue (((PropertySerializer)propertyReaders[0]).getPropertyReader() instanceof PrivatePropertyReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singlePrivateWithConstructorAndGetter()
|
||||||
|
throws NotSerializableException, NoSuchFieldException, IllegalAccessException {
|
||||||
|
SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader());
|
||||||
|
SerializationOutput ser = new SerializationOutput(factory);
|
||||||
|
DeserializationInput des = new DeserializationInput(factory);
|
||||||
|
|
||||||
|
C2 c = new C2("dripping taps");
|
||||||
|
C2 c2 = des.deserialize(ser.serialize(c), C2.class);
|
||||||
|
|
||||||
|
assertEquals (c.a, c2.a);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Now ensure we actually got a private property serializer
|
||||||
|
//
|
||||||
|
Field f = SerializerFactory.class.getDeclaredField("serializersByDescriptor");
|
||||||
|
f.setAccessible(true);
|
||||||
|
ConcurrentHashMap<Object, AMQPSerializer<Object>> serializersByDescriptor =
|
||||||
|
(ConcurrentHashMap<Object, AMQPSerializer<Object>>) f.get(factory);
|
||||||
|
|
||||||
|
assertEquals(1, serializersByDescriptor.size());
|
||||||
|
ObjectSerializer cSerializer = ((ObjectSerializer)serializersByDescriptor.values().toArray()[0]);
|
||||||
|
assertEquals(1, cSerializer.getPropertySerializers().component1().size());
|
||||||
|
Object[] propertyReaders = cSerializer.getPropertySerializers().component1().toArray();
|
||||||
|
assertTrue (((PropertySerializer)propertyReaders[0]).getPropertyReader() instanceof PublicPropertyReader);
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,8 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
import net.corda.core.serialization.SerializedBytes
|
|
||||||
import org.apache.qpid.proton.codec.Data
|
import org.apache.qpid.proton.codec.Data
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
|
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
|
||||||
import java.io.NotSerializableException
|
|
||||||
|
|
||||||
fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
fun testDefaultFactory() = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||||
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
|
fun testDefaultFactoryWithWhitelist() = SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import junit.framework.TestCase.assertTrue
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.apache.qpid.proton.amqp.Symbol
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
class PrivatePropertyTests {
|
||||||
|
private val factory = testDefaultFactory()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWithOnePrivateProperty() {
|
||||||
|
data class C(private val b: String)
|
||||||
|
|
||||||
|
val c1 = C("Pants are comfortable sometimes")
|
||||||
|
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
|
||||||
|
assertEquals(c1, c2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWithOnePrivatePropertyNullableNotNull() {
|
||||||
|
data class C(private val b: String?)
|
||||||
|
|
||||||
|
val c1 = C("Pants are comfortable sometimes")
|
||||||
|
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
|
||||||
|
assertEquals(c1, c2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWithOnePrivatePropertyNullableNull() {
|
||||||
|
data class C(private val b: String?)
|
||||||
|
|
||||||
|
val c1 = C(null)
|
||||||
|
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
|
||||||
|
assertEquals(c1, c2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWithOnePublicOnePrivateProperty() {
|
||||||
|
data class C(val a: Int, private val b: Int)
|
||||||
|
|
||||||
|
val c1 = C(1, 2)
|
||||||
|
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
|
||||||
|
assertEquals(c1, c2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWithOnePublicOnePrivateProperty2() {
|
||||||
|
data class C(val a: Int, private val b: Int)
|
||||||
|
|
||||||
|
val c1 = C(1, 2)
|
||||||
|
val schemaAndBlob = SerializationOutput(factory).serializeAndReturnSchema(c1)
|
||||||
|
assertEquals(1, schemaAndBlob.schema.types.size)
|
||||||
|
|
||||||
|
val field = SerializerFactory::class.java.getDeclaredField("serializersByDescriptor")
|
||||||
|
field.isAccessible = true
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val serializersByDescriptor = field.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
|
||||||
|
|
||||||
|
val schemaDescriptor = schemaAndBlob.schema.types.first().descriptor.name
|
||||||
|
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
|
||||||
|
assertEquals(1, this.size)
|
||||||
|
assertTrue(this.first() is ObjectSerializer)
|
||||||
|
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.getters.toList()
|
||||||
|
assertEquals(2, propertySerializers.size)
|
||||||
|
// a was public so should have a synthesised getter
|
||||||
|
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
|
||||||
|
|
||||||
|
// b is private and thus won't have teh getter so we'll have reverted
|
||||||
|
// to using reflection to remove the inaccessible property
|
||||||
|
assertTrue(propertySerializers[1].propertyReader is PrivatePropertyReader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetterMakesAPublicReader() {
|
||||||
|
data class C(val a: Int, private val b: Int) {
|
||||||
|
@Suppress("UNUSED")
|
||||||
|
fun getB() = b
|
||||||
|
}
|
||||||
|
|
||||||
|
val c1 = C(1, 2)
|
||||||
|
val schemaAndBlob = SerializationOutput(factory).serializeAndReturnSchema(c1)
|
||||||
|
assertEquals(1, schemaAndBlob.schema.types.size)
|
||||||
|
|
||||||
|
val field = SerializerFactory::class.java.getDeclaredField("serializersByDescriptor")
|
||||||
|
field.isAccessible = true
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val serializersByDescriptor = field.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
|
||||||
|
|
||||||
|
val schemaDescriptor = schemaAndBlob.schema.types.first().descriptor.name
|
||||||
|
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
|
||||||
|
assertEquals(1, this.size)
|
||||||
|
assertTrue(this.first() is ObjectSerializer)
|
||||||
|
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.getters.toList()
|
||||||
|
assertEquals(2, propertySerializers.size)
|
||||||
|
|
||||||
|
// as before, a is public so we'll use the getter method
|
||||||
|
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
|
||||||
|
|
||||||
|
// the getB() getter explicitly added means we should use the "normal" public
|
||||||
|
// method reader rather than the private oen
|
||||||
|
assertTrue(propertySerializers[1].propertyReader is PublicPropertyReader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNested() {
|
||||||
|
data class Inner(private val a: Int)
|
||||||
|
data class Outer(private val i: Inner)
|
||||||
|
|
||||||
|
val c1 = Outer(Inner(1010101))
|
||||||
|
val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1))
|
||||||
|
assertEquals(c1, c2)
|
||||||
|
}
|
||||||
|
}
|
@ -35,9 +35,16 @@ fun Schema.mangleNames(names: List<String>): Schema {
|
|||||||
return Schema(types = newTypes)
|
return Schema(types = newTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom implementation of a [SerializerFactory] where we need to give it a class carpenter
|
||||||
|
* rather than have it create its own
|
||||||
|
*/
|
||||||
|
class SerializerFactoryExternalCarpenter(override val classCarpenter: ClassCarpenter)
|
||||||
|
: SerializerFactory (classCarpenter.whitelist, classCarpenter.classloader)
|
||||||
|
|
||||||
open class AmqpCarpenterBase(whitelist: ClassWhitelist) {
|
open class AmqpCarpenterBase(whitelist: ClassWhitelist) {
|
||||||
var cc = ClassCarpenter(whitelist = whitelist)
|
var cc = ClassCarpenter(whitelist = whitelist)
|
||||||
var factory = SerializerFactory(AllWhitelist, cc.classloader)
|
var factory = SerializerFactoryExternalCarpenter(cc)
|
||||||
|
|
||||||
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
|
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
|
||||||
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
|
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
|
||||||
|
@ -6,7 +6,7 @@ apply plugin: 'net.corda.plugins.publish-utils'
|
|||||||
apply plugin: 'us.kirchmeier.capsule'
|
apply plugin: 'us.kirchmeier.capsule'
|
||||||
apply plugin: 'com.jfrog.artifactory'
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
|
||||||
description 'Corda Enterprise'
|
description 'R3 Corda'
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
runtimeArtifacts.extendsFrom runtime
|
runtimeArtifacts.extendsFrom runtime
|
||||||
@ -28,7 +28,7 @@ targetCompatibility = 1.6
|
|||||||
|
|
||||||
task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) {
|
task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) {
|
||||||
applicationClass 'net.corda.node.Corda'
|
applicationClass 'net.corda.node.Corda'
|
||||||
archiveName "corda-enterprise-${corda_release_version}.jar"
|
archiveName "corda-r3-${corda_release_version}.jar"
|
||||||
applicationSource = files(
|
applicationSource = files(
|
||||||
project(':node').configurations.runtime,
|
project(':node').configurations.runtime,
|
||||||
project(':node').jar,
|
project(':node').jar,
|
||||||
|
@ -17,9 +17,10 @@ import net.corda.node.services.config.NotaryConfig
|
|||||||
import net.corda.nodeapi.internal.DevIdentityGenerator
|
import net.corda.nodeapi.internal.DevIdentityGenerator
|
||||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||||
import net.corda.nodeapi.internal.network.NotaryInfo
|
import net.corda.nodeapi.internal.network.NotaryInfo
|
||||||
import net.corda.testing.*
|
import net.corda.testing.chooseIdentity
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
|
import net.corda.testing.dummyCommand
|
||||||
import net.corda.testing.internal.IntegrationTest
|
import net.corda.testing.internal.IntegrationTest
|
||||||
import net.corda.testing.internal.IntegrationTestSchemas
|
import net.corda.testing.internal.IntegrationTestSchemas
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
@ -27,7 +28,10 @@ import net.corda.testing.node.MockNodeParameters
|
|||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import net.corda.testing.node.inMemoryH2DataSourceConfig
|
import net.corda.testing.node.inMemoryH2DataSourceConfig
|
||||||
import net.corda.testing.node.startFlow
|
import net.corda.testing.node.startFlow
|
||||||
import org.junit.*
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import org.junit.Test
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
@ -17,6 +17,11 @@ import net.corda.node.internal.StartedNode
|
|||||||
import net.corda.node.services.messaging.MessagingService
|
import net.corda.node.services.messaging.MessagingService
|
||||||
import net.corda.node.services.messaging.ReceivedMessage
|
import net.corda.node.services.messaging.ReceivedMessage
|
||||||
import net.corda.node.services.messaging.send
|
import net.corda.node.services.messaging.send
|
||||||
|
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||||
|
import net.corda.testing.*
|
||||||
|
import net.corda.node.services.messaging.*
|
||||||
|
import net.corda.testing.ALICE_NAME
|
||||||
|
import net.corda.testing.chooseIdentity
|
||||||
import net.corda.testing.ALICE_NAME
|
import net.corda.testing.ALICE_NAME
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.chooseIdentity
|
||||||
import net.corda.testing.driver.DriverDSL
|
import net.corda.testing.driver.DriverDSL
|
||||||
|
@ -187,7 +187,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
initCertificate()
|
initCertificate()
|
||||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||||
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||||
return initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database ->
|
return initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database ->
|
||||||
// TODO The fact that we need to specify an empty list of notaries just to generate our node info looks like
|
// TODO The fact that we need to specify an empty list of notaries just to generate our node info looks like
|
||||||
// a code smell.
|
// a code smell.
|
||||||
val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList())
|
val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList())
|
||||||
|
@ -265,6 +265,9 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
printBasicNodeInfo("Database connection url is", "jdbc:h2:$url/node")
|
printBasicNodeInfo("Database connection url is", "jdbc:h2:$url/node")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (databaseUrl != null) {
|
||||||
|
printBasicNodeInfo("Database connection url is", databaseUrl)
|
||||||
|
}
|
||||||
return super.initialiseDatabasePersistence(schemaService, identityService, insideTransaction)
|
return super.initialiseDatabasePersistence(schemaService, identityService, insideTransaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
package net.corda.node.services.config
|
package net.corda.node.services.config
|
||||||
|
|
||||||
import com.typesafe.config.*
|
|
||||||
import net.corda.core.crypto.Crypto
|
|
||||||
import net.corda.core.crypto.SignatureScheme
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import com.typesafe.config.ConfigParseOptions
|
import com.typesafe.config.ConfigParseOptions
|
||||||
|
@ -36,13 +36,8 @@ fun freshCertificate(identityService: IdentityServiceInternal,
|
|||||||
require(issuerRole == CertRole.LEGAL_IDENTITY) { "Confidential identities can only be issued from well known identities, provided issuer ${issuer.name} has role $issuerRole" }
|
require(issuerRole == CertRole.LEGAL_IDENTITY) { "Confidential identities can only be issued from well known identities, provided issuer ${issuer.name} has role $issuerRole" }
|
||||||
val issuerCert = issuer.certificate
|
val issuerCert = issuer.certificate
|
||||||
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert)
|
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert)
|
||||||
val ourCertificate = X509Utilities.createCertificate(
|
val ourCertificate = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuerCert.subjectX500Principal,
|
||||||
CertificateType.CONFIDENTIAL_LEGAL_IDENTITY,
|
issuerSigner, issuer.name.x500Principal, subjectPublicKey, window)
|
||||||
issuerCert.subjectX500Principal,
|
|
||||||
issuerSigner,
|
|
||||||
issuer.name.x500Principal,
|
|
||||||
subjectPublicKey,
|
|
||||||
window)
|
|
||||||
val ourCertPath = X509CertificateFactory().generateCertPath(listOf(ourCertificate) + issuer.certPath.certificates)
|
val ourCertPath = X509CertificateFactory().generateCertPath(listOf(ourCertificate) + issuer.certPath.certificates)
|
||||||
val anonymisedIdentity = PartyAndCertificate(ourCertPath)
|
val anonymisedIdentity = PartyAndCertificate(ourCertPath)
|
||||||
identityService.justVerifyAndRegisterIdentity(anonymisedIdentity)
|
identityService.justVerifyAndRegisterIdentity(anonymisedIdentity)
|
||||||
|
@ -8,7 +8,8 @@ import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
|||||||
import org.hibernate.Session
|
import org.hibernate.Session
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.Date
|
import java.time.LocalDateTime
|
||||||
|
import java.time.temporal.ChronoField
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@ -47,7 +48,7 @@ class RunOnceService(private val database: CordaPersistence, private val machine
|
|||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = TABLE)
|
@Table(name = TABLE)
|
||||||
class MutualExclusion(machineNameInit: String, pidInit: String) {
|
class MutualExclusion(machineNameInit: String, pidInit: String, timeStampInit: LocalDateTime) {
|
||||||
@Column(name = ID, insertable = false, updatable = false)
|
@Column(name = ID, insertable = false, updatable = false)
|
||||||
@Id
|
@Id
|
||||||
val id: Char = 'X'
|
val id: Char = 'X'
|
||||||
@ -59,8 +60,7 @@ class RunOnceService(private val database: CordaPersistence, private val machine
|
|||||||
val pid = pidInit
|
val pid = pidInit
|
||||||
|
|
||||||
@Column(name = TIMESTAMP)
|
@Column(name = TIMESTAMP)
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
val timestamp = timeStampInit
|
||||||
val timestamp: Date? = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
@ -130,7 +130,7 @@ class RunOnceService(private val database: CordaPersistence, private val machine
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateTimestamp(session: Session, mutualExclusion: MutualExclusion): Boolean {
|
private fun updateTimestamp(session: Session, mutualExclusion: MutualExclusion): Boolean {
|
||||||
val minWaitTime = simpleDateFormat.format(Date(mutualExclusion.timestamp!!.time + waitInterval))
|
val minWaitTime = mutualExclusion.timestamp.plus(waitInterval, ChronoField.MILLI_OF_SECOND.baseUnit)
|
||||||
val query = session.createNativeQuery("UPDATE $TABLE SET $MACHINE_NAME = :machineName, $TIMESTAMP = CURRENT_TIMESTAMP, $PID = :pid " +
|
val query = session.createNativeQuery("UPDATE $TABLE SET $MACHINE_NAME = :machineName, $TIMESTAMP = CURRENT_TIMESTAMP, $PID = :pid " +
|
||||||
"WHERE $ID = 'X' AND " +
|
"WHERE $ID = 'X' AND " +
|
||||||
|
|
||||||
|
@ -167,11 +167,7 @@ class InMemoryIdentityServiceTests {
|
|||||||
val issuerKeyPair = generateKeyPair()
|
val issuerKeyPair = generateKeyPair()
|
||||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
||||||
val txKeyPair = Crypto.generateKeyPair()
|
val txKeyPair = Crypto.generateKeyPair()
|
||||||
val txCert = X509Utilities.createCertificate(
|
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuer.certificate, issuerKeyPair, x500Name.x500Principal,
|
||||||
CertificateType.CONFIDENTIAL_LEGAL_IDENTITY,
|
|
||||||
issuer.certificate,
|
|
||||||
issuerKeyPair,
|
|
||||||
x500Name.x500Principal,
|
|
||||||
txKeyPair.public)
|
txKeyPair.public)
|
||||||
val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert) + issuer.certPath.certificates)
|
val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert) + issuer.certPath.certificates)
|
||||||
return Pair(issuer, PartyAndCertificate(txCertPath))
|
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||||
|
@ -14,7 +14,7 @@ import org.junit.Before
|
|||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.contrib.java.lang.system.ExpectedSystemExit
|
import org.junit.contrib.java.lang.system.ExpectedSystemExit
|
||||||
import java.util.*
|
import java.time.LocalDateTime
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
import java.util.concurrent.ScheduledFuture
|
import java.util.concurrent.ScheduledFuture
|
||||||
import javax.persistence.Query
|
import javax.persistence.Query
|
||||||
@ -64,7 +64,9 @@ class RunOnceServiceTest {
|
|||||||
fun `row updated when change of master node`() {
|
fun `row updated when change of master node`() {
|
||||||
runOnceServiceMachine1.start()
|
runOnceServiceMachine1.start()
|
||||||
|
|
||||||
var firstTimestamp: Date? = null
|
var secondTimestamp = LocalDateTime.now()
|
||||||
|
var firstTimestamp = LocalDateTime.now()
|
||||||
|
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
||||||
val result = machine1RowCheck(query)
|
val result = machine1RowCheck(query)
|
||||||
@ -76,14 +78,13 @@ class RunOnceServiceTest {
|
|||||||
|
|
||||||
runOnceServiceMachine2.start()
|
runOnceServiceMachine2.start()
|
||||||
|
|
||||||
var secondTimestamp: Date? = null
|
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
||||||
val result = machine2RowCheck(query)
|
val result = machine2RowCheck(query)
|
||||||
secondTimestamp = result.timestamp
|
secondTimestamp = result.timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(secondTimestamp!!.toInstant().isAfter(firstTimestamp!!.toInstant()))
|
assertTrue(secondTimestamp.isAfter(firstTimestamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -105,7 +106,9 @@ class RunOnceServiceTest {
|
|||||||
fun `row updated when last run was same machine`() {
|
fun `row updated when last run was same machine`() {
|
||||||
runOnceServiceMachine1.start()
|
runOnceServiceMachine1.start()
|
||||||
|
|
||||||
var firstTimestamp: Date? = null
|
var secondTimestamp = LocalDateTime.now()
|
||||||
|
var firstTimestamp = LocalDateTime.now()
|
||||||
|
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
||||||
val result = machine1RowCheck(query)
|
val result = machine1RowCheck(query)
|
||||||
@ -117,14 +120,13 @@ class RunOnceServiceTest {
|
|||||||
|
|
||||||
runOnceServiceMachine1.start()
|
runOnceServiceMachine1.start()
|
||||||
|
|
||||||
var secondTimestamp: Date? = null
|
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
||||||
val result = machine1RowCheck(query)
|
val result = machine1RowCheck(query)
|
||||||
secondTimestamp = result.timestamp
|
secondTimestamp = result.timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(secondTimestamp!!.toInstant().isAfter(firstTimestamp!!.toInstant()))
|
assertTrue(secondTimestamp.isAfter(firstTimestamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -132,7 +134,9 @@ class RunOnceServiceTest {
|
|||||||
whenever(mockUpdateExecutor.scheduleAtFixedRate(any(), any(), any(), any())).thenAnswer { invocation ->
|
whenever(mockUpdateExecutor.scheduleAtFixedRate(any(), any(), any(), any())).thenAnswer { invocation ->
|
||||||
val runnable = invocation.arguments[0] as Runnable
|
val runnable = invocation.arguments[0] as Runnable
|
||||||
|
|
||||||
var firstTimestamp: Date? = null
|
var secondTimestamp = LocalDateTime.now()
|
||||||
|
var firstTimestamp = LocalDateTime.now()
|
||||||
|
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
||||||
val result = machine1RowCheck(query)
|
val result = machine1RowCheck(query)
|
||||||
@ -141,14 +145,13 @@ class RunOnceServiceTest {
|
|||||||
|
|
||||||
runnable.run()
|
runnable.run()
|
||||||
|
|
||||||
var secondTimestamp: Date? = null
|
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
val query = session.createNativeQuery(selectQuery, RunOnceService.MutualExclusion::class.java)
|
||||||
val result = machine1RowCheck(query)
|
val result = machine1RowCheck(query)
|
||||||
secondTimestamp = result.timestamp
|
secondTimestamp = result.timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(secondTimestamp!!.toInstant().isAfter(firstTimestamp!!.toInstant()))
|
assertTrue(secondTimestamp.isAfter(firstTimestamp))
|
||||||
|
|
||||||
mock<ScheduledFuture<*>>()
|
mock<ScheduledFuture<*>>()
|
||||||
}
|
}
|
||||||
@ -228,7 +231,7 @@ class RunOnceServiceTest {
|
|||||||
assertEquals('X', result.id)
|
assertEquals('X', result.id)
|
||||||
assertEquals("machine1", result.machineName)
|
assertEquals("machine1", result.machineName)
|
||||||
assertEquals("123", result.pid)
|
assertEquals("123", result.pid)
|
||||||
assertTrue(result.timestamp is Date)
|
assertTrue(result.timestamp is LocalDateTime)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +241,7 @@ class RunOnceServiceTest {
|
|||||||
assertEquals('X', result.id)
|
assertEquals('X', result.id)
|
||||||
assertEquals("machine2", result.machineName)
|
assertEquals("machine2", result.machineName)
|
||||||
assertEquals("789", result.pid)
|
assertEquals("789", result.pid)
|
||||||
assertTrue(result.timestamp is Date)
|
assertTrue(result.timestamp is LocalDateTime)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,9 +8,9 @@ import net.corda.core.utilities.NetworkHostAndPort
|
|||||||
import net.corda.node.services.config.NotaryConfig
|
import net.corda.node.services.config.NotaryConfig
|
||||||
import net.corda.node.services.config.RaftConfig
|
import net.corda.node.services.config.RaftConfig
|
||||||
import net.corda.nodeapi.internal.DevIdentityGenerator
|
import net.corda.nodeapi.internal.DevIdentityGenerator
|
||||||
import net.corda.testing.node.internal.demorun.*
|
|
||||||
import net.corda.testing.ALICE_NAME
|
import net.corda.testing.ALICE_NAME
|
||||||
import net.corda.testing.BOB_NAME
|
import net.corda.testing.BOB_NAME
|
||||||
|
import net.corda.testing.node.internal.demorun.*
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
fun main(args: Array<String>) = RaftNotaryCordform().deployNodes()
|
fun main(args: Array<String>) = RaftNotaryCordform().deployNodes()
|
||||||
|
@ -52,7 +52,7 @@ allprojects {
|
|||||||
systemProperties["attestation.home"] = "$buildDir/logs"
|
systemProperties["attestation.home"] = "$buildDir/logs"
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'com.r3.corda.enterprise'
|
group 'com.r3.corda'
|
||||||
version '1.0-SNAPSHOT'
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -5,7 +5,6 @@ import com.google.common.jimfs.Jimfs
|
|||||||
import com.nhaarman.mockito_kotlin.doReturn
|
import com.nhaarman.mockito_kotlin.doReturn
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
import net.corda.core.crypto.entropyToKeyPair
|
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
@ -110,6 +110,9 @@ Now choose `File/Open` from the main menu, and select the profile that you have
|
|||||||
just saved. DemoBench should close the two existing tabs and then relaunch the
|
just saved. DemoBench should close the two existing tabs and then relaunch the
|
||||||
Notary and Bank nodes.
|
Notary and Bank nodes.
|
||||||
|
|
||||||
|
If you want to edit the content, please make sure that when zipping content back,
|
||||||
|
the notary node is the first directory (ZIP files content is ordered)
|
||||||
|
|
||||||
## Exiting DemoBench
|
## Exiting DemoBench
|
||||||
|
|
||||||
Close DemoBench as a normal application on your platform; it should close any
|
Close DemoBench as a normal application on your platform; it should close any
|
||||||
|
@ -10,6 +10,7 @@ import net.corda.nodeapi.internal.config.User
|
|||||||
import net.corda.nodeapi.internal.config.toConfig
|
import net.corda.nodeapi.internal.config.toConfig
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
|
import java.util.Properties
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a subset of FullNodeConfiguration, containing only those configs which we need. The node uses reference.conf
|
* This is a subset of FullNodeConfiguration, containing only those configs which we need. The node uses reference.conf
|
||||||
@ -25,7 +26,10 @@ data class NodeConfig(
|
|||||||
val h2port: Int,
|
val h2port: Int,
|
||||||
val rpcUsers: List<User> = listOf(defaultUser),
|
val rpcUsers: List<User> = listOf(defaultUser),
|
||||||
/** This is an extra config used by the Cash app. */
|
/** This is an extra config used by the Cash app. */
|
||||||
val issuableCurrencies: List<String> = emptyList()
|
val issuableCurrencies: List<String> = emptyList(),
|
||||||
|
/** Pass-through for generating node.conf with external DB */
|
||||||
|
val dataSourceProperties: Properties? = null,
|
||||||
|
val database: Properties? = null
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
|
val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
|
||||||
|
@ -10,10 +10,10 @@ import net.corda.core.internal.noneOrSingle
|
|||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.demobench.plugin.CordappController
|
import net.corda.demobench.plugin.CordappController
|
||||||
import net.corda.demobench.pty.R3Pty
|
import net.corda.demobench.pty.R3Pty
|
||||||
|
import net.corda.nodeapi.internal.DevIdentityGenerator
|
||||||
import net.corda.nodeapi.internal.network.NetworkParameters
|
import net.corda.nodeapi.internal.network.NetworkParameters
|
||||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||||
import net.corda.nodeapi.internal.network.NotaryInfo
|
import net.corda.nodeapi.internal.network.NotaryInfo
|
||||||
import net.corda.nodeapi.internal.DevIdentityGenerator
|
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.lang.management.ManagementFactory
|
import java.lang.management.ManagementFactory
|
||||||
|
@ -34,16 +34,44 @@ The Participant nodes are only able to spend cash (eg. move cash).
|
|||||||
|
|
||||||
**These Corda nodes will be created on the following port on localhost.**
|
**These Corda nodes will be created on the following port on localhost.**
|
||||||
|
|
||||||
* Notary -> 20003 (Does not accept logins)
|
* Notary -> 20001 (Does not accept logins)
|
||||||
* Alice -> 20006
|
* Alice -> 20004
|
||||||
* Bob -> 20009
|
* Bob -> 20007
|
||||||
* UK Bank Plc -> 20012 (*Issuer node*)
|
* UK Bank Plc -> 20010 (*Issuer node*)
|
||||||
* USA Bank Corp -> 20015 (*Issuer node*)
|
* USA Bank Corp -> 20013 (*Issuer node*)
|
||||||
|
|
||||||
Explorer login credentials to the Issuer nodes are defaulted to ``manager`` and ``test``.
|
Explorer login credentials to the Issuer nodes are defaulted to ``manager`` and ``test``.
|
||||||
Explorer login credentials to the Participants nodes are defaulted to ``user1`` and ``test``.
|
Explorer login credentials to the Participant nodes are defaulted to ``user1`` and ``test``.
|
||||||
Please note you are not allowed to login to the notary.
|
Please note you are not allowed to login to the notary.
|
||||||
|
|
||||||
|
## Running Simulation Nodes
|
||||||
|
|
||||||
|
Building on the demonstration Corda network topology described above, simulation mode performs continuous
|
||||||
|
issue, move and exit cash operations across all participant nodes.
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
|
||||||
|
gradlew.bat tools:explorer:runSimulationNodes
|
||||||
|
|
||||||
|
**Other:**
|
||||||
|
|
||||||
|
./gradlew tools:explorer:runSimulationNodes
|
||||||
|
|
||||||
|
## Running Flow Triage scenario
|
||||||
|
|
||||||
|
Once again, building on the demonstration Corda network topology described above, this scenario mode triggers
|
||||||
|
an exception within a flow which can then be visualized using the "Flow Triage" panel within the Explorer.
|
||||||
|
The "Flow Triage" panel will be enhanced in the future to enable operators to take corrective actions upon flow failures
|
||||||
|
(eg. retry, terminate, amend and replay)
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
|
||||||
|
gradlew.bat tools:explorer:runFlowTriageNodes
|
||||||
|
|
||||||
|
**Other:**
|
||||||
|
|
||||||
|
./gradlew tools:explorer:runFlowTriageNodes
|
||||||
|
|
||||||
## Business Network reference implementation
|
## Business Network reference implementation
|
||||||
|
|
||||||
An additional "IOU" panel is now visible in the main Explorer dashboard to demonstrate the new Business Networks concept using a sample IOU product.
|
An additional "IOU" panel is now visible in the main Explorer dashboard to demonstrate the new Business Networks concept using a sample IOU product.
|
||||||
@ -71,4 +99,4 @@ Please note Business Networks functionality only applies to the same IOU CorDapp
|
|||||||
- Support other contract types.
|
- Support other contract types.
|
||||||
|
|
||||||
|
|
||||||
More information can be found in the [Project website](https://corda.net) and [Documentation](https://docs.corda.net).
|
More information can be found in the [Project website](https://corda.net) and [Documentation](https://docs.corda.net).
|
@ -65,10 +65,10 @@ class Main : App(MainView::class) {
|
|||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
stage.hide()
|
stage.hide()
|
||||||
loginView.login()
|
loginView.login()
|
||||||
addOptionalViews()
|
|
||||||
(find(primaryView) as MainView).initializeControls()
|
|
||||||
stage.show()
|
|
||||||
}
|
}
|
||||||
|
addOptionalViews()
|
||||||
|
(find(primaryView) as MainView).initializeControls()
|
||||||
|
stage.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addOptionalViews() {
|
private fun addOptionalViews() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user