CORDA-830 Introducing the network bootstrapper

Copying of the node-info files moved out of Cordform and into NetworkParametersGenerator (which is now called NetworkBootstrapper). This class becomes an external tool to enable deployment of nodes in a test setup on a single filesystem.
This commit is contained in:
Shams Asari 2017-12-07 14:48:31 +00:00
parent e9cead9055
commit 00a5e3db6b
33 changed files with 378 additions and 341 deletions

2
.idea/compiler.xml generated
View File

@ -10,6 +10,8 @@
<module name="bank-of-corda-demo_integrationTest" target="1.8" />
<module name="bank-of-corda-demo_main" target="1.8" />
<module name="bank-of-corda-demo_test" target="1.8" />
<module name="bootstrapper_main" target="1.8" />
<module name="bootstrapper_test" target="1.8" />
<module name="buildSrc_main" target="1.8" />
<module name="buildSrc_test" target="1.8" />
<module name="client_main" target="1.8" />

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=3.0.1
gradlePluginsVersion=3.0.2
kotlinVersion=1.1.60
platformVersion=2
guavaVersion=21.0

View File

@ -6,13 +6,11 @@ from the previous milestone release.
UNRELEASED
----------
* Support for external user credentials data source and password encryption [CORDA-827].
* The network map service concept has been re-designed. More information can be found in :doc:`network-map`.
* The previous design was never intended to be final but was rather a quick implementation in the earliest days of the
Corda project to unblock higher priority items. It sufffers from numerous disadvantages including lack of scalability,
Corda project to unblock higher priority items. It suffers from numerous disadvantages including lack of scalability,
as one node is expected to hold open and manage connections to every node on the network; not reliable; hard to defend
against DoS attacks; etc.
@ -50,6 +48,8 @@ UNRELEASED
* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This
was needed to allow changes to the schema.
* Support for external user credentials data source and password encryption [CORDA-827].
* Exporting additional JMX metrics (artemis, hibernate statistics) and loading Jolokia agent at JVM startup when using
DriverDSL and/or cordformation node runner.

View File

@ -13,7 +13,7 @@ Protocol Design
---------------
The node info publishing protocol:
* Create a ``NodeInfo`` object, and sign it to create a ``SignedData<NodeInfo>`` object. TODO: We will need list of signatures in ``SignedData`` to support multiple node identities in the future.
* Create a ``NodeInfo`` object, and sign it to create a ``SignedNodeInfo`` object.
* Serialise the signed data and POST the data to the network map server.
@ -61,7 +61,7 @@ The ``additional-node-infos`` directory
---------------------------------------
Each Corda node reads, and continuously polls, the files contained in a directory named ``additional-node-infos`` inside the node base directory.
Nodes expect to find a serialized ``SignedData<NodeInfo>`` object, the same object which is sent to network map server.
Nodes expect to find a serialized ``SignedNodeInfo`` object, the same object which is sent to network map server.
Whenever a node starts it writes on disk a file containing its own ``NodeInfo``, this file is called ``nodeInfo-XXX`` where ``XXX`` is a long string.

View File

@ -6,11 +6,9 @@ Creating a Corda network
A Corda network consists of a number of machines running nodes. These nodes communicate using persistent protocols in
order to create and validate transactions.
There are four broader categories of functionality one such node may have. These pieces of functionality are provided
There are three broader categories of functionality one such node may have. These pieces of functionality are provided
as services, and one node may run several of them.
* Network map: The node running the network map provides a way to resolve identities to physical node addresses and
associated public keys
* Notary: Nodes running a notary service witness state spends and have the final say in whether a transaction is a
double-spend or not
* Oracle: Network services that link the ledger to the outside world by providing facts that affect the validity of
@ -46,12 +44,38 @@ The most important fields regarding network configuration are:
* ``rpcAddress``: The address to which Artemis will bind for RPC calls.
* ``webAddress``: The address the webserver should bind. Note that the port must be distinct from that of ``p2pAddress`` and ``rpcAddress`` if they are on the same machine.
Bootstrapping the network
~~~~~~~~~~~~~~~~~~~~~~~~~
The nodes see each other using the network map. This is a collection of statically signed node-info files, one for each
node in the network. Most production deployments will use a highly available, secure distribution of the network map via HTTP.
For test deployments where the nodes (at least initially) reside on the same filesystem, these node-info files can be
placed directly in the node's ``additional-node-infos`` directory from where the node will pick them up and store them
in its local network map cache. The node generates its own node-info file on startup.
In addition to the network map, all the nodes on a network must use the same set of network parameters. These are a set
of constants which guarantee interoperability between nodes. The HTTP network map distributes the network parameters
which the node downloads automatically. In the absence of this the network parameters must be generated locally. This can
be done with the network bootstrapper. This a tool that scans all the node configurations from a common directory to
generate the network parameters file which is copied to the nodes' directories. It also copies each node's node-info file
to every other node.
The bootstrapper tool can be built with the command:
``gradlew buildBootstrapperJar``
The resulting jar can be found in ``tools/bootstrapper/build/libs/``.
To use it, run the following command, specifying the root directory which hosts all the node directories as the argument:
``java -jar network-bootstrapper.jar <nodes-root-dir>``
Starting the nodes
~~~~~~~~~~~~~~~~~~
You may now start the nodes in any order. Note that the node is not fully started until it has successfully registered with the network map!
You should see a banner, some log lines and eventually ``Node started up and registered``, indicating that the node is fully started.
You may now start the nodes in any order. You should see a banner, some log lines and eventually ``Node started up and registered``,
indicating that the node is fully started.
.. TODO: Add a better way of polling for startup. A programmatic way of determining whether a node is up is to check whether it's ``webAddress`` is bound.
@ -66,7 +90,6 @@ details/diagnosing problems check the logs.
Logging is standard log4j2_ and may be configured accordingly. Logs
are by default redirected to files in ``NODE_DIRECTORY/logs/``.
Connecting to the nodes
~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -2,18 +2,16 @@ package net.corda.plugins
import groovy.lang.Closure
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode
import org.apache.tools.ant.filters.FixCrLfFilter
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.lang.reflect.InvocationTargetException
import java.net.URLClassLoader
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.TimeUnit
import java.util.jar.JarInputStream
/**
@ -118,13 +116,13 @@ open class Cordform : DefaultTask() {
}
/**
* The parametersGenerator needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
* The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
*/
private fun loadNetworkParamsGenClass(): Class<*> {
private fun loadNetworkBootstrapperClass(): Class<*> {
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.NetworkParametersGenerator")
return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper")
}
/**
@ -136,8 +134,7 @@ open class Cordform : DefaultTask() {
initializeConfiguration()
installRunScript()
nodes.forEach(Node::build)
generateAndInstallNodeInfos()
generateAndInstallNetworkParameters()
bootstrapNetwork()
}
private fun initializeConfiguration() {
@ -164,14 +161,17 @@ open class Cordform : DefaultTask() {
}
}
private fun generateAndInstallNetworkParameters() {
project.logger.info("Generating and installing network parameters")
val networkParamsGenClass = loadNetworkParamsGenClass()
val nodeDirs = nodes.map(Node::fullPath)
val networkParamsGenObject = networkParamsGenClass.newInstance()
val runMethod = networkParamsGenClass.getMethod("run", List::class.java).apply { isAccessible = true }
// Call NetworkParametersGenerator.run
runMethod.invoke(networkParamsGenObject, nodeDirs)
private fun bootstrapNetwork() {
val networkBootstrapperClass = loadNetworkBootstrapperClass()
val networkBootstrapper = networkBootstrapperClass.newInstance()
val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true }
// Call NetworkBootstrapper.bootstrap
try {
val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize()
bootstrapMethod.invoke(networkBootstrapper, rootDir)
} catch (e: InvocationTargetException) {
throw e.cause!!
}
}
private fun CordformDefinition.getMatchingCordapps(): List<File> {
@ -197,90 +197,4 @@ open class Cordform : DefaultTask() {
return false
}
}
private fun generateAndInstallNodeInfos() {
generateNodeInfos()
installNodeInfos()
}
private fun generateNodeInfos() {
project.logger.info("Generating node infos")
val nodeProcesses = buildNodeProcesses()
try {
validateNodeProcessess(nodeProcesses)
} finally {
destroyNodeProcesses(nodeProcesses)
}
}
private fun buildNodeProcesses(): Map<Node, Process> {
val command = generateNodeInfoCommand()
return nodes.map {
it.makeLogDirectory()
buildProcess(it, command, "generate-info.log") }.toMap()
}
private fun validateNodeProcessess(nodeProcesses: Map<Node, Process>) {
nodeProcesses.forEach { (node, process) ->
validateNodeProcess(node, process)
}
}
private fun destroyNodeProcesses(nodeProcesses: Map<Node, Process>) {
nodeProcesses.forEach { (_, process) ->
process.destroyForcibly()
}
}
private fun buildProcess(node: Node, command: List<String>, logFile: String): Pair<Node, Process> {
val process = ProcessBuilder(command)
.directory(node.fullPath().toFile())
.redirectErrorStream(true)
// InheritIO causes hangs on windows due the gradle buffer also not being flushed.
// Must redirect to output or logger (node log is still written, this is just startup banner)
.redirectOutput(node.logFile(logFile).toFile())
.addEnvironment("CAPSULE_CACHE_DIR", Node.capsuleCacheDir)
.start()
return Pair(node, process)
}
private fun generateNodeInfoCommand(): List<String> = listOf(
"java",
"-Dcapsule.log=verbose",
"-Dcapsule.dir=${Node.capsuleCacheDir}",
"-jar",
Node.nodeJarName,
"--just-generate-node-info"
)
private fun validateNodeProcess(node: Node, process: Process) {
val generateTimeoutSeconds = 60L
if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) {
throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${node.fullPath()}/logs")
}
if (process.exitValue() != 0) {
throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${node.fullPath()}/logs")
}
project.logger.info("Generated node info for ${node.fullPath()}")
}
private fun installNodeInfos() {
project.logger.info("Installing node infos")
for (source in nodes) {
for (destination in nodes) {
if (source.nodeDir != destination.nodeDir) {
project.copy {
it.apply {
from(source.fullPath().toString())
include("nodeInfo-*")
into(destination.fullPath().resolve(CordformNode.NODE_INFO_DIRECTORY).toString())
}
}
}
}
}
}
private fun Node.logFile(name: String): Path = this.logDirectory().resolve(name)
private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) }
}

View File

@ -20,13 +20,8 @@ class Node(private val project: Project) : CordformNode() {
@JvmStatic
val webJarName = "corda-webserver.jar"
private val configFileProperty = "configFile"
val capsuleCacheDir: String = "./cache"
}
fun fullPath(): Path = project.projectDir.toPath().resolve(nodeDir.toPath())
fun logDirectory(): Path = fullPath().resolve("logs")
fun makeLogDirectory() = Files.createDirectories(logDirectory())
/**
* Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven
* dependency name, eg: com.example:product-name:0.1

View File

@ -1,31 +0,0 @@
package net.corda.nodeapi.internal
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.sign
import net.corda.core.internal.copyTo
import net.corda.core.internal.div
import net.corda.core.serialization.serialize
import java.math.BigInteger
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
class NetworkParametersCopier(networkParameters: NetworkParameters) {
private companion object {
val DUMMY_MAP_KEY = entropyToKeyPair(BigInteger.valueOf(123))
}
private val serializedNetworkParameters = networkParameters.let {
val serialize = it.serialize()
val signature = DUMMY_MAP_KEY.sign(serialize)
SignedData(serialize, signature).serialize()
}
fun install(dir: Path) {
try {
serializedNetworkParameters.open().copyTo(dir / NETWORK_PARAMS_FILE_NAME)
} catch (e: FileAlreadyExistsException) {
// Leave the file untouched if it already exists
}
}
}

View File

@ -1,107 +0,0 @@
package net.corda.nodeapi.internal
import com.typesafe.config.ConfigFactory
import net.corda.core.identity.Party
import net.corda.core.internal.div
import net.corda.core.internal.list
import net.corda.core.internal.readAll
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.days
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import java.nio.file.Path
import java.time.Instant
/**
* This class is loaded by Cordform using reflection to generate the network parameters. It is assumed that Cordform has
* already asked each node to generate its node info file.
*/
@Suppress("UNUSED")
class NetworkParametersGenerator {
companion object {
private val logger = contextLogger()
}
fun run(nodesDirs: List<Path>) {
logger.info("NetworkParameters generation using node directories: $nodesDirs")
try {
initialiseSerialization()
val notaryInfos = gatherNotaryIdentities(nodesDirs)
val copier = NetworkParametersCopier(NetworkParameters(
minimumPlatformVersion = 1,
notaries = notaryInfos,
modifiedTime = Instant.now(),
maxMessageSize = 10485760,
maxTransactionSize = 40000,
epoch = 1
))
nodesDirs.forEach(copier::install)
} finally {
_contextSerializationEnv.set(null)
}
}
private fun gatherNotaryIdentities(nodesDirs: List<Path>): List<NotaryInfo> {
return nodesDirs.mapNotNull { nodeDir ->
val nodeConfig = ConfigFactory.parseFile((nodeDir / "node.conf").toFile())
if (nodeConfig.hasPath("notary")) {
val validating = nodeConfig.getConfig("notary").getBoolean("validating")
val nodeInfoFile = nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() }
processFile(nodeInfoFile)?.let { NotaryInfo(it.notaryIdentity(), validating) }
} else {
null
}
}.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity
}
private fun NodeInfo.notaryIdentity(): Party {
return when (legalIdentities.size) {
// Single node notaries have just one identity like all other nodes. This identity is the notary identity
1 -> legalIdentities[0]
// Nodes which are part of a distributed notary have a second identity which is the composite identity of the
// cluster and is shared by all the other members. This is the notary identity.
2 -> legalIdentities[1]
else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this")
}
}
private fun processFile(file: Path): NodeInfo? {
return try {
logger.info("Reading NodeInfo from file: $file")
val signedData = file.readAll().deserialize<SignedNodeInfo>()
signedData.verified()
} catch (e: Exception) {
logger.warn("Exception parsing NodeInfo from file. $file", e)
null
}
}
// We need to to set serialization env, because generation of parameters is run from Cordform.
// KryoServerSerializationScheme is not accessible from nodeapi.
private fun initialiseSerialization() {
_contextSerializationEnv.set(SerializationEnvironmentImpl(
SerializationFactoryImpl().apply {
registerScheme(KryoParametersSerializationScheme)
registerScheme(AMQPServerSerializationScheme())
},
AMQP_P2P_CONTEXT)
)
}
private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() {
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
}
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
}
}

View File

@ -0,0 +1,167 @@
package net.corda.nodeapi.internal.network
import com.typesafe.config.ConfigFactory
import net.corda.cordform.CordformNode
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.time.Instant
import java.util.concurrent.TimeUnit.SECONDS
import kotlin.streams.toList
/**
* Class to bootstrap a local network of Corda nodes on the same filesystem.
*/
class NetworkBootstrapper {
companion object {
// TODO This will probably need to change once we start using a bundled JVM
private val nodeInfoGenCmd = listOf(
"java",
"-jar",
"corda.jar",
"--just-generate-node-info"
)
private const val LOGS_DIR_NAME = "logs"
@JvmStatic
fun main(args: Array<String>) {
val arg = args.singleOrNull() ?: throw IllegalArgumentException("Expecting single argument which is the nodes' parent directory")
NetworkBootstrapper().bootstrap(Paths.get(arg).toAbsolutePath().normalize())
}
}
fun bootstrap(directory: Path) {
directory.createDirectories()
println("Bootstrapping local network in $directory")
val nodeDirs = directory.list { paths -> paths.filter { (it / "corda.jar").exists() }.toList() }
require(nodeDirs.isNotEmpty()) { "No nodes found" }
println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}")
val processes = startNodeInfoGeneration(nodeDirs)
initialiseSerialization()
try {
println("Waiting for all nodes to generate their node-info files")
val nodeInfoFiles = gatherNodeInfoFiles(processes, nodeDirs)
println("Distributing all node info-files to all nodes")
distributeNodeInfos(nodeDirs, nodeInfoFiles)
println("Gathering notary identities")
val notaryInfos = gatherNotaryInfos(nodeInfoFiles)
println("Notary identities to be used in network-parameters file: ${notaryInfos.joinToString("; ") { it.prettyPrint() }}")
installNetworkParameters(notaryInfos, nodeDirs)
println("Bootstrapping complete!")
} finally {
_contextSerializationEnv.set(null)
processes.forEach { if (it.isAlive) it.destroyForcibly() }
}
}
private fun startNodeInfoGeneration(nodeDirs: List<Path>): List<Process> {
return nodeDirs.map { nodeDir ->
val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories()
ProcessBuilder(nodeInfoGenCmd)
.directory(nodeDir.toFile())
.redirectErrorStream(true)
.redirectOutput((logsDir / "node-info-gen.log").toFile())
.apply { environment()["CAPSULE_CACHE_DIR"] = "../.cache" }
.start()
}
}
private fun gatherNodeInfoFiles(processes: List<Process>, nodeDirs: List<Path>): List<Path> {
val timeOutInSeconds = 60L
return processes.zip(nodeDirs).map { (process, nodeDir) ->
check(process.waitFor(timeOutInSeconds, SECONDS)) {
"Node in ${nodeDir.fileName} took longer than ${timeOutInSeconds}s to generate its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}"
}
check(process.exitValue() == 0) {
"Node in ${nodeDir.fileName} exited with ${process.exitValue()} when generating its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}"
}
nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() }
}
}
private fun distributeNodeInfos(nodeDirs: List<Path>, nodeInfoFiles: List<Path>) {
for (nodeDir in nodeDirs) {
val additionalNodeInfosDir = (nodeDir / CordformNode.NODE_INFO_DIRECTORY).createDirectories()
for (nodeInfoFile in nodeInfoFiles) {
nodeInfoFile.copyToDirectory(additionalNodeInfosDir, StandardCopyOption.REPLACE_EXISTING)
}
}
}
private fun gatherNotaryInfos(nodeInfoFiles: List<Path>): List<NotaryInfo> {
return nodeInfoFiles.mapNotNull { nodeInfoFile ->
// The config contains the notary type
val nodeConfig = ConfigFactory.parseFile((nodeInfoFile.parent / "node.conf").toFile())
if (nodeConfig.hasPath("notary")) {
val validating = nodeConfig.getConfig("notary").getBoolean("validating")
// And the node-info file contains the notary's identity
val nodeInfo = nodeInfoFile.readAll().deserialize<SignedNodeInfo>().verified()
NotaryInfo(nodeInfo.notaryIdentity(), validating)
} else {
null
}
}.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity
}
private fun installNetworkParameters(notaryInfos: List<NotaryInfo>, nodeDirs: List<Path>) {
// TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
val copier = NetworkParametersCopier(NetworkParameters(
minimumPlatformVersion = 1,
notaries = notaryInfos,
modifiedTime = Instant.now(),
maxMessageSize = 10485760,
maxTransactionSize = 40000,
epoch = 1
), overwriteFile = true)
nodeDirs.forEach(copier::install)
}
private fun NotaryInfo.prettyPrint(): String = "${identity.name} (${if (validating) "" else "non-"}validating)"
private fun NodeInfo.notaryIdentity(): Party {
return when (legalIdentities.size) {
// Single node notaries have just one identity like all other nodes. This identity is the notary identity
1 -> legalIdentities[0]
// Nodes which are part of a distributed notary have a second identity which is the composite identity of the
// cluster and is shared by all the other members. This is the notary identity.
2 -> legalIdentities[1]
else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this")
}
}
// We need to to set serialization env, because generation of parameters is run from Cordform.
// KryoServerSerializationScheme is not accessible from nodeapi.
private fun initialiseSerialization() {
_contextSerializationEnv.set(SerializationEnvironmentImpl(
SerializationFactoryImpl().apply {
registerScheme(KryoParametersSerializationScheme)
registerScheme(AMQPServerSerializationScheme())
},
AMQP_P2P_CONTEXT)
)
}
private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() {
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
}
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
}
}

View File

@ -1,4 +1,4 @@
package net.corda.nodeapi.internal
package net.corda.nodeapi.internal.network
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
@ -15,7 +15,7 @@ import java.security.cert.X509Certificate
import java.time.Instant
const val NETWORK_PARAMS_FILE_NAME = "network-parameters"
// TODO: Need more discussion on rather we should move this class out of internal.
/**
* Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes.
*/

View File

@ -0,0 +1,35 @@
package net.corda.nodeapi.internal.network
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign
import net.corda.core.internal.copyTo
import net.corda.core.internal.div
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.crypto.X509Utilities
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.security.KeyPair
class NetworkParametersCopier(
networkParameters: NetworkParameters,
signingKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME),
overwriteFile: Boolean = false
) {
private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
private val serializedNetworkParameters = networkParameters.let {
val serialize = it.serialize()
val signature = signingKeyPair.sign(serialize)
SignedData(serialize, signature).serialize()
}
fun install(nodeDir: Path) {
try {
serializedNetworkParameters.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions)
} catch (e: FileAlreadyExistsException) {
// This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we
// ignore this exception as we're happy with the existing file.
}
}
}

View File

@ -1,4 +1,4 @@
package net.corda.nodeapi.internal
package net.corda.nodeapi.internal.network
import net.corda.cordform.CordformNode
import net.corda.core.internal.ThreadBox
@ -65,7 +65,6 @@ class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseabl
}
/**
* @param nodeConfig the configuration to be removed.
* Remove the configuration of a node which is about to be stopped or already stopped.
* No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this
* one.

View File

@ -1,7 +1,7 @@
package net.corda.nodeapi
package net.corda.nodeapi.internal.network
import net.corda.cordform.CordformNode
import net.corda.nodeapi.internal.NodeInfoFilesCopier
import net.corda.nodeapi.eventually
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -14,14 +14,7 @@ import java.util.concurrent.TimeUnit
import kotlin.streams.toList
import kotlin.test.assertEquals
/**
* tests for [NodeInfoFilesCopier]
*/
class NodeInfoFilesCopierTest {
@Rule @JvmField var folder = TemporaryFolder()
private val rootPath get() = folder.root.toPath()
private val scheduler = TestScheduler()
companion object {
private const val ORGANIZATION = "Organization"
private const val NODE_1_PATH = "node1"
@ -33,6 +26,13 @@ class NodeInfoFilesCopierTest {
private val BAD_NODE_INFO_NAME = "something"
}
@Rule
@JvmField
val folder = TemporaryFolder()
private val rootPath get() = folder.root.toPath()
private val scheduler = TestScheduler()
private fun nodeDir(nodeBaseDir : String) = rootPath.resolve(nodeBaseDir).resolve(ORGANIZATION.toLowerCase())
private val node1RootPath by lazy { nodeDir(NODE_1_PATH) }
@ -40,7 +40,7 @@ class NodeInfoFilesCopierTest {
private val node1AdditionalNodeInfoPath by lazy { node1RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) }
private val node2AdditionalNodeInfoPath by lazy { node2RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) }
lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier
private lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier
@Before
fun setUp() {

View File

@ -25,9 +25,9 @@ import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.minClusterSize
import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.nodeapi.internal.ServiceIdentityGenerator
import net.corda.nodeapi.internal.NotaryInfo
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.chooseIdentity
import net.corda.nodeapi.internal.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand

View File

@ -1,19 +1,20 @@
package net.corda.node.services.network
import net.corda.core.crypto.SignedData
import net.corda.core.internal.list
import net.corda.core.internal.readAll
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.NetworkParameters
import net.corda.testing.node.internal.CompatibilityZoneParams
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.testing.ALICE_NAME
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.BOB_NAME
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.PortAllocation
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
@ -22,8 +23,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.net.URL
import java.nio.file.Files
import kotlin.streams.toList
import kotlin.test.assertEquals
class NetworkMapTest {
@ -51,10 +50,12 @@ class NetworkMapTest {
@Test
fun `node correctly downloads and saves network parameters file on startup`() {
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone, initialiseSerialization = false) {
val aliceDir = baseDirectory(ALICE_NAME)
startNode(providedName = ALICE_NAME).getOrThrow()
val networkParameters = Files.list(aliceDir).toList().single { NETWORK_PARAMS_FILE_NAME == it.fileName.toString() }
.readAll().deserialize<SignedData<NetworkParameters>>().verified()
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val networkParameters = alice.configuration.baseDirectory
.list { paths -> paths.filter { it.fileName.toString() == NETWORK_PARAMS_FILE_NAME }.findFirst().get() }
.readAll()
.deserialize<SignedData<NetworkParameters>>()
.verified()
assertEquals(NetworkMapServer.stubNetworkParameter, networkParameters)
}
}

View File

@ -7,8 +7,8 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.KeyManagementService
import net.corda.nodeapi.internal.NodeInfoFilesCopier
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import net.corda.testing.ALICE_NAME
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.internal.createNodeInfoAndSigned

View File

@ -59,10 +59,10 @@ import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSoftLockManager
import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.internal.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.NetworkParameters
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.*
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
@ -73,7 +73,6 @@ import rx.Observable
import rx.Scheduler
import java.io.IOException
import java.lang.reflect.InvocationTargetException
import java.nio.file.Files
import java.security.KeyPair
import java.security.KeyStoreException
import java.security.PublicKey
@ -87,7 +86,6 @@ import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit.SECONDS
import kotlin.collections.set
import kotlin.reflect.KClass
import kotlin.streams.toList
import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
/**
@ -210,10 +208,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
val identityService = makeIdentityService(identity.certificate)
networkMapClient = configuration.compatibilityZoneURL?.let {
NetworkMapClient(it, identityService.trustRoot)
}
readNetworkParameters()
networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) }
retrieveNetworkParameters()
// Do all of this in a database transaction so anything that might need a connection has one.
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database ->
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService)
@ -648,29 +644,31 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
return PersistentKeyManagementService(identityService, keyPairs)
}
private fun readNetworkParameters() {
val files = Files.list(configuration.baseDirectory).filter { NETWORK_PARAMS_FILE_NAME == it.fileName.toString() }.toList()
val paramsFromFile = try {
// It's fine at this point if we don't have network parameters or have corrupted file, later we check if parameters can be downloaded from network map server.
files[0].readAll().deserialize<SignedData<NetworkParameters>>().verified()
} catch (t: Exception) {
log.warn("Couldn't find correct network parameters file in the base directory")
null
private fun retrieveNetworkParameters() {
val networkParamsFile = configuration.baseDirectory.list { paths ->
paths.filter { it.fileName.toString() == NETWORK_PARAMS_FILE_NAME }.findFirst().orElse(null)
}
networkParameters = if (paramsFromFile != null) {
paramsFromFile
} else if (networkMapClient != null) {
log.info("Requesting network parameters from network map server...")
val (networkMap, _) = networkMapClient!!.getNetworkMap()
val signedParams = networkMapClient!!.getNetworkParameter(networkMap.networkParameterHash) ?: throw IllegalArgumentException("Failed loading network parameters from network map server")
val verifiedParams = signedParams.verified() // Verify before saving.
networkParameters = if (networkParamsFile != null) {
networkParamsFile.readAll().deserialize<SignedData<NetworkParameters>>().verified()
} else {
log.info("No network-parameters file found. Expecting network parameters to be available from the network map.")
val networkMapClient = checkNotNull(networkMapClient) {
"Node hasn't been configured to connect to a network map from which to get the network parameters"
}
val (networkMap, _) = networkMapClient.getNetworkMap()
val signedParams = checkNotNull(networkMapClient.getNetworkParameter(networkMap.networkParameterHash)) {
"Failed loading network parameters from network map server"
}
val verifiedParams = signedParams.verified()
signedParams.serialize().open().copyTo(configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME)
verifiedParams
} else {
throw IllegalArgumentException("Couldn't load network parameters file")
}
log.info("Loaded network parameters $networkParameters")
check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node's platform version is lower than network's required minimumPlatformVersion" }
log.info("Loaded network parameters: $networkParameters")
check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) {
"Node's platform version is lower than network's required minimumPlatformVersion"
}
}
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService {

View File

@ -12,9 +12,9 @@ import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.utilities.NamedThreadFactory
import net.corda.nodeapi.internal.NetworkMap
import net.corda.nodeapi.internal.NetworkParameters
import net.corda.nodeapi.internal.SignedNetworkMap
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.SignedNetworkMap
import net.corda.nodeapi.internal.SignedNodeInfo
import okhttp3.CacheControl
import okhttp3.Headers

View File

@ -8,7 +8,7 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.NodeInfoFilesCopier
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import net.corda.nodeapi.internal.SignedNodeInfo
import rx.Observable
import rx.Scheduler

View File

@ -25,7 +25,7 @@ import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
import net.corda.nodeapi.internal.NotaryInfo
import net.corda.nodeapi.internal.network.NotaryInfo
import org.hibernate.Session
import rx.Observable
import rx.subjects.PublishSubject

View File

@ -7,17 +7,21 @@ import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueFlow
import net.corda.node.services.config.NotaryConfig
import net.corda.nodeapi.internal.NetworkParameters
import net.corda.nodeapi.internal.NetworkParametersCopier
import net.corda.nodeapi.internal.NotaryInfo
import net.corda.testing.*
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.DUMMY_NOTARY_NAME
import net.corda.testing.chooseIdentity
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.node.*
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
import org.junit.Test
import java.nio.file.Path
import kotlin.test.assertFails
import org.assertj.core.api.Assertions.*
class NetworkParametersTest {
private val mockNet = MockNetwork(

View File

@ -16,8 +16,8 @@ import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
import net.corda.core.utilities.millis
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.nodeapi.internal.NetworkMap
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.testing.ALICE_NAME
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.internal.TestNodeInfoBuilder

View File

@ -33,6 +33,7 @@ include 'tools:explorer:capsule'
include 'tools:demobench'
include 'tools:loadtest'
include 'tools:graphs'
include 'tools:bootstrapper'
include 'example-code'
project(':example-code').projectDir = file("$settingsDir/docs/source/example-code")
include 'samples:attachment-demo'

View File

@ -22,8 +22,8 @@ import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.node.VersionInfo
import net.corda.core.utilities.seconds
import net.corda.node.VersionInfo
import net.corda.node.internal.AbstractNode
import net.corda.node.internal.StartedNode
import net.corda.node.internal.cordapp.CordappLoader
@ -37,18 +37,18 @@ import net.corda.node.services.transactions.BFTSMaRt
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.nodeapi.internal.NetworkParametersCopier
import net.corda.nodeapi.internal.NotaryInfo
import net.corda.nodeapi.internal.ServiceIdentityGenerator
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.DUMMY_NOTARY_NAME
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.testThreadFactory
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.internal.rigorousMock
import net.corda.testing.setGlobalSerialization
import org.apache.activemq.artemis.utils.ReusableLatch
import org.apache.sshd.common.util.security.SecurityUtils
@ -229,12 +229,12 @@ class MockNetwork(private val cordappPackages: List<String>,
}
private fun generateNotaryIdentities(): List<NotaryInfo> {
return notarySpecs.mapIndexed { index, spec ->
return notarySpecs.mapIndexed { index, (name, validating) ->
val identity = ServiceIdentityGenerator.generateToDisk(
dirs = listOf(baseDirectory(nextNodeId + index)),
serviceName = spec.name,
serviceName = name,
serviceId = "identity")
NotaryInfo(identity, spec.validating)
NotaryInfo(identity, validating)
}
}

View File

@ -35,7 +35,8 @@ import net.corda.node.services.transactions.RaftNonValidatingNotaryService
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.nodeapi.internal.*
import net.corda.nodeapi.internal.ServiceIdentityGenerator
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.config.toConfig
@ -43,8 +44,13 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.addOrReplaceCertificate
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
import net.corda.nodeapi.internal.crypto.save
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.DUMMY_BANK_A_NAME
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.*
import net.corda.testing.driver.*
import net.corda.testing.node.ClusterSpec
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO

View File

@ -12,9 +12,9 @@ import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.services.config.*
import net.corda.nodeapi.internal.NetworkParametersCopier
import net.corda.nodeapi.internal.config.User
import net.corda.testing.SerializationEnvironmentRule
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.getFreeLocalPorts
import net.corda.testing.internal.testThreadFactory

View File

@ -7,6 +7,10 @@ import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.network.DigitalSignatureWithCert
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.SignedNetworkMap
import net.corda.nodeapi.internal.*
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType

View File

@ -8,7 +8,7 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.common.internal.asContextEnv
import java.nio.file.Path

View File

@ -1,7 +1,7 @@
package net.corda.testing.common.internal
import net.corda.nodeapi.internal.NetworkParameters
import net.corda.nodeapi.internal.NotaryInfo
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.NotaryInfo
import java.time.Instant
fun testNetworkParameters(

View File

@ -0,0 +1,27 @@
apply plugin: 'us.kirchmeier.capsule'
description 'Network bootstrapper'
configurations {
runtimeArtifacts
}
// TODO Fix SLF4J warnings that occur when running the bootstrapper
task buildBootstrapperJar(type: FatCapsule, dependsOn: project(':node-api').compileJava) {
applicationClass 'net.corda.nodeapi.internal.network.NetworkBootstrapper'
archiveName "network-bootstrapper.jar"
capsuleManifest {
applicationVersion = corda_release_version
systemProperties['visualvm.display.name'] = 'Network Bootstrapper'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
applicationSource = files(
project(':node-api').configurations.runtime,
project(':node-api').jar
)
}
artifacts {
runtimeArtifacts buildBootstrapperJar
}

View File

@ -1,6 +1,6 @@
package net.corda.demobench.model
import net.corda.nodeapi.internal.NodeInfoFilesCopier
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import rx.Scheduler
import rx.schedulers.Schedulers
import tornadofx.*

View File

@ -8,12 +8,11 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.noneOrSingle
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.days
import net.corda.demobench.plugin.CordappController
import net.corda.demobench.pty.R3Pty
import net.corda.nodeapi.internal.NetworkParameters
import net.corda.nodeapi.internal.NetworkParametersCopier
import net.corda.nodeapi.internal.NotaryInfo
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.nodeapi.internal.ServiceIdentityGenerator
import tornadofx.*
import java.io.IOException