mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
merge os to enterprise
This commit is contained in:
commit
1c77736d50
@ -60,7 +60,7 @@ buildscript {
|
||||
ext.rxjava_version = '1.2.4'
|
||||
ext.dokka_version = '0.9.16-eap-2'
|
||||
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.beanutils_version = '1.9.3'
|
||||
ext.crash_version = 'cce5a00f114343c1145c1d7756e1dd6df3ea984e'
|
||||
|
@ -11,6 +11,7 @@ CorDapps
|
||||
cordapp-build-systems
|
||||
building-against-master
|
||||
corda-api
|
||||
serialization
|
||||
secure-coding-guidelines
|
||||
flow-cookbook
|
||||
cheat-sheet
|
||||
|
@ -51,6 +51,10 @@ The ``cordformation`` plugin adds two new gradle configurations:
|
||||
* ``cordaCompile``, which extends ``compile``
|
||||
* ``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:
|
||||
|
||||
* ``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 "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
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
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
|
||||
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
|
||||
vault
|
||||
serialization
|
||||
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
|
||||
====================
|
||||
|
||||
.. contents::
|
||||
|
||||
What is serialization (and deserialization)?
|
||||
--------------------------------------------
|
||||
Introduction
|
||||
------------
|
||||
|
||||
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
|
||||
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
|
||||
------------
|
||||
|
||||
@ -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
|
||||
====
|
||||
----
|
||||
|
||||
Originally Corda used a ``Kryo``-based serialization scheme throughout for all serialization contexts. However, it was realised there
|
||||
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:
|
||||
Corda uses an extended form of AMQP 1.0 as its binary wire protocol.
|
||||
|
||||
#. 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
|
||||
a decade ago, long after the code has changed) and between differing code versions
|
||||
#. 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
|
||||
#. Peer-to-peer networking.
|
||||
#. Persisted messages, like signed transactions and states.
|
||||
|
||||
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
|
||||
both the client and server side. However, it is planned that the RPC framework will move to the AMQP framework when ready.
|
||||
.. 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.
|
||||
|
||||
The AMQP framework is currently used for:
|
||||
|
||||
#. 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.
|
||||
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.
|
||||
|
||||
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,
|
||||
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.
|
||||
|
||||
.. note:: Selection of serialization context should, for the most part, be opaque to CorDapp developers, the Corda framework selecting
|
||||
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`.
|
||||
Selection of serialization context should, for the most part, be opaque to CorDapp developers, the Corda framework selecting
|
||||
the correct context as configured.
|
||||
|
||||
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
|
||||
@ -102,8 +110,9 @@ This section describes the classes and interfaces that the AMQP serialization fo
|
||||
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.
|
||||
For example, if you use a Guava implementation of a collection, it will deserialize as the primitive collection type.
|
||||
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. 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.
|
||||
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
|
||||
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 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
|
||||
|
||||
.. sourcecode:: Java
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class Example {
|
||||
private int a;
|
||||
private int b;
|
||||
private int c;
|
||||
class Example(var a: Int, var b: Int, var c: Int)
|
||||
|
||||
public int getA() { return a; }
|
||||
public int getB() { return b; }
|
||||
public int getC() { return c; }
|
||||
.. sourcecode:: java
|
||||
|
||||
public void setA(int a) { this.a = a; }
|
||||
public void setB(int b) { this.b = b; }
|
||||
public void setC(int c) { this.c = c; }
|
||||
}
|
||||
class Example {
|
||||
private int a;
|
||||
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
|
||||
```````````````````````````````
|
||||
@ -335,30 +351,26 @@ accessible getter methods, this development idiom is strongly discouraged.
|
||||
|
||||
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 {
|
||||
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,
|
||||
When designing Corda states, it should be remembered that they are not, despite appearances, traditional
|
||||
OOP style objects. They are signed over, transformed, serialised, and relationally mapped. As such,
|
||||
all elements should be publicly accessible by design.
|
||||
|
||||
.. 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:
|
||||
|
||||
.. 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) {
|
||||
public fun getB() = b
|
||||
}
|
||||
.. sourcecode:: java
|
||||
|
||||
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 a;
|
||||
private Integer b;
|
||||
|
||||
C(Integer a, Integer b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public Integer getB() {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
public Integer getB() {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
``````````
|
||||
@ -414,24 +424,23 @@ The following rules apply to supported ``Throwable`` implementations.
|
||||
Kotlin Objects
|
||||
``````````````
|
||||
|
||||
#. 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
|
||||
singleton instance. Currently, the same is not true of Java singletons, which will deserialize to new instances
|
||||
of the class
|
||||
#. 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
|
||||
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
|
||||
singleton instance. Currently, the same is not true of Java singletons, which will deserialize to new instances
|
||||
of the class. This is hard to fix because there's no perfectly standard idiom for Java singletons.
|
||||
|
||||
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,
|
||||
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.
|
||||
Class synthesis
|
||||
---------------
|
||||
|
||||
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
|
||||
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`.
|
||||
|
||||
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``.
|
||||
|
||||
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.node.utilities.registration
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
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.network.NetworkMapServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
|
||||
import org.junit.*
|
||||
@ -37,12 +35,10 @@ import java.io.InputStream
|
||||
import java.net.URL
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import javax.ws.rs.*
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
@ -118,28 +114,6 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
).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")
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.node
|
||||
|
||||
import com.typesafe.config.ConfigException
|
||||
import joptsimple.OptionParser
|
||||
import joptsimple.util.EnumConverter
|
||||
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 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 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 justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info",
|
||||
"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 justGenerateNodeInfo = optionSet.has(justGenerateNodeInfoArg)
|
||||
val bootstrapRaftCluster = optionSet.has(bootstrapRaftClusterArg)
|
||||
return CmdLineOptions(baseDirectory, configFile, help, loggingLevel, logToConsole, isRegistration, isVersion,
|
||||
noLocalShell, sshdServer, justGenerateNodeInfo, bootstrapRaftCluster)
|
||||
val networkRootTruststorePath = optionSet.valueOf(networkRootTruststorePathArg)?.let { Paths.get(it).normalize().toAbsolutePath() }
|
||||
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)
|
||||
@ -71,6 +87,8 @@ data class CmdLineOptions(val baseDirectory: Path,
|
||||
val loggingLevel: Level,
|
||||
val logToConsole: Boolean,
|
||||
val isRegistration: Boolean,
|
||||
val networkRootTruststorePath: Path?,
|
||||
val networkRootTruststorePassword: String?,
|
||||
val isVersion: Boolean,
|
||||
val noLocalShell: Boolean,
|
||||
val sshdServer: Boolean,
|
||||
@ -80,6 +98,8 @@ data class CmdLineOptions(val baseDirectory: Path,
|
||||
val config = ConfigHelper.loadConfig(baseDirectory, configFile).parseAsNodeConfiguration()
|
||||
if (isRegistration) {
|
||||
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
|
||||
}
|
||||
|
@ -95,7 +95,8 @@ open class NodeStartup(val args: Array<String>) {
|
||||
banJavaSerialisation(conf)
|
||||
preNetworkRegistration(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
|
||||
}
|
||||
logStartupInfo(versionInfo, cmdlineOptions, conf)
|
||||
@ -184,7 +185,7 @@ open class NodeStartup(val args: Array<String>) {
|
||||
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!!
|
||||
println()
|
||||
println("******************************************************************")
|
||||
@ -192,7 +193,7 @@ open class NodeStartup(val args: Array<String>) {
|
||||
println("* Registering as a new participant with Corda network *")
|
||||
println("* *")
|
||||
println("******************************************************************")
|
||||
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore()
|
||||
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL), networkRootTruststorePath, networkRootTruststorePassword).buildKeystore()
|
||||
}
|
||||
|
||||
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 flowLogicRefFactory: FlowLogicRefFactory,
|
||||
private val log: Logger = staticLog,
|
||||
scheduledStates: MutableMap<StateRef, ScheduledStateRef> = createMap())
|
||||
private val scheduledStates: MutableMap<StateRef, ScheduledStateRef> = createMap())
|
||||
: SchedulerService, SingletonSerializeAsToken() {
|
||||
|
||||
companion object {
|
||||
@ -153,13 +153,13 @@ class NodeSchedulerService(private val clock: CordaClock,
|
||||
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 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.
|
||||
fun start() {
|
||||
mutex.locked {
|
||||
@ -170,9 +170,9 @@ class NodeSchedulerService(private val clock: CordaClock,
|
||||
|
||||
override fun scheduleStateActivity(action: ScheduledStateRef) {
|
||||
log.trace { "Schedule $action" }
|
||||
val previousState = scheduledStates[action.ref]
|
||||
scheduledStates[action.ref] = action
|
||||
mutex.locked {
|
||||
val previousState = scheduledStates[action.ref]
|
||||
scheduledStates[action.ref] = action
|
||||
val previousEarliest = scheduledStatesQueue.peek()
|
||||
scheduledStatesQueue.remove(previousState)
|
||||
scheduledStatesQueue.add(action)
|
||||
@ -192,12 +192,15 @@ class NodeSchedulerService(private val clock: CordaClock,
|
||||
|
||||
override fun unscheduleStateActivity(ref: StateRef) {
|
||||
log.trace { "Unschedule $ref" }
|
||||
val removedAction = scheduledStates.remove(ref)
|
||||
mutex.locked {
|
||||
val removedAction = scheduledStates.remove(ref)
|
||||
if (removedAction != null) {
|
||||
scheduledStatesQueue.remove(removedAction)
|
||||
unfinishedSchedules.countDown()
|
||||
if (removedAction == scheduledStatesQueue.peek()) {
|
||||
val wasNext = (removedAction == scheduledStatesQueue.peek())
|
||||
val wasRemoved = scheduledStatesQueue.remove(removedAction)
|
||||
if (wasRemoved) {
|
||||
unfinishedSchedules.countDown()
|
||||
}
|
||||
if (wasNext) {
|
||||
rescheduleWakeUp()
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ class ScheduledActivityObserver private constructor(private val schedulerService
|
||||
fun install(vaultService: VaultService, schedulerService: SchedulerService, flowLogicRefFactory: FlowLogicRefFactory) {
|
||||
val observer = ScheduledActivityObserver(schedulerService, flowLogicRefFactory)
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||
import net.corda.node.utilities.NamedThreadFactory
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
@ -29,10 +30,15 @@ import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509Certificate) {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
|
||||
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
|
||||
|
||||
fun publish(signedNodeInfo: SignedNodeInfo) {
|
||||
val publishURL = URL("$networkMapUrl/publish")
|
||||
logger.trace { "Publishing NodeInfo to $publishURL." }
|
||||
publishURL.openHttpConnection().apply {
|
||||
doOutput = true
|
||||
requestMethod = "POST"
|
||||
@ -40,27 +46,41 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509C
|
||||
outputStream.use { signedNodeInfo.serialize().open().copyTo(it) }
|
||||
checkOkResponse()
|
||||
}
|
||||
logger.trace { "Published NodeInfo to $publishURL successfully." }
|
||||
}
|
||||
|
||||
fun getNetworkMap(): NetworkMapResponse {
|
||||
logger.trace { "Fetching network map update from $networkMapUrl." }
|
||||
val connection = networkMapUrl.openHttpConnection()
|
||||
val signedNetworkMap = connection.responseAs<SignedDataWithCert<NetworkMap>>()
|
||||
val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot)
|
||||
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)
|
||||
}
|
||||
|
||||
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> {
|
||||
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 {
|
||||
val connection = URL("$networkMapUrl/my-hostname").openHttpConnection()
|
||||
return connection.inputStream.bufferedReader().use(BufferedReader::readLine)
|
||||
val url = URL("$networkMapUrl/my-hostname")
|
||||
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.Query
|
||||
import io.atomix.copycat.server.Commit
|
||||
import io.atomix.copycat.server.Snapshottable
|
||||
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.node.utilities.AppendOnlyPersistentMap
|
||||
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
|
||||
* 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 {
|
||||
private val log = contextLogger()
|
||||
}
|
||||
@ -75,4 +78,29 @@ class DistributedImmutableMap<K : Any, V : Any, E, EK>(val db: CordaPersistence,
|
||||
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.node.services.config.NodeConfiguration
|
||||
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.CORDA_CLIENT_CA
|
||||
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.x509
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
import org.bouncycastle.util.io.pem.PemObject
|
||||
import java.io.StringWriter
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStore
|
||||
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
|
||||
* 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 {
|
||||
val pollInterval = 10.seconds
|
||||
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"
|
||||
// TODO: Use different password for private key.
|
||||
private val privateKeyPassword = config.keyStorePassword
|
||||
private val rootTrustStore: X509KeyStore
|
||||
private val rootCert: X509Certificate
|
||||
|
||||
init {
|
||||
require(config.trustStoreFile.exists()) {
|
||||
"${config.trustStoreFile} does not exist. This file must contain the root CA cert of your compatibility zone. " +
|
||||
require(networkRootTrustStorePath.exists()) {
|
||||
"$networkRootTrustStorePath does not exist. This file must contain the root CA cert of your compatibility zone. " +
|
||||
"Please contact your CZ operator."
|
||||
}
|
||||
val rootCert = config.loadTrustStore().internal.getCertificate(CORDA_ROOT_CA)
|
||||
require(rootCert != null) {
|
||||
"${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
|
||||
rootTrustStore = X509KeyStore.fromFile(networkRootTrustStorePath, networkRootTruststorePassword)
|
||||
rootCert = rootTrustStore.getCertificate(CORDA_ROOT_CA)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,7 +109,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
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)
|
||||
|
||||
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()
|
||||
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 {
|
||||
println("Generating SSL certificate for node messaging service.")
|
||||
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.slf4j.event.Level
|
||||
import java.nio.file.Paths
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ArgsParserTest {
|
||||
private val parser = ArgsParser()
|
||||
@ -25,8 +26,9 @@ class ArgsParserTest {
|
||||
noLocalShell = false,
|
||||
sshdServer = false,
|
||||
justGenerateNodeInfo = false,
|
||||
bootstrapRaftCluster = false
|
||||
))
|
||||
bootstrapRaftCluster = false,
|
||||
networkRootTruststorePassword = null,
|
||||
networkRootTruststorePath = null))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -111,8 +113,11 @@ class ArgsParserTest {
|
||||
|
||||
@Test
|
||||
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()
|
||||
assertEquals(truststorePath.toAbsolutePath(), cmdLineOptions.networkRootTruststorePath)
|
||||
assertEquals("password-test", cmdLineOptions.networkRootTruststorePassword)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -8,16 +8,16 @@ import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRef
|
||||
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.concurrent.openFuture
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.node.StateLoader
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.node.services.api.FlowStarter
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
|
||||
import net.corda.testing.internal.doLookup
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.TestClock
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@ -165,7 +165,7 @@ class NodeSchedulerServiceTest {
|
||||
val eventA = schedule(mark + 1.days)
|
||||
val eventB = schedule(mark + 1.days)
|
||||
scheduler.unscheduleStateActivity(eventA.stateRef)
|
||||
assertWaitingFor(eventA) // XXX: Shouldn't it be waiting for eventB now?
|
||||
assertWaitingFor(eventB)
|
||||
testClock.advanceBy(1.days)
|
||||
assertStarted(eventB)
|
||||
}
|
||||
|
@ -10,9 +10,11 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.x500Name
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||
@ -35,10 +37,13 @@ class NetworkRegistrationHelperTest {
|
||||
private val nodeLegalName = ALICE_NAME
|
||||
|
||||
private lateinit var config: NodeConfiguration
|
||||
private val networkRootTrustStoreFileName = "network-root-truststore.jks"
|
||||
private val networkRootTrustStorePassword = "network-root-truststore-password"
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
val baseDirectory = fs.getPath("/baseDir").createDirectories()
|
||||
|
||||
abstract class AbstractNodeConfiguration : NodeConfiguration
|
||||
config = rigorousMock<AbstractNodeConfiguration>().also {
|
||||
doReturn(baseDirectory).whenever(it).baseDirectory
|
||||
@ -62,7 +67,7 @@ class NetworkRegistrationHelperTest {
|
||||
|
||||
val nodeCaCertPath = createNodeCaCertPath()
|
||||
|
||||
saveTrustStoreWithRootCa(nodeCaCertPath.last())
|
||||
saveNetworkTrustStore(nodeCaCertPath.last())
|
||||
createRegistrationHelper(nodeCaCertPath).buildKeystore()
|
||||
|
||||
val nodeKeystore = config.loadNodeKeyStore()
|
||||
@ -105,7 +110,7 @@ class NetworkRegistrationHelperTest {
|
||||
@Test
|
||||
fun `node CA with incorrect cert role`() {
|
||||
val nodeCaCertPath = createNodeCaCertPath(type = CertificateType.TLS)
|
||||
saveTrustStoreWithRootCa(nodeCaCertPath.last())
|
||||
saveNetworkTrustStore(nodeCaCertPath.last())
|
||||
val registrationHelper = createRegistrationHelper(nodeCaCertPath)
|
||||
assertThatExceptionOfType(CertificateRequestException::class.java)
|
||||
.isThrownBy { registrationHelper.buildKeystore() }
|
||||
@ -116,7 +121,7 @@ class NetworkRegistrationHelperTest {
|
||||
fun `node CA with incorrect subject`() {
|
||||
val invalidName = CordaX500Name("Foo", "MU", "GB")
|
||||
val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName)
|
||||
saveTrustStoreWithRootCa(nodeCaCertPath.last())
|
||||
saveNetworkTrustStore(nodeCaCertPath.last())
|
||||
val registrationHelper = createRegistrationHelper(nodeCaCertPath)
|
||||
assertThatExceptionOfType(CertificateRequestException::class.java)
|
||||
.isThrownBy { registrationHelper.buildKeystore() }
|
||||
@ -128,7 +133,7 @@ class NetworkRegistrationHelperTest {
|
||||
val wrongRootCert = X509Utilities.createSelfSignedCACertificate(
|
||||
X500Principal("O=Foo,L=MU,C=GB"),
|
||||
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||
saveTrustStoreWithRootCa(wrongRootCert)
|
||||
saveNetworkTrustStore(wrongRootCert)
|
||||
val registrationHelper = createRegistrationHelper(createNodeCaCertPath())
|
||||
assertThatThrownBy {
|
||||
registrationHelper.buildKeystore()
|
||||
@ -155,12 +160,13 @@ class NetworkRegistrationHelperTest {
|
||||
doReturn(requestId).whenever(it).submitRequest(any())
|
||||
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.loadTrustStore(createNew = true).update {
|
||||
val rootTruststorePath = config.certificatesDirectory / networkRootTrustStoreFileName
|
||||
X509KeyStore.fromFile(rootTruststorePath, networkRootTrustStorePassword, createNew = true).update {
|
||||
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,8 @@ dependencies {
|
||||
testCompile project(':node-driver')
|
||||
testCompile "junit:junit:$junit_version"
|
||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
|
||||
integrationTestCompile project(path: ":samples:irs-demo:web", configuration: "demoArtifacts")
|
||||
}
|
||||
|
||||
bootRepackage {
|
||||
|
@ -94,6 +94,14 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
rpcUsers = ext.rpcUsers
|
||||
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: []) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.irs
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.testing.core.DUMMY_BANK_A_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) {
|
||||
val (nodeA, nodeB) = listOf(
|
||||
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() }
|
||||
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.rpc.CordaRPCClient
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.vaultTrackBy
|
||||
import net.corda.core.toFuture
|
||||
@ -64,7 +65,8 @@ class IRSDemoTest : IntegrationTest() {
|
||||
) {
|
||||
val (nodeA, nodeB) = listOf(
|
||||
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() }
|
||||
val controller = defaultNotaryNode.getOrThrow()
|
||||
|
||||
|
@ -1,34 +1,34 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}")
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}")
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.craigburke.client-dependencies' version '1.4.0'
|
||||
id 'com.craigburke.client-dependencies' version '1.4.0'
|
||||
}
|
||||
|
||||
clientDependencies {
|
||||
registry 'realBower', type:'bower', url:'https://registry.bower.io'
|
||||
realBower {
|
||||
"angular"("1.5.8")
|
||||
"jquery"("^3.0.0")
|
||||
"angular-route"("1.5.8")
|
||||
"lodash"("^4.13.1")
|
||||
"angular-fcsa-number"("^1.5.3")
|
||||
"jquery.maskedinput"("^1.4.1")
|
||||
"requirejs"("^2.2.0")
|
||||
"semantic-ui"("^2.2.2", into: "semantic")
|
||||
}
|
||||
registry 'realBower', type:'bower', url:'https://registry.bower.io'
|
||||
realBower {
|
||||
"angular"("1.5.8")
|
||||
"jquery"("^3.0.0")
|
||||
"angular-route"("1.5.8")
|
||||
"lodash"("^4.13.1")
|
||||
"angular-fcsa-number"("^1.5.3")
|
||||
"jquery.maskedinput"("^1.4.1")
|
||||
"requirejs"("^2.2.0")
|
||||
"semantic-ui"("^2.2.2", into: "semantic")
|
||||
}
|
||||
|
||||
// put the JS dependencies into src directory so it can easily be referenced
|
||||
// from HTML files in webapp frontend, useful for testing/development
|
||||
// Note that this dir is added to .gitignore
|
||||
installDir = 'src/main/resources/static/js/bower_components'
|
||||
// put the JS dependencies into src directory so it can easily be referenced
|
||||
// from HTML files in webapp frontend, useful for testing/development
|
||||
// Note that this dir is added to .gitignore
|
||||
installDir = 'src/main/resources/static/js/bower_components'
|
||||
}
|
||||
|
||||
// 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: 'application'
|
||||
|
||||
configurations {
|
||||
demoArtifacts.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-web') {
|
||||
exclude module: "spring-boot-starter-logging"
|
||||
exclude module: "logback-classic"
|
||||
}
|
||||
compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.9")
|
||||
compile project(":client:rpc")
|
||||
compile project(":client:jackson")
|
||||
compile project(":test-utils")
|
||||
compile project(path: ":samples:irs-demo:cordapp", configuration: "demoArtifacts")
|
||||
testCompile('org.springframework.boot:spring-boot-starter-test') {
|
||||
exclude module: "spring-boot-starter-logging"
|
||||
exclude module: "logback-classic"
|
||||
}
|
||||
compile('org.springframework.boot:spring-boot-starter-web') {
|
||||
exclude module: "spring-boot-starter-logging"
|
||||
exclude module: "logback-classic"
|
||||
}
|
||||
compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.9")
|
||||
compile project(":client:rpc")
|
||||
compile project(":client:jackson")
|
||||
compile project(":test-utils")
|
||||
compile project(path: ":samples:irs-demo:cordapp", configuration: "demoArtifacts")
|
||||
testCompile('org.springframework.boot:spring-boot-starter-test') {
|
||||
exclude module: "spring-boot-starter-logging"
|
||||
exclude module: "logback-classic"
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
from sourceSets.test.output
|
||||
dependsOn clientInstall
|
||||
from sourceSets.test.output
|
||||
dependsOn clientInstall
|
||||
}
|
||||
|
||||
task deployWebapps(type: Copy, dependsOn: ['jar', 'bootRepackage']) {
|
||||
ext.webappDir = file("build/webapps")
|
||||
ext.webappDir = file("build/webapps")
|
||||
|
||||
from(jar.outputs)
|
||||
from("src/test/resources/scripts/") {
|
||||
filter { it
|
||||
.replace('#JAR_PATH#', jar.archiveName)
|
||||
.replace('#DIR#', ext.webappDir.getAbsolutePath())
|
||||
}
|
||||
}
|
||||
into ext.webappDir
|
||||
from(jar.outputs)
|
||||
from("src/test/resources/scripts/") {
|
||||
filter { it
|
||||
.replace('#JAR_PATH#', jar.archiveName)
|
||||
.replace('#DIR#', ext.webappDir.getAbsolutePath())
|
||||
}
|
||||
}
|
||||
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.config.parseAs
|
||||
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.network.NetworkParametersCopier
|
||||
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
|
||||
@ -239,17 +240,24 @@ class DriverDSLImpl(
|
||||
))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return if (startNodesInProcess) {
|
||||
executorService.fork {
|
||||
NetworkRegistrationHelper(config.corda, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore()
|
||||
NetworkRegistrationHelper(config.corda, HTTPNetworkRegistrationService(compatibilityZoneURL), rootTruststorePath, rootTruststorePassword).buildKeystore()
|
||||
config
|
||||
}
|
||||
} 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) {
|
||||
null -> startSingleNotary(it, localNetworkMap)
|
||||
is ClusterSpec.Raft,
|
||||
// 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
|
||||
// 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
|
||||
is DummyClusterSpec -> startRaftNotaryCluster(it, localNetworkMap)
|
||||
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
|
||||
* 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 monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null
|
||||
val process = startOutOfProcessNode(
|
||||
@ -607,7 +615,7 @@ class DriverDSLImpl(
|
||||
systemProperties,
|
||||
cordappPackages,
|
||||
"200m",
|
||||
extraCmdLineFlag
|
||||
*extraCmdLineFlag
|
||||
)
|
||||
|
||||
return poll(executorService, "$extraCmdLineFlag (${config.corda.myLegalName})") {
|
||||
@ -651,7 +659,7 @@ class DriverDSLImpl(
|
||||
} else {
|
||||
val debugPort = if (isDebug) debugPortAllocation.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) {
|
||||
state.locked {
|
||||
processes += process
|
||||
@ -762,7 +770,7 @@ class DriverDSLImpl(
|
||||
overriddenSystemProperties: Map<String, String>,
|
||||
cordappPackages: List<String>,
|
||||
maximumHeapSize: String,
|
||||
extraCmdLineFlag: String?
|
||||
vararg extraCmdLineFlag: String
|
||||
): Process {
|
||||
log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " +
|
||||
"debug port is " + (debugPort ?: "not enabled") + ", " +
|
||||
@ -800,9 +808,7 @@ class DriverDSLImpl(
|
||||
"--base-directory=${config.corda.baseDirectory}",
|
||||
"--logging-level=$loggingLevel",
|
||||
"--no-local-shell").also {
|
||||
if (extraCmdLineFlag != null) {
|
||||
it += extraCmdLineFlag
|
||||
}
|
||||
it += extraCmdLineFlag
|
||||
}.toList()
|
||||
|
||||
return ProcessUtilities.startCordaProcess(
|
||||
|
Loading…
Reference in New Issue
Block a user