merge os to enterprise

This commit is contained in:
Patrick Kuo 2018-01-30 14:00:22 +00:00
commit 1c77736d50
22 changed files with 358 additions and 243 deletions

View File

@ -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'

View File

@ -11,6 +11,7 @@ CorDapps
cordapp-build-systems
building-against-master
corda-api
serialization
secure-coding-guidelines
flow-cookbook
cheat-sheet

View File

@ -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.

View File

@ -6,5 +6,4 @@ Node internals
node-services
vault
serialization
messaging

View File

@ -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``.

View File

@ -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")

View File

@ -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
}

View File

@ -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()

View File

@ -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()
}
}

View File

@ -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) }
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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: []) {

View File

@ -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()

View File

@ -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()

View File

@ -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
}

View File

@ -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(