mirror of
https://github.com/corda/corda.git
synced 2024-12-28 16:58:55 +00:00
Merge pull request #424 from corda/pat/os-enterprise-merge
os to enterprise merge
This commit is contained in:
commit
4d78d51663
@ -60,7 +60,7 @@ buildscript {
|
|||||||
ext.rxjava_version = '1.2.4'
|
ext.rxjava_version = '1.2.4'
|
||||||
ext.dokka_version = '0.9.16-eap-2'
|
ext.dokka_version = '0.9.16-eap-2'
|
||||||
ext.eddsa_version = '0.2.0'
|
ext.eddsa_version = '0.2.0'
|
||||||
ext.dependency_checker_version = '3.0.1'
|
ext.dependency_checker_version = '3.1.0'
|
||||||
ext.commons_collections_version = '4.1'
|
ext.commons_collections_version = '4.1'
|
||||||
ext.beanutils_version = '1.9.3'
|
ext.beanutils_version = '1.9.3'
|
||||||
ext.crash_version = 'cce5a00f114343c1145c1d7756e1dd6df3ea984e'
|
ext.crash_version = 'cce5a00f114343c1145c1d7756e1dd6df3ea984e'
|
||||||
|
@ -11,6 +11,7 @@ CorDapps
|
|||||||
cordapp-build-systems
|
cordapp-build-systems
|
||||||
building-against-master
|
building-against-master
|
||||||
corda-api
|
corda-api
|
||||||
|
serialization
|
||||||
secure-coding-guidelines
|
secure-coding-guidelines
|
||||||
flow-cookbook
|
flow-cookbook
|
||||||
cheat-sheet
|
cheat-sheet
|
||||||
|
@ -51,6 +51,10 @@ The ``cordformation`` plugin adds two new gradle configurations:
|
|||||||
* ``cordaCompile``, which extends ``compile``
|
* ``cordaCompile``, which extends ``compile``
|
||||||
* ``cordaRuntime``, which extends ``runtime``
|
* ``cordaRuntime``, which extends ``runtime``
|
||||||
|
|
||||||
|
``cordaCompile`` and ``cordaRuntime`` indicate dependencies that should not be included in the CorDapp JAR. These
|
||||||
|
configurations should be used for any Corda dependency (e.g. ``corda-core``, ``corda-node``) in order to prevent a
|
||||||
|
dependency from being included twice (once in the CorDapp JAR and once in the Corda JARs).
|
||||||
|
|
||||||
To build against Corda, you must add the following to your ``build.gradle`` file:
|
To build against Corda, you must add the following to your ``build.gradle`` file:
|
||||||
|
|
||||||
* ``net.corda:corda:$corda_release_version`` as a ``cordaRuntime`` dependency
|
* ``net.corda:corda:$corda_release_version`` as a ``cordaRuntime`` dependency
|
||||||
@ -73,6 +77,15 @@ ways to add another CorDapp as a dependency in your CorDapp's ``build.gradle`` f
|
|||||||
* ``cordapp project(":another-cordapp")`` (use this if the other CorDapp is defined in a module in the same project)
|
* ``cordapp project(":another-cordapp")`` (use this if the other CorDapp is defined in a module in the same project)
|
||||||
* ``cordapp "net.corda:another-cordapp:1.0"`` (use this otherwise)
|
* ``cordapp "net.corda:another-cordapp:1.0"`` (use this otherwise)
|
||||||
|
|
||||||
|
The ``cordapp`` gradle configuration serves two purposes:
|
||||||
|
|
||||||
|
* When using the ``cordformation`` Gradle plugin, the ``cordapp`` configuration indicates that this JAR should be
|
||||||
|
included on your node as a CorDapp
|
||||||
|
* When using the ``cordapp`` Gradle plugin, the ``cordapp`` configuration prevents the dependency from being included
|
||||||
|
in the CorDapp JAR
|
||||||
|
|
||||||
|
Note that the ``cordformation`` and ``cordapp`` Gradle plugins can be used together.
|
||||||
|
|
||||||
Other dependencies
|
Other dependencies
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
If your CorDapps have any additional external dependencies, they can be specified like normal Kotlin/Java dependencies
|
If your CorDapps have any additional external dependencies, they can be specified like normal Kotlin/Java dependencies
|
||||||
@ -143,4 +156,4 @@ Installing the CorDapp JAR
|
|||||||
|
|
||||||
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
|
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
|
||||||
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
|
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
|
||||||
the node's JAR and configuration files are stored.
|
the node's JAR and configuration files are stored.
|
||||||
|
@ -6,5 +6,4 @@ Node internals
|
|||||||
|
|
||||||
node-services
|
node-services
|
||||||
vault
|
vault
|
||||||
serialization
|
|
||||||
messaging
|
messaging
|
||||||
|
@ -1,15 +1,41 @@
|
|||||||
|
.. highlight:: kotlin
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||||
|
|
||||||
Object serialization
|
Object serialization
|
||||||
====================
|
====================
|
||||||
|
|
||||||
.. contents::
|
.. contents::
|
||||||
|
|
||||||
What is serialization (and deserialization)?
|
Introduction
|
||||||
--------------------------------------------
|
------------
|
||||||
|
|
||||||
Object serialization is the process of converting objects into a stream of bytes and, deserialization, the reverse
|
Object serialization is the process of converting objects into a stream of bytes and, deserialization, the reverse
|
||||||
process of creating objects from a stream of bytes. It takes place every time nodes pass objects to each other as
|
process of creating objects from a stream of bytes. It takes place every time nodes pass objects to each other as
|
||||||
messages, when objects are sent to or from RPC clients from the node, and when we store transactions in the database.
|
messages, when objects are sent to or from RPC clients from the node, and when we store transactions in the database.
|
||||||
|
|
||||||
|
Corda pervasively uses a custom form of type safe binary serialisation. This stands in contrast to some other systems that use
|
||||||
|
weakly or untyped string-based serialisation schemes like JSON or XML. The primary drivers for this were:
|
||||||
|
|
||||||
|
* A desire to have a schema describing what has been serialized alongside the actual data:
|
||||||
|
|
||||||
|
#. To assist with versioning, both in terms of being able to interpret data archived long ago (e.g. trades from
|
||||||
|
a decade ago, long after the code has changed) and between differing code versions.
|
||||||
|
#. To make it easier to write generic code e.g. user interfaces that can navigate the serialized form of data.
|
||||||
|
#. To support cross platform (non-JVM) interaction, where the format of a class file is not so easily interpreted.
|
||||||
|
|
||||||
|
* A desire to use a documented and static wire format that is platform independent, and is not subject to change with
|
||||||
|
3rd party library upgrades, etc.
|
||||||
|
* A desire to support open-ended polymorphism, where the number of subclasses of a superclass can expand over time
|
||||||
|
and the subclasses do not need to be defined in the schema *upfront*. This is key to many Corda concepts, such as states.
|
||||||
|
* Increased security by constructing deserialized objects through supported constructors, rather than having
|
||||||
|
data inserted directly into their fields without an opportunity to validate consistency or intercept attempts to manipulate
|
||||||
|
supposed invariants.
|
||||||
|
* Binary formats work better with digital signatures than text based formats, as there's much less scope for
|
||||||
|
changes that modify syntax but not semantics.
|
||||||
|
|
||||||
Whitelisting
|
Whitelisting
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@ -49,46 +75,28 @@ It's reproduced here as an example of both ways you can do this for a couple of
|
|||||||
.. _amqp_ref:
|
.. _amqp_ref:
|
||||||
|
|
||||||
AMQP
|
AMQP
|
||||||
====
|
----
|
||||||
|
|
||||||
Originally Corda used a ``Kryo``-based serialization scheme throughout for all serialization contexts. However, it was realised there
|
Corda uses an extended form of AMQP 1.0 as its binary wire protocol.
|
||||||
was a compelling use case for the definition and development of a custom format based upon AMQP 1.0. The primary drivers for this were:
|
|
||||||
|
|
||||||
#. A desire to have a schema describing what has been serialized alongside the actual data:
|
Corda serialisation is currently used for:
|
||||||
|
|
||||||
#. To assist with versioning, both in terms of being able to interpret data archived long ago (e.g. trades from
|
#. Peer-to-peer networking.
|
||||||
a decade ago, long after the code has changed) and between differing code versions
|
#. Persisted messages, like signed transactions and states.
|
||||||
#. To make it easier to write user interfaces that can navigate the serialized form of data
|
|
||||||
#. To support cross platform (non-JVM) interaction, where the format of a class file is not so easily interpreted
|
|
||||||
#. A desire to use a documented and static wire format that is platform independent, and is not subject to change with
|
|
||||||
3rd party library upgrades, etc.
|
|
||||||
#. A desire to support open-ended polymorphism, where the number of subclasses of a superclass can expand over time
|
|
||||||
and the subclasses do not need to be defined in the schema *upfront*. This is key to many Corda concepts, such as states.
|
|
||||||
#. Increased security by constructing deserialized objects through supported constructors, rather than having
|
|
||||||
data inserted directly into their fields without an opportunity to validate consistency or intercept attempts to manipulate
|
|
||||||
supposed invariants
|
|
||||||
|
|
||||||
Delivering this is an ongoing effort by the Corda development team. At present, the ``Kryo``-based format is still used by the RPC framework on
|
.. note:: At present, the Kryo-based format is still used by the RPC framework on both the client and server side. However, it is planned that the RPC framework will move to the AMQP framework soon.
|
||||||
both the client and server side. However, it is planned that the RPC framework will move to the AMQP framework when ready.
|
|
||||||
|
|
||||||
The AMQP framework is currently used for:
|
For the checkpointing of flows Corda uses a private scheme that is subject to change. It is currently based on the Kryo
|
||||||
|
framework, but this may not be true in future.
|
||||||
#. The peer-to-peer context, representing inter-node communication
|
|
||||||
#. The persistence layer, representing contract states persisted into the vault
|
|
||||||
|
|
||||||
Finally, for the checkpointing of flows, Corda will continue to use the existing ``Kryo`` scheme.
|
|
||||||
|
|
||||||
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. ``Kryo`` is more suited to the serialization of a program's stack frames, as it is more flexible
|
attempting to force a one-size-fits-all approach. Kryo is more suited to the serialization of a program's stack frames, as it is more flexible
|
||||||
than our AMQP framework in what it can construct and serialize. However, that flexibility makes it exceptionally difficult to make secure. Conversely,
|
than our AMQP framework in what it can construct and serialize. However, that flexibility makes it exceptionally difficult to make secure. Conversely,
|
||||||
our AMQP framework allows us to concentrate on a secure framework that can be reasoned about and thus made safer, with far fewer
|
our AMQP framework allows us to concentrate on a secure framework that can be reasoned about and thus made safer, with far fewer
|
||||||
security holes.
|
security holes.
|
||||||
|
|
||||||
.. note:: Selection of serialization context should, for the most part, be opaque to CorDapp developers, the Corda framework selecting
|
Selection of serialization context should, for the most part, be opaque to CorDapp developers, the Corda framework selecting
|
||||||
the correct context as configured.
|
the correct context as configured.
|
||||||
|
|
||||||
.. note:: For information on our choice of AMQP 1.0, see :doc:`amqp-choice`. For detail on how we utilise AMQP 1.0 and represent
|
|
||||||
objects in AMQP types, see :doc:`amqp-format`.
|
|
||||||
|
|
||||||
This document describes what is currently and what will be supported in the Corda AMQP format from the perspective
|
This document describes what is currently and what will be supported in the Corda AMQP format from the perspective
|
||||||
of CorDapp developers, to allow CorDapps to take into consideration the future state. The AMQP serialization format will
|
of CorDapp developers, to allow CorDapps to take into consideration the future state. The AMQP serialization format will
|
||||||
@ -102,8 +110,9 @@ This section describes the classes and interfaces that the AMQP serialization fo
|
|||||||
Collection Types
|
Collection Types
|
||||||
````````````````
|
````````````````
|
||||||
|
|
||||||
The following collection types are supported. Any implementation of the following will be mapped to *an* implementation of the interface or class on the other end.
|
The following collection types are supported. Any implementation of the following will be mapped to *an* implementation
|
||||||
For example, if you use a Guava implementation of a collection, it will deserialize as the primitive collection type.
|
of the interface or class on the other end. For example, if you use a Guava implementation of a collection, it will
|
||||||
|
deserialize as the primitive collection type.
|
||||||
|
|
||||||
The declared types of properties should only use these types, and not any concrete implementation types (e.g.
|
The declared types of properties should only use these types, and not any concrete implementation types (e.g.
|
||||||
Guava implementations). Collections must specify their generic type, the generic type parameters will be included in
|
Guava implementations). Collections must specify their generic type, the generic type parameters will be included in
|
||||||
@ -240,7 +249,7 @@ General Rules
|
|||||||
|
|
||||||
.. note:: In circumstances where classes cannot be recompiled, such as when using a third-party library, a
|
.. note:: In circumstances where classes cannot be recompiled, such as when using a third-party library, a
|
||||||
proxy serializer can be used to avoid this problem. Details on creating such an object can be found on the
|
proxy serializer can be used to avoid this problem. Details on creating such an object can be found on the
|
||||||
:doc:`cordapp-custom-serializers` page.
|
:doc:`cordapp-custom-serializers` page.
|
||||||
|
|
||||||
#. The class must be annotated with ``@CordaSerializable``
|
#. The class must be annotated with ``@CordaSerializable``
|
||||||
#. The declared types of constructor arguments, getters, and setters must be supported, and where generics are used, the
|
#. The declared types of constructor arguments, getters, and setters must be supported, and where generics are used, the
|
||||||
@ -311,21 +320,28 @@ For example:
|
|||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
.. sourcecode:: Java
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
class Example {
|
class Example(var a: Int, var b: Int, var c: Int)
|
||||||
private int a;
|
|
||||||
private int b;
|
|
||||||
private int c;
|
|
||||||
|
|
||||||
public int getA() { return a; }
|
.. sourcecode:: java
|
||||||
public int getB() { return b; }
|
|
||||||
public int getC() { return c; }
|
|
||||||
|
|
||||||
public void setA(int a) { this.a = a; }
|
class Example {
|
||||||
public void setB(int b) { this.b = b; }
|
private int a;
|
||||||
public void setC(int c) { this.c = c; }
|
private int b;
|
||||||
}
|
private int c;
|
||||||
|
|
||||||
|
public int getA() { return a; }
|
||||||
|
public int getB() { return b; }
|
||||||
|
public int getC() { return c; }
|
||||||
|
|
||||||
|
public void setA(int a) { this.a = a; }
|
||||||
|
public void setB(int b) { this.b = b; }
|
||||||
|
public void setC(int c) { this.c = c; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.. warning:: We do not recommend this pattern! Corda tries to use immutable data structures throughout, and if you
|
||||||
|
rely heavily on mutable JavaBean style objects then you may sometimes find the API behaves in unintuitive ways.
|
||||||
|
|
||||||
Inaccessible Private Properties
|
Inaccessible Private Properties
|
||||||
```````````````````````````````
|
```````````````````````````````
|
||||||
@ -335,30 +351,26 @@ accessible getter methods, this development idiom is strongly discouraged.
|
|||||||
|
|
||||||
For example.
|
For example.
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
Kotlin:
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
class C(val a: Int, private val b: Int)
|
||||||
|
|
||||||
data class C(val a: Int, private val b: Int)
|
.. sourcecode:: java
|
||||||
|
|
||||||
Java:
|
class C {
|
||||||
|
public Integer a;
|
||||||
|
private Integer b;
|
||||||
|
|
||||||
.. sourcecode:: Java
|
public C(Integer a, Integer b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class C {
|
When designing Corda states, it should be remembered that they are not, despite appearances, traditional
|
||||||
public Integer a;
|
OOP style objects. They are signed over, transformed, serialised, and relationally mapped. As such,
|
||||||
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.
|
all elements should be publicly accessible by design.
|
||||||
|
|
||||||
.. warning:: IDEs will indicate erroneously that properties can be given something other than public visibility. Ignore
|
.. warning:: IDEs will indicate erroneously that properties can be given something other than public visibility. Ignore
|
||||||
@ -366,40 +378,38 @@ all elements should be publicly accessible by design.
|
|||||||
|
|
||||||
Providing a public getter, as per the following example, is acceptable:
|
Providing a public getter, as per the following example, is acceptable:
|
||||||
|
|
||||||
.. container:: codeset
|
.. container:: codeset
|
||||||
|
|
||||||
Kotlin:
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
.. sourcecode:: kotlin
|
class C(val a: Int, b: Int) {
|
||||||
|
var b: Int = b
|
||||||
|
private set
|
||||||
|
}
|
||||||
|
|
||||||
data class C(val a: Int, private val b: Int) {
|
.. sourcecode:: java
|
||||||
public fun getB() = b
|
|
||||||
}
|
|
||||||
|
|
||||||
Java:
|
class C {
|
||||||
|
public Integer a;
|
||||||
|
private Integer b;
|
||||||
|
|
||||||
.. sourcecode:: Java
|
C(Integer a, Integer b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
class C {
|
public Integer getB() {
|
||||||
public Integer a;
|
return b;
|
||||||
private Integer b;
|
}
|
||||||
|
}
|
||||||
C(Integer a, Integer b) {
|
|
||||||
this.a = a;
|
|
||||||
this.b = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getB() {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Enums
|
Enums
|
||||||
`````
|
`````
|
||||||
|
|
||||||
#. All enums are supported, provided they are annotated with ``@CordaSerializable``
|
All enums are supported, provided they are annotated with ``@CordaSerializable``. Corda supports interoperability of
|
||||||
|
enumerated type versions. This allows such types to be changed over time without breaking backward (or forward)
|
||||||
|
compatibility. The rules and mechanisms for doing this are discussed in :doc:`serialization-enum-evolution`.
|
||||||
|
|
||||||
Exceptions
|
Exceptions
|
||||||
``````````
|
``````````
|
||||||
@ -414,24 +424,23 @@ The following rules apply to supported ``Throwable`` implementations.
|
|||||||
Kotlin Objects
|
Kotlin Objects
|
||||||
``````````````
|
``````````````
|
||||||
|
|
||||||
#. Kotlin's non-anonymous ``object`` s (i.e. constructs like ``object foo : Contract {...}``) are singletons and
|
Kotlin's non-anonymous ``object`` s (i.e. constructs like ``object foo : Contract {...}``) are singletons and
|
||||||
treated differently. They are recorded into the stream with no properties, and deserialize back to the
|
treated differently. They are recorded into the stream with no properties, and deserialize back to the
|
||||||
singleton instance. Currently, the same is not true of Java singletons, which will deserialize to new instances
|
singleton instance. Currently, the same is not true of Java singletons, which will deserialize to new instances
|
||||||
of the class
|
of the class. This is hard to fix because there's no perfectly standard idiom for Java singletons.
|
||||||
#. Kotlin's anonymous ``object`` s (i.e. constructs like ``object : Contract {...}``) are not currently supported
|
|
||||||
and will not serialize correctly. They need to be re-written as an explicit class declaration
|
|
||||||
|
|
||||||
The Carpenter
|
Kotlin's anonymous ``object`` s (i.e. constructs like ``object : Contract {...}``) are not currently supported
|
||||||
`````````````
|
and will not serialize correctly. They need to be re-written as an explicit class declaration.
|
||||||
|
|
||||||
We support a class carpenter that can dynamically manufacture classes from the supplied schema when deserializing,
|
Class synthesis
|
||||||
without the supporting classes being present on the classpath. This can be useful where other components might expect to
|
---------------
|
||||||
be able to use reflection over the deserialized data, and also for ensuring classes not on the classpath can be
|
|
||||||
deserialized without loading potentially malicious code dynamically without security review outside of a fully sandboxed
|
|
||||||
environment. A more detailed discussion of the carpenter will be provided in a future update to the documentation.
|
|
||||||
|
|
||||||
Future Enhancements
|
Corda serialization supports dynamically synthesising classes from the supplied schema when deserializing,
|
||||||
```````````````````
|
without the supporting classes being present on the classpath. This can be useful where generic code might expect to
|
||||||
|
be able to use reflection over the deserialized data, for scripting languages that run on the JVM, and also for
|
||||||
|
ensuring classes not on the classpath can be deserialized without loading potentially malicious code.
|
||||||
|
|
||||||
|
Possible future enhancements include:
|
||||||
|
|
||||||
#. Java singleton support. We will add support for identifying classes which are singletons and identifying the
|
#. Java singleton support. We will add support for identifying classes which are singletons and identifying the
|
||||||
static method responsible for returning the singleton instance
|
static method responsible for returning the singleton instance
|
||||||
@ -449,10 +458,5 @@ and a version of the current state of the class instantiated.
|
|||||||
|
|
||||||
More detail can be found in :doc:`serialization-default-evolution`.
|
More detail can be found in :doc:`serialization-default-evolution`.
|
||||||
|
|
||||||
Enum Evolution
|
|
||||||
``````````````
|
|
||||||
Corda supports interoperability of enumerated type versions. This allows such types to be changed over time without breaking
|
|
||||||
backward (or forward) compatibility. The rules and mechanisms for doing this are discussed in :doc:`serialization-enum-evolution``.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,10 +126,13 @@ class SigningServiceIntegrationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.certificatesDirectory.createDirectories()
|
config.certificatesDirectory.createDirectories()
|
||||||
val trustStore = X509KeyStore.fromFile(config.trustStoreFile, config.trustStorePassword, createNew = true)
|
val networkTrustStorePath = config.certificatesDirectory / "network-root-truststore.jks"
|
||||||
trustStore.update {
|
val networkTrustStorePassword = "network-trust-password"
|
||||||
|
val networkTrustStore = X509KeyStore.fromFile(networkTrustStorePath, networkTrustStorePassword, createNew = true)
|
||||||
|
networkTrustStore.update {
|
||||||
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCaCert)
|
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCaCert)
|
||||||
}
|
}
|
||||||
|
val trustStore = X509KeyStore.fromFile(config.trustStoreFile, config.trustStorePassword, createNew = true)
|
||||||
val nodeKeyStore = X509KeyStore.fromFile(config.nodeKeystore, config.keyStorePassword, createNew = true)
|
val nodeKeyStore = X509KeyStore.fromFile(config.nodeKeystore, config.keyStorePassword, createNew = true)
|
||||||
val sslKeyStore = X509KeyStore.fromFile(config.sslKeystore, config.keyStorePassword, createNew = true)
|
val sslKeyStore = X509KeyStore.fromFile(config.sslKeystore, config.keyStorePassword, createNew = true)
|
||||||
config.also {
|
config.also {
|
||||||
@ -137,7 +140,7 @@ class SigningServiceIntegrationTest {
|
|||||||
doReturn(nodeKeyStore).whenever(it).loadNodeKeyStore(any())
|
doReturn(nodeKeyStore).whenever(it).loadNodeKeyStore(any())
|
||||||
doReturn(sslKeyStore).whenever(it).loadSslKeyStore(any())
|
doReturn(sslKeyStore).whenever(it).loadSslKeyStore(any())
|
||||||
}
|
}
|
||||||
NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore()
|
NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!), networkTrustStorePath, networkTrustStorePassword).buildKeystore()
|
||||||
verify(hsmSigner).sign(any())
|
verify(hsmSigner).sign(any())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.node.utilities.registration
|
package net.corda.node.utilities.registration
|
||||||
|
|
||||||
import net.corda.core.crypto.Crypto
|
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.concurrent.transpose
|
import net.corda.core.internal.concurrent.transpose
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
@ -28,7 +27,6 @@ import net.corda.testing.node.internal.CompatibilityZoneParams
|
|||||||
import net.corda.testing.node.internal.internalDriver
|
import net.corda.testing.node.internal.internalDriver
|
||||||
import net.corda.testing.node.internal.network.NetworkMapServer
|
import net.corda.testing.node.internal.network.NetworkMapServer
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
|
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
|
||||||
import org.junit.*
|
import org.junit.*
|
||||||
@ -37,12 +35,10 @@ import java.io.InputStream
|
|||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.cert.CertPath
|
import java.security.cert.CertPath
|
||||||
import java.security.cert.CertPathValidatorException
|
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
import javax.security.auth.x500.X500Principal
|
|
||||||
import javax.ws.rs.*
|
import javax.ws.rs.*
|
||||||
import javax.ws.rs.core.MediaType
|
import javax.ws.rs.core.MediaType
|
||||||
import javax.ws.rs.core.Response
|
import javax.ws.rs.core.Response
|
||||||
@ -118,28 +114,6 @@ class NodeRegistrationTest : IntegrationTest() {
|
|||||||
).returnValue.getOrThrow()
|
).returnValue.getOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `node registration wrong root cert`() {
|
|
||||||
val someRootCert = X509Utilities.createSelfSignedCACertificate(
|
|
||||||
X500Principal("CN=Integration Test Corda Node Root CA,O=R3 Ltd,L=London,C=GB"),
|
|
||||||
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
|
||||||
val compatibilityZone = CompatibilityZoneParams(
|
|
||||||
URL("http://$serverHostAndPort"),
|
|
||||||
publishNotaries = { server.networkParameters = testNetworkParameters(it) },
|
|
||||||
rootCert = someRootCert)
|
|
||||||
internalDriver(
|
|
||||||
portAllocation = portAllocation,
|
|
||||||
compatibilityZone = compatibilityZone,
|
|
||||||
initialiseSerialization = false,
|
|
||||||
notarySpecs = listOf(NotarySpec(notaryName)),
|
|
||||||
startNodesInProcess = true // We need to run the nodes in the same process so that we can capture the correct exception
|
|
||||||
) {
|
|
||||||
assertThatThrownBy {
|
|
||||||
defaultNotaryNode.getOrThrow()
|
|
||||||
}.isInstanceOf(CertPathValidatorException::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("certificate")
|
@Path("certificate")
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.node
|
package net.corda.node
|
||||||
|
|
||||||
import com.typesafe.config.ConfigException
|
|
||||||
import joptsimple.OptionParser
|
import joptsimple.OptionParser
|
||||||
import joptsimple.util.EnumConverter
|
import joptsimple.util.EnumConverter
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
@ -36,6 +35,10 @@ class ArgsParser {
|
|||||||
private val sshdServerArg = optionParser.accepts("sshd", "Enables SSHD server for node administration.")
|
private val sshdServerArg = optionParser.accepts("sshd", "Enables SSHD server for node administration.")
|
||||||
private val noLocalShellArg = optionParser.accepts("no-local-shell", "Do not start the embedded shell locally.")
|
private val noLocalShellArg = optionParser.accepts("no-local-shell", "Do not start the embedded shell locally.")
|
||||||
private val isRegistrationArg = optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.")
|
private val isRegistrationArg = optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.")
|
||||||
|
private val networkRootTruststorePathArg = optionParser.accepts("network-root-truststore", "Network root trust store obtained from network operator.")
|
||||||
|
.withRequiredArg()
|
||||||
|
private val networkRootTruststorePasswordArg = optionParser.accepts("network-root-truststore-password", "Network root trust store password obtained from network operator.")
|
||||||
|
.withRequiredArg()
|
||||||
private val isVersionArg = optionParser.accepts("version", "Print the version and exit")
|
private val isVersionArg = optionParser.accepts("version", "Print the version and exit")
|
||||||
private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info",
|
private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info",
|
||||||
"Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then quit")
|
"Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then quit")
|
||||||
@ -58,8 +61,21 @@ class ArgsParser {
|
|||||||
val sshdServer = optionSet.has(sshdServerArg)
|
val sshdServer = optionSet.has(sshdServerArg)
|
||||||
val justGenerateNodeInfo = optionSet.has(justGenerateNodeInfoArg)
|
val justGenerateNodeInfo = optionSet.has(justGenerateNodeInfoArg)
|
||||||
val bootstrapRaftCluster = optionSet.has(bootstrapRaftClusterArg)
|
val bootstrapRaftCluster = optionSet.has(bootstrapRaftClusterArg)
|
||||||
return CmdLineOptions(baseDirectory, configFile, help, loggingLevel, logToConsole, isRegistration, isVersion,
|
val networkRootTruststorePath = optionSet.valueOf(networkRootTruststorePathArg)?.let { Paths.get(it).normalize().toAbsolutePath() }
|
||||||
noLocalShell, sshdServer, justGenerateNodeInfo, bootstrapRaftCluster)
|
val networkRootTruststorePassword = optionSet.valueOf(networkRootTruststorePasswordArg)
|
||||||
|
return CmdLineOptions(baseDirectory,
|
||||||
|
configFile,
|
||||||
|
help,
|
||||||
|
loggingLevel,
|
||||||
|
logToConsole,
|
||||||
|
isRegistration,
|
||||||
|
networkRootTruststorePath,
|
||||||
|
networkRootTruststorePassword,
|
||||||
|
isVersion,
|
||||||
|
noLocalShell,
|
||||||
|
sshdServer,
|
||||||
|
justGenerateNodeInfo,
|
||||||
|
bootstrapRaftCluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
|
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
|
||||||
@ -71,6 +87,8 @@ data class CmdLineOptions(val baseDirectory: Path,
|
|||||||
val loggingLevel: Level,
|
val loggingLevel: Level,
|
||||||
val logToConsole: Boolean,
|
val logToConsole: Boolean,
|
||||||
val isRegistration: Boolean,
|
val isRegistration: Boolean,
|
||||||
|
val networkRootTruststorePath: Path?,
|
||||||
|
val networkRootTruststorePassword: String?,
|
||||||
val isVersion: Boolean,
|
val isVersion: Boolean,
|
||||||
val noLocalShell: Boolean,
|
val noLocalShell: Boolean,
|
||||||
val sshdServer: Boolean,
|
val sshdServer: Boolean,
|
||||||
@ -80,6 +98,8 @@ data class CmdLineOptions(val baseDirectory: Path,
|
|||||||
val config = ConfigHelper.loadConfig(baseDirectory, configFile).parseAsNodeConfiguration()
|
val config = ConfigHelper.loadConfig(baseDirectory, configFile).parseAsNodeConfiguration()
|
||||||
if (isRegistration) {
|
if (isRegistration) {
|
||||||
requireNotNull(config.compatibilityZoneURL) { "Compatibility Zone Url must be provided in registration mode." }
|
requireNotNull(config.compatibilityZoneURL) { "Compatibility Zone Url must be provided in registration mode." }
|
||||||
|
requireNotNull(networkRootTruststorePath) { "Network root trust store path must be provided in registration mode." }
|
||||||
|
requireNotNull(networkRootTruststorePassword) { "Network root trust store password must be provided in registration mode." }
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,8 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
banJavaSerialisation(conf)
|
banJavaSerialisation(conf)
|
||||||
preNetworkRegistration(conf)
|
preNetworkRegistration(conf)
|
||||||
if (shouldRegisterWithNetwork(cmdlineOptions, conf)) {
|
if (shouldRegisterWithNetwork(cmdlineOptions, conf)) {
|
||||||
registerWithNetwork(cmdlineOptions, conf)
|
// Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig]
|
||||||
|
registerWithNetwork(conf, cmdlineOptions.networkRootTruststorePath!!, cmdlineOptions.networkRootTruststorePassword!!)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
logStartupInfo(versionInfo, cmdlineOptions, conf)
|
logStartupInfo(versionInfo, cmdlineOptions, conf)
|
||||||
@ -184,7 +185,7 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
return !(!cmdlineOptions.isRegistration || compatibilityZoneURL == null)
|
return !(!cmdlineOptions.isRegistration || compatibilityZoneURL == null)
|
||||||
}
|
}
|
||||||
|
|
||||||
open protected fun registerWithNetwork(cmdlineOptions: CmdLineOptions, conf: NodeConfiguration) {
|
open protected fun registerWithNetwork(conf: NodeConfiguration, networkRootTruststorePath: Path, networkRootTruststorePassword: String) {
|
||||||
val compatibilityZoneURL = conf.compatibilityZoneURL!!
|
val compatibilityZoneURL = conf.compatibilityZoneURL!!
|
||||||
println()
|
println()
|
||||||
println("******************************************************************")
|
println("******************************************************************")
|
||||||
@ -192,7 +193,7 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
println("* Registering as a new participant with Corda network *")
|
println("* Registering as a new participant with Corda network *")
|
||||||
println("* *")
|
println("* *")
|
||||||
println("******************************************************************")
|
println("******************************************************************")
|
||||||
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore()
|
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL), networkRootTruststorePath, networkRootTruststorePassword).buildKeystore()
|
||||||
}
|
}
|
||||||
|
|
||||||
open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): NodeConfiguration = cmdlineOptions.loadConfig()
|
open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): NodeConfiguration = cmdlineOptions.loadConfig()
|
||||||
|
@ -61,7 +61,7 @@ class NodeSchedulerService(private val clock: CordaClock,
|
|||||||
private val serverThread: Executor,
|
private val serverThread: Executor,
|
||||||
private val flowLogicRefFactory: FlowLogicRefFactory,
|
private val flowLogicRefFactory: FlowLogicRefFactory,
|
||||||
private val log: Logger = staticLog,
|
private val log: Logger = staticLog,
|
||||||
scheduledStates: MutableMap<StateRef, ScheduledStateRef> = createMap())
|
private val scheduledStates: MutableMap<StateRef, ScheduledStateRef> = createMap())
|
||||||
: SchedulerService, SingletonSerializeAsToken() {
|
: SchedulerService, SingletonSerializeAsToken() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -153,13 +153,13 @@ class NodeSchedulerService(private val clock: CordaClock,
|
|||||||
var scheduledAt: Instant = Instant.now()
|
var scheduledAt: Instant = Instant.now()
|
||||||
)
|
)
|
||||||
|
|
||||||
private class InnerState(var scheduledStates: MutableMap<StateRef, ScheduledStateRef>) {
|
private class InnerState {
|
||||||
var scheduledStatesQueue: PriorityQueue<ScheduledStateRef> = PriorityQueue({ a, b -> a.scheduledAt.compareTo(b.scheduledAt) })
|
var scheduledStatesQueue: PriorityQueue<ScheduledStateRef> = PriorityQueue({ a, b -> a.scheduledAt.compareTo(b.scheduledAt) })
|
||||||
|
|
||||||
var rescheduled: GuavaSettableFuture<Boolean>? = null
|
var rescheduled: GuavaSettableFuture<Boolean>? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mutex = ThreadBox(InnerState(scheduledStates))
|
private val mutex = ThreadBox(InnerState())
|
||||||
// We need the [StateMachineManager] to be constructed before this is called in case it schedules a flow.
|
// We need the [StateMachineManager] to be constructed before this is called in case it schedules a flow.
|
||||||
fun start() {
|
fun start() {
|
||||||
mutex.locked {
|
mutex.locked {
|
||||||
@ -170,9 +170,9 @@ class NodeSchedulerService(private val clock: CordaClock,
|
|||||||
|
|
||||||
override fun scheduleStateActivity(action: ScheduledStateRef) {
|
override fun scheduleStateActivity(action: ScheduledStateRef) {
|
||||||
log.trace { "Schedule $action" }
|
log.trace { "Schedule $action" }
|
||||||
|
val previousState = scheduledStates[action.ref]
|
||||||
|
scheduledStates[action.ref] = action
|
||||||
mutex.locked {
|
mutex.locked {
|
||||||
val previousState = scheduledStates[action.ref]
|
|
||||||
scheduledStates[action.ref] = action
|
|
||||||
val previousEarliest = scheduledStatesQueue.peek()
|
val previousEarliest = scheduledStatesQueue.peek()
|
||||||
scheduledStatesQueue.remove(previousState)
|
scheduledStatesQueue.remove(previousState)
|
||||||
scheduledStatesQueue.add(action)
|
scheduledStatesQueue.add(action)
|
||||||
@ -192,12 +192,15 @@ class NodeSchedulerService(private val clock: CordaClock,
|
|||||||
|
|
||||||
override fun unscheduleStateActivity(ref: StateRef) {
|
override fun unscheduleStateActivity(ref: StateRef) {
|
||||||
log.trace { "Unschedule $ref" }
|
log.trace { "Unschedule $ref" }
|
||||||
|
val removedAction = scheduledStates.remove(ref)
|
||||||
mutex.locked {
|
mutex.locked {
|
||||||
val removedAction = scheduledStates.remove(ref)
|
|
||||||
if (removedAction != null) {
|
if (removedAction != null) {
|
||||||
scheduledStatesQueue.remove(removedAction)
|
val wasNext = (removedAction == scheduledStatesQueue.peek())
|
||||||
unfinishedSchedules.countDown()
|
val wasRemoved = scheduledStatesQueue.remove(removedAction)
|
||||||
if (removedAction == scheduledStatesQueue.peek()) {
|
if (wasRemoved) {
|
||||||
|
unfinishedSchedules.countDown()
|
||||||
|
}
|
||||||
|
if (wasNext) {
|
||||||
rescheduleWakeUp()
|
rescheduleWakeUp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ class ScheduledActivityObserver private constructor(private val schedulerService
|
|||||||
fun install(vaultService: VaultService, schedulerService: SchedulerService, flowLogicRefFactory: FlowLogicRefFactory) {
|
fun install(vaultService: VaultService, schedulerService: SchedulerService, flowLogicRefFactory: FlowLogicRefFactory) {
|
||||||
val observer = ScheduledActivityObserver(schedulerService, flowLogicRefFactory)
|
val observer = ScheduledActivityObserver(schedulerService, flowLogicRefFactory)
|
||||||
vaultService.rawUpdates.subscribe { (consumed, produced) ->
|
vaultService.rawUpdates.subscribe { (consumed, produced) ->
|
||||||
consumed.forEach { schedulerService.unscheduleStateActivity(it.ref) }
|
consumed.forEach { if (it.state.data is SchedulableState) schedulerService.unscheduleStateActivity(it.ref) }
|
||||||
produced.forEach { observer.scheduleStateActivity(it) }
|
produced.forEach { observer.scheduleStateActivity(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import net.corda.core.serialization.serialize
|
|||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.minutes
|
import net.corda.core.utilities.minutes
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
|
import net.corda.core.utilities.trace
|
||||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||||
import net.corda.node.utilities.NamedThreadFactory
|
import net.corda.node.utilities.NamedThreadFactory
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
@ -29,10 +30,15 @@ import java.util.concurrent.Executors
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509Certificate) {
|
class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509Certificate) {
|
||||||
|
companion object {
|
||||||
|
private val logger = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
|
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
|
||||||
|
|
||||||
fun publish(signedNodeInfo: SignedNodeInfo) {
|
fun publish(signedNodeInfo: SignedNodeInfo) {
|
||||||
val publishURL = URL("$networkMapUrl/publish")
|
val publishURL = URL("$networkMapUrl/publish")
|
||||||
|
logger.trace { "Publishing NodeInfo to $publishURL." }
|
||||||
publishURL.openHttpConnection().apply {
|
publishURL.openHttpConnection().apply {
|
||||||
doOutput = true
|
doOutput = true
|
||||||
requestMethod = "POST"
|
requestMethod = "POST"
|
||||||
@ -40,27 +46,41 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509C
|
|||||||
outputStream.use { signedNodeInfo.serialize().open().copyTo(it) }
|
outputStream.use { signedNodeInfo.serialize().open().copyTo(it) }
|
||||||
checkOkResponse()
|
checkOkResponse()
|
||||||
}
|
}
|
||||||
|
logger.trace { "Published NodeInfo to $publishURL successfully." }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNetworkMap(): NetworkMapResponse {
|
fun getNetworkMap(): NetworkMapResponse {
|
||||||
|
logger.trace { "Fetching network map update from $networkMapUrl." }
|
||||||
val connection = networkMapUrl.openHttpConnection()
|
val connection = networkMapUrl.openHttpConnection()
|
||||||
val signedNetworkMap = connection.responseAs<SignedDataWithCert<NetworkMap>>()
|
val signedNetworkMap = connection.responseAs<SignedDataWithCert<NetworkMap>>()
|
||||||
val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot)
|
val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot)
|
||||||
val timeout = CacheControl.parse(Headers.of(connection.headerFields.filterKeys { it != null }.mapValues { it.value[0] })).maxAgeSeconds().seconds
|
val timeout = CacheControl.parse(Headers.of(connection.headerFields.filterKeys { it != null }.mapValues { it.value[0] })).maxAgeSeconds().seconds
|
||||||
|
logger.trace { "Fetched network map update from $networkMapUrl successfully, retrieved ${networkMap.nodeInfoHashes.size} node info hashes. Node Info hashes: ${networkMap.nodeInfoHashes.joinToString("\n")}" }
|
||||||
return NetworkMapResponse(networkMap, timeout)
|
return NetworkMapResponse(networkMap, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo {
|
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo {
|
||||||
return URL("$networkMapUrl/node-info/$nodeInfoHash").openHttpConnection().responseAs<SignedNodeInfo>().verified()
|
val url = URL("$networkMapUrl/node-info/$nodeInfoHash")
|
||||||
|
logger.trace { "Fetching node info: '$nodeInfoHash' from $url." }
|
||||||
|
val verifiedNodeInfo = url.openHttpConnection().responseAs<SignedNodeInfo>().verified()
|
||||||
|
logger.trace { "Fetched node info: '$nodeInfoHash' successfully. Node Info: $verifiedNodeInfo" }
|
||||||
|
return verifiedNodeInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getNetworkParameters(networkParameterHash: SecureHash): SignedDataWithCert<NetworkParameters> {
|
fun getNetworkParameters(networkParameterHash: SecureHash): SignedDataWithCert<NetworkParameters> {
|
||||||
return URL("$networkMapUrl/network-parameters/$networkParameterHash").openHttpConnection().responseAs()
|
val url = URL("$networkMapUrl/network-parameters/$networkParameterHash")
|
||||||
|
logger.trace { "Fetching network parameters: '$networkParameterHash' from $url." }
|
||||||
|
val networkParameter = url.openHttpConnection().responseAs<SignedDataWithCert<NetworkParameters>>()
|
||||||
|
logger.trace { "Fetched network parameters: '$networkParameterHash' successfully. Network Parameters: $networkParameter" }
|
||||||
|
return networkParameter
|
||||||
}
|
}
|
||||||
|
|
||||||
fun myPublicHostname(): String {
|
fun myPublicHostname(): String {
|
||||||
val connection = URL("$networkMapUrl/my-hostname").openHttpConnection()
|
val url = URL("$networkMapUrl/my-hostname")
|
||||||
return connection.inputStream.bufferedReader().use(BufferedReader::readLine)
|
logger.trace { "Resolving public hostname from '$url'." }
|
||||||
|
val hostName = url.openHttpConnection().inputStream.bufferedReader().use(BufferedReader::readLine)
|
||||||
|
logger.trace { "My public hostname is $hostName." }
|
||||||
|
return hostName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@ package net.corda.node.services.transactions
|
|||||||
import io.atomix.copycat.Command
|
import io.atomix.copycat.Command
|
||||||
import io.atomix.copycat.Query
|
import io.atomix.copycat.Query
|
||||||
import io.atomix.copycat.server.Commit
|
import io.atomix.copycat.server.Commit
|
||||||
|
import io.atomix.copycat.server.Snapshottable
|
||||||
import io.atomix.copycat.server.StateMachine
|
import io.atomix.copycat.server.StateMachine
|
||||||
|
import io.atomix.copycat.server.storage.snapshot.SnapshotReader
|
||||||
|
import io.atomix.copycat.server.storage.snapshot.SnapshotWriter
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
@ -16,7 +19,7 @@ import java.util.*
|
|||||||
* The map contents are backed by a JDBC table. State re-synchronisation is achieved by replaying the command log to the
|
* The map contents are backed by a JDBC table. State re-synchronisation is achieved by replaying the command log to the
|
||||||
* new (or re-joining) cluster member.
|
* new (or re-joining) cluster member.
|
||||||
*/
|
*/
|
||||||
class DistributedImmutableMap<K : Any, V : Any, E, EK>(val db: CordaPersistence, createMap: () -> AppendOnlyPersistentMap<K, Pair<Long, V>, E, EK>) : StateMachine() {
|
class DistributedImmutableMap<K : Any, V : Any, E, EK>(val db: CordaPersistence, createMap: () -> AppendOnlyPersistentMap<K, Pair<Long, V>, E, EK>) : StateMachine(), Snapshottable {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
@ -75,4 +78,29 @@ class DistributedImmutableMap<K : Any, V : Any, E, EK>(val db: CordaPersistence,
|
|||||||
return db.transaction { map.size }
|
return db.transaction { map.size }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes out all [map] entries to disk. Note that this operation does not load all entries into memory, as the
|
||||||
|
* [SnapshotWriter] is using a disk-backed buffer internally, and iterating map entries results in only a
|
||||||
|
* fixed number of recently accessed entries to ever be kept in memory.
|
||||||
|
*/
|
||||||
|
override fun snapshot(writer: SnapshotWriter) {
|
||||||
|
db.transaction {
|
||||||
|
writer.writeInt(map.size)
|
||||||
|
map.allPersisted().forEach { writer.writeObject(it.first to it.second) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reads entries from disk and adds them to [map]. */
|
||||||
|
override fun install(reader: SnapshotReader) {
|
||||||
|
val size = reader.readInt()
|
||||||
|
db.transaction {
|
||||||
|
map.clear()
|
||||||
|
// TODO: read & put entries in batches
|
||||||
|
for (i in 1..size) {
|
||||||
|
val (key, value) = reader.readObject<Pair<K, Pair<Long, V>>>()
|
||||||
|
map[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,15 @@ import net.corda.core.internal.*
|
|||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||||
import net.corda.nodeapi.internal.crypto.x509
|
|
||||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||||
import org.bouncycastle.util.io.pem.PemObject
|
import org.bouncycastle.util.io.pem.PemObject
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
|
import java.nio.file.Path
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
@ -22,7 +23,10 @@ import java.security.cert.X509Certificate
|
|||||||
* Helper for managing the node registration process, which checks for any existing certificates and requests them if
|
* Helper for managing the node registration process, which checks for any existing certificates and requests them if
|
||||||
* needed.
|
* needed.
|
||||||
*/
|
*/
|
||||||
class NetworkRegistrationHelper(private val config: NodeConfiguration, private val certService: NetworkRegistrationService) {
|
class NetworkRegistrationHelper(private val config: NodeConfiguration,
|
||||||
|
private val certService: NetworkRegistrationService,
|
||||||
|
networkRootTrustStorePath: Path,
|
||||||
|
networkRootTruststorePassword: String) {
|
||||||
private companion object {
|
private companion object {
|
||||||
val pollInterval = 10.seconds
|
val pollInterval = 10.seconds
|
||||||
const val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key"
|
const val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key"
|
||||||
@ -31,20 +35,16 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
|||||||
private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
|
private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
|
||||||
// TODO: Use different password for private key.
|
// TODO: Use different password for private key.
|
||||||
private val privateKeyPassword = config.keyStorePassword
|
private val privateKeyPassword = config.keyStorePassword
|
||||||
|
private val rootTrustStore: X509KeyStore
|
||||||
private val rootCert: X509Certificate
|
private val rootCert: X509Certificate
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(config.trustStoreFile.exists()) {
|
require(networkRootTrustStorePath.exists()) {
|
||||||
"${config.trustStoreFile} does not exist. This file must contain the root CA cert of your compatibility zone. " +
|
"$networkRootTrustStorePath does not exist. This file must contain the root CA cert of your compatibility zone. " +
|
||||||
"Please contact your CZ operator."
|
"Please contact your CZ operator."
|
||||||
}
|
}
|
||||||
val rootCert = config.loadTrustStore().internal.getCertificate(CORDA_ROOT_CA)
|
rootTrustStore = X509KeyStore.fromFile(networkRootTrustStorePath, networkRootTruststorePassword)
|
||||||
require(rootCert != null) {
|
rootCert = rootTrustStore.getCertificate(CORDA_ROOT_CA)
|
||||||
"${config.trustStoreFile} does not contain a certificate with the key $CORDA_ROOT_CA." +
|
|
||||||
"This file must contain the root CA cert of your compatibility zone. " +
|
|
||||||
"Please contact your CZ operator."
|
|
||||||
}
|
|
||||||
this.rootCert = rootCert.x509
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,7 +109,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
|||||||
throw CertificateRequestException("Received node CA cert has invalid role: $nodeCaCertRole")
|
throw CertificateRequestException("Received node CA cert has invalid role: $nodeCaCertRole")
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Checking root of the certificate path is what we expect.")
|
// Validate certificate chain returned from the doorman with the root cert obtained via out-of-band process, to prevent MITM attack on doorman server.
|
||||||
X509Utilities.validateCertificateChain(rootCert, certificates)
|
X509Utilities.validateCertificateChain(rootCert, certificates)
|
||||||
|
|
||||||
println("Certificate signing request approved, storing private key with the certificate chain.")
|
println("Certificate signing request approved, storing private key with the certificate chain.")
|
||||||
@ -119,6 +119,14 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
|||||||
nodeKeyStore.save()
|
nodeKeyStore.save()
|
||||||
println("Node private key and certificate stored in ${config.nodeKeystore}.")
|
println("Node private key and certificate stored in ${config.nodeKeystore}.")
|
||||||
|
|
||||||
|
// Save root certificates to trust store.
|
||||||
|
config.loadTrustStore(createNew = true).update {
|
||||||
|
println("Generating trust store for corda node.")
|
||||||
|
// Assumes certificate chain always starts with client certificate and end with root certificate.
|
||||||
|
setCertificate(CORDA_ROOT_CA, certificates.last())
|
||||||
|
}
|
||||||
|
println("Node trust store stored in ${config.trustStoreFile}.")
|
||||||
|
|
||||||
config.loadSslKeyStore(createNew = true).update {
|
config.loadSslKeyStore(createNew = true).update {
|
||||||
println("Generating SSL certificate for node messaging service.")
|
println("Generating SSL certificate for node messaging service.")
|
||||||
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
|
@ -7,6 +7,7 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.slf4j.event.Level
|
import org.slf4j.event.Level
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class ArgsParserTest {
|
class ArgsParserTest {
|
||||||
private val parser = ArgsParser()
|
private val parser = ArgsParser()
|
||||||
@ -25,8 +26,9 @@ class ArgsParserTest {
|
|||||||
noLocalShell = false,
|
noLocalShell = false,
|
||||||
sshdServer = false,
|
sshdServer = false,
|
||||||
justGenerateNodeInfo = false,
|
justGenerateNodeInfo = false,
|
||||||
bootstrapRaftCluster = false
|
bootstrapRaftCluster = false,
|
||||||
))
|
networkRootTruststorePassword = null,
|
||||||
|
networkRootTruststorePath = null))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -111,8 +113,11 @@ class ArgsParserTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `initial-registration`() {
|
fun `initial-registration`() {
|
||||||
val cmdLineOptions = parser.parse("--initial-registration")
|
val truststorePath = Paths.get("truststore") / "file.jks"
|
||||||
|
val cmdLineOptions = parser.parse("--initial-registration", "--network-root-truststore", "$truststorePath", "--network-root-truststore-password", "password-test")
|
||||||
assertThat(cmdLineOptions.isRegistration).isTrue()
|
assertThat(cmdLineOptions.isRegistration).isTrue()
|
||||||
|
assertEquals(truststorePath.toAbsolutePath(), cmdLineOptions.networkRootTruststorePath)
|
||||||
|
assertEquals("password-test", cmdLineOptions.networkRootTruststorePassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -8,16 +8,16 @@ import net.corda.core.crypto.newSecureRandom
|
|||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.FlowLogicRef
|
import net.corda.core.flows.FlowLogicRef
|
||||||
import net.corda.core.flows.FlowLogicRefFactory
|
import net.corda.core.flows.FlowLogicRefFactory
|
||||||
import net.corda.core.utilities.days
|
|
||||||
import net.corda.testing.internal.rigorousMock
|
|
||||||
import net.corda.core.internal.FlowStateMachine
|
import net.corda.core.internal.FlowStateMachine
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.node.StateLoader
|
import net.corda.core.node.StateLoader
|
||||||
|
import net.corda.core.utilities.days
|
||||||
import net.corda.node.services.api.FlowStarter
|
import net.corda.node.services.api.FlowStarter
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
|
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
|
||||||
import net.corda.testing.internal.doLookup
|
import net.corda.testing.internal.doLookup
|
||||||
|
import net.corda.testing.internal.rigorousMock
|
||||||
import net.corda.testing.node.TestClock
|
import net.corda.testing.node.TestClock
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -165,7 +165,7 @@ class NodeSchedulerServiceTest {
|
|||||||
val eventA = schedule(mark + 1.days)
|
val eventA = schedule(mark + 1.days)
|
||||||
val eventB = schedule(mark + 1.days)
|
val eventB = schedule(mark + 1.days)
|
||||||
scheduler.unscheduleStateActivity(eventA.stateRef)
|
scheduler.unscheduleStateActivity(eventA.stateRef)
|
||||||
assertWaitingFor(eventA) // XXX: Shouldn't it be waiting for eventB now?
|
assertWaitingFor(eventB)
|
||||||
testClock.advanceBy(1.days)
|
testClock.advanceBy(1.days)
|
||||||
assertStarted(eventB)
|
assertStarted(eventB)
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,11 @@ import net.corda.core.crypto.Crypto
|
|||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.createDirectories
|
import net.corda.core.internal.createDirectories
|
||||||
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.x500Name
|
import net.corda.core.internal.x500Name
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||||
@ -35,10 +37,13 @@ class NetworkRegistrationHelperTest {
|
|||||||
private val nodeLegalName = ALICE_NAME
|
private val nodeLegalName = ALICE_NAME
|
||||||
|
|
||||||
private lateinit var config: NodeConfiguration
|
private lateinit var config: NodeConfiguration
|
||||||
|
private val networkRootTrustStoreFileName = "network-root-truststore.jks"
|
||||||
|
private val networkRootTrustStorePassword = "network-root-truststore-password"
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun init() {
|
fun init() {
|
||||||
val baseDirectory = fs.getPath("/baseDir").createDirectories()
|
val baseDirectory = fs.getPath("/baseDir").createDirectories()
|
||||||
|
|
||||||
abstract class AbstractNodeConfiguration : NodeConfiguration
|
abstract class AbstractNodeConfiguration : NodeConfiguration
|
||||||
config = rigorousMock<AbstractNodeConfiguration>().also {
|
config = rigorousMock<AbstractNodeConfiguration>().also {
|
||||||
doReturn(baseDirectory).whenever(it).baseDirectory
|
doReturn(baseDirectory).whenever(it).baseDirectory
|
||||||
@ -62,7 +67,7 @@ class NetworkRegistrationHelperTest {
|
|||||||
|
|
||||||
val nodeCaCertPath = createNodeCaCertPath()
|
val nodeCaCertPath = createNodeCaCertPath()
|
||||||
|
|
||||||
saveTrustStoreWithRootCa(nodeCaCertPath.last())
|
saveNetworkTrustStore(nodeCaCertPath.last())
|
||||||
createRegistrationHelper(nodeCaCertPath).buildKeystore()
|
createRegistrationHelper(nodeCaCertPath).buildKeystore()
|
||||||
|
|
||||||
val nodeKeystore = config.loadNodeKeyStore()
|
val nodeKeystore = config.loadNodeKeyStore()
|
||||||
@ -105,7 +110,7 @@ class NetworkRegistrationHelperTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `node CA with incorrect cert role`() {
|
fun `node CA with incorrect cert role`() {
|
||||||
val nodeCaCertPath = createNodeCaCertPath(type = CertificateType.TLS)
|
val nodeCaCertPath = createNodeCaCertPath(type = CertificateType.TLS)
|
||||||
saveTrustStoreWithRootCa(nodeCaCertPath.last())
|
saveNetworkTrustStore(nodeCaCertPath.last())
|
||||||
val registrationHelper = createRegistrationHelper(nodeCaCertPath)
|
val registrationHelper = createRegistrationHelper(nodeCaCertPath)
|
||||||
assertThatExceptionOfType(CertificateRequestException::class.java)
|
assertThatExceptionOfType(CertificateRequestException::class.java)
|
||||||
.isThrownBy { registrationHelper.buildKeystore() }
|
.isThrownBy { registrationHelper.buildKeystore() }
|
||||||
@ -116,7 +121,7 @@ class NetworkRegistrationHelperTest {
|
|||||||
fun `node CA with incorrect subject`() {
|
fun `node CA with incorrect subject`() {
|
||||||
val invalidName = CordaX500Name("Foo", "MU", "GB")
|
val invalidName = CordaX500Name("Foo", "MU", "GB")
|
||||||
val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName)
|
val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName)
|
||||||
saveTrustStoreWithRootCa(nodeCaCertPath.last())
|
saveNetworkTrustStore(nodeCaCertPath.last())
|
||||||
val registrationHelper = createRegistrationHelper(nodeCaCertPath)
|
val registrationHelper = createRegistrationHelper(nodeCaCertPath)
|
||||||
assertThatExceptionOfType(CertificateRequestException::class.java)
|
assertThatExceptionOfType(CertificateRequestException::class.java)
|
||||||
.isThrownBy { registrationHelper.buildKeystore() }
|
.isThrownBy { registrationHelper.buildKeystore() }
|
||||||
@ -128,7 +133,7 @@ class NetworkRegistrationHelperTest {
|
|||||||
val wrongRootCert = X509Utilities.createSelfSignedCACertificate(
|
val wrongRootCert = X509Utilities.createSelfSignedCACertificate(
|
||||||
X500Principal("O=Foo,L=MU,C=GB"),
|
X500Principal("O=Foo,L=MU,C=GB"),
|
||||||
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||||
saveTrustStoreWithRootCa(wrongRootCert)
|
saveNetworkTrustStore(wrongRootCert)
|
||||||
val registrationHelper = createRegistrationHelper(createNodeCaCertPath())
|
val registrationHelper = createRegistrationHelper(createNodeCaCertPath())
|
||||||
assertThatThrownBy {
|
assertThatThrownBy {
|
||||||
registrationHelper.buildKeystore()
|
registrationHelper.buildKeystore()
|
||||||
@ -155,12 +160,13 @@ class NetworkRegistrationHelperTest {
|
|||||||
doReturn(requestId).whenever(it).submitRequest(any())
|
doReturn(requestId).whenever(it).submitRequest(any())
|
||||||
doReturn(response).whenever(it).retrieveCertificates(eq(requestId))
|
doReturn(response).whenever(it).retrieveCertificates(eq(requestId))
|
||||||
}
|
}
|
||||||
return NetworkRegistrationHelper(config, certService)
|
return NetworkRegistrationHelper(config, certService, config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveTrustStoreWithRootCa(rootCert: X509Certificate) {
|
private fun saveNetworkTrustStore(rootCert: X509Certificate) {
|
||||||
config.certificatesDirectory.createDirectories()
|
config.certificatesDirectory.createDirectories()
|
||||||
config.loadTrustStore(createNew = true).update {
|
val rootTruststorePath = config.certificatesDirectory / networkRootTrustStoreFileName
|
||||||
|
X509KeyStore.fromFile(rootTruststorePath, networkRootTrustStorePassword, createNew = true).update {
|
||||||
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
|
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,8 @@ dependencies {
|
|||||||
testCompile project(':node-driver')
|
testCompile project(':node-driver')
|
||||||
testCompile "junit:junit:$junit_version"
|
testCompile "junit:junit:$junit_version"
|
||||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||||
|
|
||||||
|
integrationTestCompile project(path: ":samples:irs-demo:web", configuration: "demoArtifacts")
|
||||||
}
|
}
|
||||||
|
|
||||||
bootRepackage {
|
bootRepackage {
|
||||||
|
@ -94,6 +94,14 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
rpcUsers = ext.rpcUsers
|
rpcUsers = ext.rpcUsers
|
||||||
useTestClock true
|
useTestClock true
|
||||||
}
|
}
|
||||||
|
node {
|
||||||
|
name "O=Regulator,L=Moscow,C=RU"
|
||||||
|
p2pPort 10010
|
||||||
|
rpcPort 10011
|
||||||
|
cordapps = ["${project.group}:finance:$corda_release_version"]
|
||||||
|
rpcUsers = ext.rpcUsers
|
||||||
|
useTestClock true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task integrationTest(type: Test, dependsOn: []) {
|
task integrationTest(type: Test, dependsOn: []) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.irs
|
package net.corda.irs
|
||||||
|
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||||
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||||
@ -13,7 +14,8 @@ fun main(args: Array<String>) {
|
|||||||
driver(useTestClock = true, isDebug = true, waitForAllNodesToFinish = true) {
|
driver(useTestClock = true, isDebug = true, waitForAllNodesToFinish = true) {
|
||||||
val (nodeA, nodeB) = listOf(
|
val (nodeA, nodeB) = listOf(
|
||||||
startNode(providedName = DUMMY_BANK_A_NAME),
|
startNode(providedName = DUMMY_BANK_A_NAME),
|
||||||
startNode(providedName = DUMMY_BANK_B_NAME)
|
startNode(providedName = DUMMY_BANK_B_NAME),
|
||||||
|
startNode(providedName = CordaX500Name("Regulator", "Moscow", "RU"))
|
||||||
).map { it.getOrThrow() }
|
).map { it.getOrThrow() }
|
||||||
val controller = defaultNotaryNode.getOrThrow()
|
val controller = defaultNotaryNode.getOrThrow()
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import com.fasterxml.jackson.module.kotlin.readValue
|
|||||||
import net.corda.client.jackson.JacksonSupport
|
import net.corda.client.jackson.JacksonSupport
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
import net.corda.core.contracts.UniqueIdentifier
|
import net.corda.core.contracts.UniqueIdentifier
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.messaging.vaultTrackBy
|
import net.corda.core.messaging.vaultTrackBy
|
||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
@ -64,7 +65,8 @@ class IRSDemoTest : IntegrationTest() {
|
|||||||
) {
|
) {
|
||||||
val (nodeA, nodeB) = listOf(
|
val (nodeA, nodeB) = listOf(
|
||||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = rpcUsers),
|
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = rpcUsers),
|
||||||
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = rpcUsers)
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = rpcUsers),
|
||||||
|
startNode(providedName = CordaX500Name("Regulator", "Moscow", "RU"))
|
||||||
).map { it.getOrThrow() }
|
).map { it.getOrThrow() }
|
||||||
val controller = defaultNotaryNode.getOrThrow()
|
val controller = defaultNotaryNode.getOrThrow()
|
||||||
|
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}")
|
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.craigburke.client-dependencies' version '1.4.0'
|
id 'com.craigburke.client-dependencies' version '1.4.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
clientDependencies {
|
clientDependencies {
|
||||||
registry 'realBower', type:'bower', url:'https://registry.bower.io'
|
registry 'realBower', type:'bower', url:'https://registry.bower.io'
|
||||||
realBower {
|
realBower {
|
||||||
"angular"("1.5.8")
|
"angular"("1.5.8")
|
||||||
"jquery"("^3.0.0")
|
"jquery"("^3.0.0")
|
||||||
"angular-route"("1.5.8")
|
"angular-route"("1.5.8")
|
||||||
"lodash"("^4.13.1")
|
"lodash"("^4.13.1")
|
||||||
"angular-fcsa-number"("^1.5.3")
|
"angular-fcsa-number"("^1.5.3")
|
||||||
"jquery.maskedinput"("^1.4.1")
|
"jquery.maskedinput"("^1.4.1")
|
||||||
"requirejs"("^2.2.0")
|
"requirejs"("^2.2.0")
|
||||||
"semantic-ui"("^2.2.2", into: "semantic")
|
"semantic-ui"("^2.2.2", into: "semantic")
|
||||||
}
|
}
|
||||||
|
|
||||||
// put the JS dependencies into src directory so it can easily be referenced
|
// put the JS dependencies into src directory so it can easily be referenced
|
||||||
// from HTML files in webapp frontend, useful for testing/development
|
// from HTML files in webapp frontend, useful for testing/development
|
||||||
// Note that this dir is added to .gitignore
|
// Note that this dir is added to .gitignore
|
||||||
installDir = 'src/main/resources/static/js/bower_components'
|
installDir = 'src/main/resources/static/js/bower_components'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spring Boot plugin adds a numerous hardcoded dependencies in the version much lower then Corda expects
|
// Spring Boot plugin adds a numerous hardcoded dependencies in the version much lower then Corda expects
|
||||||
@ -44,36 +44,49 @@ apply plugin: 'org.springframework.boot'
|
|||||||
apply plugin: 'project-report'
|
apply plugin: 'project-report'
|
||||||
apply plugin: 'application'
|
apply plugin: 'application'
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
demoArtifacts.extendsFrom testRuntime
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile('org.springframework.boot:spring-boot-starter-web') {
|
compile('org.springframework.boot:spring-boot-starter-web') {
|
||||||
exclude module: "spring-boot-starter-logging"
|
exclude module: "spring-boot-starter-logging"
|
||||||
exclude module: "logback-classic"
|
exclude module: "logback-classic"
|
||||||
}
|
}
|
||||||
compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.9")
|
compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.9")
|
||||||
compile project(":client:rpc")
|
compile project(":client:rpc")
|
||||||
compile project(":client:jackson")
|
compile project(":client:jackson")
|
||||||
compile project(":test-utils")
|
compile project(":test-utils")
|
||||||
compile project(path: ":samples:irs-demo:cordapp", configuration: "demoArtifacts")
|
compile project(path: ":samples:irs-demo:cordapp", configuration: "demoArtifacts")
|
||||||
testCompile('org.springframework.boot:spring-boot-starter-test') {
|
testCompile('org.springframework.boot:spring-boot-starter-test') {
|
||||||
exclude module: "spring-boot-starter-logging"
|
exclude module: "spring-boot-starter-logging"
|
||||||
exclude module: "logback-classic"
|
exclude module: "logback-classic"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
from sourceSets.test.output
|
from sourceSets.test.output
|
||||||
dependsOn clientInstall
|
dependsOn clientInstall
|
||||||
}
|
}
|
||||||
|
|
||||||
task deployWebapps(type: Copy, dependsOn: ['jar', 'bootRepackage']) {
|
task deployWebapps(type: Copy, dependsOn: ['jar', 'bootRepackage']) {
|
||||||
ext.webappDir = file("build/webapps")
|
ext.webappDir = file("build/webapps")
|
||||||
|
|
||||||
from(jar.outputs)
|
from(jar.outputs)
|
||||||
from("src/test/resources/scripts/") {
|
from("src/test/resources/scripts/") {
|
||||||
filter { it
|
filter { it
|
||||||
.replace('#JAR_PATH#', jar.archiveName)
|
.replace('#JAR_PATH#', jar.archiveName)
|
||||||
.replace('#DIR#', ext.webappDir.getAbsolutePath())
|
.replace('#DIR#', ext.webappDir.getAbsolutePath())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
into ext.webappDir
|
into ext.webappDir
|
||||||
|
}
|
||||||
|
|
||||||
|
task demoJar(type: Jar) {
|
||||||
|
classifier "test"
|
||||||
|
from sourceSets.test.output
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
demoArtifacts demoJar
|
||||||
}
|
}
|
@ -33,6 +33,7 @@ import net.corda.nodeapi.internal.SignedNodeInfo
|
|||||||
import net.corda.nodeapi.internal.addShutdownHook
|
import net.corda.nodeapi.internal.addShutdownHook
|
||||||
import net.corda.nodeapi.internal.config.parseAs
|
import net.corda.nodeapi.internal.config.parseAs
|
||||||
import net.corda.nodeapi.internal.config.toConfig
|
import net.corda.nodeapi.internal.config.toConfig
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||||
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
|
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
|
||||||
@ -239,17 +240,24 @@ class DriverDSLImpl(
|
|||||||
))
|
))
|
||||||
|
|
||||||
config.corda.certificatesDirectory.createDirectories()
|
config.corda.certificatesDirectory.createDirectories()
|
||||||
config.corda.loadTrustStore(createNew = true).update {
|
// Create network root truststore.
|
||||||
|
val rootTruststorePath = config.corda.certificatesDirectory / "network-root-truststore.jks"
|
||||||
|
// The network truststore will be provided by the network operator via out-of-band communication.
|
||||||
|
val rootTruststorePassword = "corda-root-password"
|
||||||
|
X509KeyStore.fromFile(rootTruststorePath, rootTruststorePassword, createNew = true).update {
|
||||||
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
|
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (startNodesInProcess) {
|
return if (startNodesInProcess) {
|
||||||
executorService.fork {
|
executorService.fork {
|
||||||
NetworkRegistrationHelper(config.corda, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore()
|
NetworkRegistrationHelper(config.corda, HTTPNetworkRegistrationService(compatibilityZoneURL), rootTruststorePath, rootTruststorePassword).buildKeystore()
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
startOutOfProcessMiniNode(config, "--initial-registration").map { config }
|
startOutOfProcessMiniNode(config,
|
||||||
|
"--initial-registration",
|
||||||
|
"--network-root-truststore=${rootTruststorePath.toAbsolutePath()}",
|
||||||
|
"--network-root-truststore-password=$rootTruststorePassword").map { config }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,8 +490,8 @@ class DriverDSLImpl(
|
|||||||
when (it.cluster) {
|
when (it.cluster) {
|
||||||
null -> startSingleNotary(it, localNetworkMap)
|
null -> startSingleNotary(it, localNetworkMap)
|
||||||
is ClusterSpec.Raft,
|
is ClusterSpec.Raft,
|
||||||
// DummyCluster is used for testing the notary communication path, and it does not matter
|
// DummyCluster is used for testing the notary communication path, and it does not matter
|
||||||
// which underlying consensus algorithm is used, so we just stick to Raft
|
// which underlying consensus algorithm is used, so we just stick to Raft
|
||||||
is DummyClusterSpec -> startRaftNotaryCluster(it, localNetworkMap)
|
is DummyClusterSpec -> startRaftNotaryCluster(it, localNetworkMap)
|
||||||
else -> throw IllegalArgumentException("BFT-SMaRt not supported")
|
else -> throw IllegalArgumentException("BFT-SMaRt not supported")
|
||||||
}
|
}
|
||||||
@ -595,7 +603,7 @@ class DriverDSLImpl(
|
|||||||
* Start the node with the given flag which is expected to start the node for some function, which once complete will
|
* Start the node with the given flag which is expected to start the node for some function, which once complete will
|
||||||
* terminate the node.
|
* terminate the node.
|
||||||
*/
|
*/
|
||||||
private fun startOutOfProcessMiniNode(config: NodeConfig, extraCmdLineFlag: String): CordaFuture<Unit> {
|
private fun startOutOfProcessMiniNode(config: NodeConfig, vararg extraCmdLineFlag: String): CordaFuture<Unit> {
|
||||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||||
val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null
|
val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null
|
||||||
val process = startOutOfProcessNode(
|
val process = startOutOfProcessNode(
|
||||||
@ -607,7 +615,7 @@ class DriverDSLImpl(
|
|||||||
systemProperties,
|
systemProperties,
|
||||||
cordappPackages,
|
cordappPackages,
|
||||||
"200m",
|
"200m",
|
||||||
extraCmdLineFlag
|
*extraCmdLineFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
return poll(executorService, "$extraCmdLineFlag (${config.corda.myLegalName})") {
|
return poll(executorService, "$extraCmdLineFlag (${config.corda.myLegalName})") {
|
||||||
@ -651,7 +659,7 @@ class DriverDSLImpl(
|
|||||||
} else {
|
} else {
|
||||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||||
val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null
|
val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null
|
||||||
val process = startOutOfProcessNode(config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, systemProperties, cordappPackages, maximumHeapSize, null)
|
val process = startOutOfProcessNode(config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, systemProperties, cordappPackages, maximumHeapSize)
|
||||||
if (waitForNodesToFinish) {
|
if (waitForNodesToFinish) {
|
||||||
state.locked {
|
state.locked {
|
||||||
processes += process
|
processes += process
|
||||||
@ -762,7 +770,7 @@ class DriverDSLImpl(
|
|||||||
overriddenSystemProperties: Map<String, String>,
|
overriddenSystemProperties: Map<String, String>,
|
||||||
cordappPackages: List<String>,
|
cordappPackages: List<String>,
|
||||||
maximumHeapSize: String,
|
maximumHeapSize: String,
|
||||||
extraCmdLineFlag: String?
|
vararg extraCmdLineFlag: String
|
||||||
): Process {
|
): Process {
|
||||||
log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " +
|
log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " +
|
||||||
"debug port is " + (debugPort ?: "not enabled") + ", " +
|
"debug port is " + (debugPort ?: "not enabled") + ", " +
|
||||||
@ -800,9 +808,7 @@ class DriverDSLImpl(
|
|||||||
"--base-directory=${config.corda.baseDirectory}",
|
"--base-directory=${config.corda.baseDirectory}",
|
||||||
"--logging-level=$loggingLevel",
|
"--logging-level=$loggingLevel",
|
||||||
"--no-local-shell").also {
|
"--no-local-shell").also {
|
||||||
if (extraCmdLineFlag != null) {
|
it += extraCmdLineFlag
|
||||||
it += extraCmdLineFlag
|
|
||||||
}
|
|
||||||
}.toList()
|
}.toList()
|
||||||
|
|
||||||
return ProcessUtilities.startCordaProcess(
|
return ProcessUtilities.startCordaProcess(
|
||||||
|
Loading…
Reference in New Issue
Block a user