mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
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:
parent
e9cead9055
commit
00a5e3db6b
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -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" />
|
||||
|
@ -1,4 +1,4 @@
|
||||
gradlePluginsVersion=3.0.1
|
||||
gradlePluginsVersion=3.0.2
|
||||
kotlinVersion=1.1.60
|
||||
platformVersion=2
|
||||
guavaVersion=21.0
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -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) }
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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.
|
||||
*/
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
@ -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() {
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
27
tools/bootstrapper/build.gradle
Normal file
27
tools/bootstrapper/build.gradle
Normal 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
|
||||
}
|
@ -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.*
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user