mirror of
https://github.com/corda/corda.git
synced 2025-04-06 19:07:08 +00:00
Merge branch 'feature-network-parameters' into shams-merge-feature-network-parameters
This commit is contained in:
commit
6d6393d984
@ -4,7 +4,7 @@ buildscript {
|
||||
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
|
||||
|
||||
// Our version: bump this on release.
|
||||
ext.corda_release_version = "3.0-SNAPSHOT"
|
||||
ext.corda_release_version = "3.0-NETWORKMAP-SNAPSHOT"
|
||||
// Increment this on any release that changes public APIs anywhere in the Corda platform
|
||||
ext.corda_platform_version = constants.getProperty("platformVersion")
|
||||
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
||||
|
@ -25,7 +25,10 @@ import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import rx.subjects.UnicastSubject
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class RPCStabilityTests {
|
||||
|
@ -48,8 +48,7 @@ public class StandaloneCordaRPCJavaClientTest {
|
||||
port.getAndIncrement(),
|
||||
port.getAndIncrement(),
|
||||
true,
|
||||
Collections.singletonList(rpcUser),
|
||||
null
|
||||
Collections.singletonList(rpcUser)
|
||||
);
|
||||
|
||||
@Before
|
||||
|
@ -4,8 +4,4 @@ trustStorePassword : "trustpass"
|
||||
p2pAddress : "localhost:10002"
|
||||
rpcAddress : "localhost:10003"
|
||||
webAddress : "localhost:10004"
|
||||
networkMapService : {
|
||||
address : "localhost:10000"
|
||||
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
|
||||
}
|
||||
useHTTPS : false
|
||||
|
@ -1,4 +1,4 @@
|
||||
gradlePluginsVersion=3.0.0
|
||||
gradlePluginsVersion=3.0.2-NETWORKMAP
|
||||
kotlinVersion=1.1.60
|
||||
platformVersion=2
|
||||
guavaVersion=21.0
|
||||
|
@ -53,7 +53,6 @@ interface NetworkMapCacheBase {
|
||||
*
|
||||
* Note that the identities are sorted based on legal name, and the ordering might change once new notaries are introduced.
|
||||
*/
|
||||
// TODO this list will be taken from NetworkParameters distributed by NetworkMap.
|
||||
val notaryIdentities: List<Party>
|
||||
// DOCEND 1
|
||||
|
||||
@ -117,7 +116,7 @@ interface NetworkMapCacheBase {
|
||||
fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name }
|
||||
// DOCEND 2
|
||||
|
||||
/** Checks whether a given party is an advertised notary identity. */
|
||||
/** Returns true if and only if the given [Party] is a notary, which is defined by the network parameters. */
|
||||
fun isNotary(party: Party): Boolean = party in notaryIdentities
|
||||
|
||||
/**
|
||||
|
@ -50,7 +50,7 @@ class X509NameConstraintsTest {
|
||||
|
||||
val nameConstraints = NameConstraints(acceptableNames, arrayOf())
|
||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||
val certFactory = X509CertificateFactory().delegate
|
||||
val certFactory = X509CertificateFactory()
|
||||
|
||||
assertFailsWith(CertPathValidatorException::class) {
|
||||
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank B"), nameConstraints)
|
||||
|
@ -48,10 +48,6 @@ handling, and ensures the Corda service is run at boot.
|
||||
trustStorePassword : "trustpass"
|
||||
useHTTPS : false
|
||||
devMode : false
|
||||
networkMapService {
|
||||
address="networkmap.foo.bar.com:10002"
|
||||
legalName="O=FooBar NetworkMap, L=Dublin, C=IE"
|
||||
}
|
||||
rpcUsers=[
|
||||
{
|
||||
user=corda
|
||||
@ -223,10 +219,6 @@ at boot, and means the Corda service stays running with no users connected to th
|
||||
extraAdvertisedServiceIds: [ "" ]
|
||||
useHTTPS : false
|
||||
devMode : false
|
||||
networkMapService {
|
||||
address="networkmap.foo.bar.com:10002"
|
||||
legalName="O=FooBar NetworkMap, L=Dublin, C=IE"
|
||||
}
|
||||
rpcUsers=[
|
||||
{
|
||||
user=corda
|
||||
|
@ -10,10 +10,6 @@ dataSourceProperties : {
|
||||
p2pAddress : "my-corda-node:10002"
|
||||
rpcAddress : "my-corda-node:10003"
|
||||
webAddress : "localhost:10004"
|
||||
networkMapService : {
|
||||
address : "my-network-map:10000"
|
||||
legalName : "O=Network Map Service,OU=corda,L=London,C=GB"
|
||||
}
|
||||
useHTTPS : false
|
||||
rpcUsers : [
|
||||
{ username=user1, password=letmein, permissions=[ StartProtocol.net.corda.protocols.CashProtocol ] }
|
||||
|
@ -45,8 +45,6 @@ The most important fields regarding network configuration are:
|
||||
resolvable name of a machine in a VPN.
|
||||
* ``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.
|
||||
* ``networkMapService``: Details of the node running the network map service. If it's this node that's running the service
|
||||
then this field must not be specified.
|
||||
|
||||
Starting the nodes
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
@ -17,9 +17,6 @@ dependencies {
|
||||
|
||||
// TypeSafe Config: for simple and human friendly config files.
|
||||
compile "com.typesafe:config:$typesafe_config_version"
|
||||
|
||||
// Bouncy Castle: for X.500 distinguished name manipulation
|
||||
compile "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
|
||||
}
|
||||
|
||||
publish {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.cordform;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface CordformContext {
|
||||
|
@ -1,14 +1,16 @@
|
||||
package net.corda.cordform;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import com.typesafe.config.*;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import com.typesafe.config.ConfigValueFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
public class CordformNode implements NodeDefinition {
|
||||
/**
|
||||
* Path relative to the running node where the serialized NodeInfos are stored.
|
||||
|
@ -117,6 +117,16 @@ open class Cordform : DefaultTask() {
|
||||
.newInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* The parametersGenerator needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||
*/
|
||||
private fun loadNetworkParamsGenClass(): 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")
|
||||
}
|
||||
|
||||
/**
|
||||
* This task action will create and install the nodes based on the node configurations added.
|
||||
*/
|
||||
@ -127,6 +137,7 @@ open class Cordform : DefaultTask() {
|
||||
installRunScript()
|
||||
nodes.forEach(Node::build)
|
||||
generateAndInstallNodeInfos()
|
||||
generateAndInstallNetworkParameters()
|
||||
}
|
||||
|
||||
private fun initializeConfiguration() {
|
||||
@ -153,6 +164,16 @@ 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 CordformDefinition.getMatchingCordapps(): List<File> {
|
||||
val cordappJars = project.configuration("cordapp").files
|
||||
return cordappPackages.map { `package` ->
|
||||
@ -193,9 +214,10 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
|
||||
private fun buildNodeProcesses(): Map<Node, Process> {
|
||||
return nodes
|
||||
.map { buildNodeProcess(it) }
|
||||
.toMap()
|
||||
val command = generateNodeInfoCommand()
|
||||
return nodes.map {
|
||||
it.makeLogDirectory()
|
||||
buildProcess(it, command, "generate-info.log") }.toMap()
|
||||
}
|
||||
|
||||
private fun validateNodeProcessess(nodeProcesses: Map<Node, Process>) {
|
||||
@ -210,14 +232,13 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildNodeProcess(node: Node): Pair<Node, Process> {
|
||||
node.makeLogDirectory()
|
||||
val process = ProcessBuilder(generateNodeInfoCommand())
|
||||
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().toFile())
|
||||
.redirectOutput(node.logFile(logFile).toFile())
|
||||
.addEnvironment("CAPSULE_CACHE_DIR", Node.capsuleCacheDir)
|
||||
.start()
|
||||
return Pair(node, process)
|
||||
@ -260,7 +281,6 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log")
|
||||
|
||||
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) }
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.cordform.CordformNode
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.gradle.api.Project
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
@ -62,21 +60,6 @@ class Node(private val project: Project) : CordformNode() {
|
||||
config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the network map address for this node.
|
||||
*
|
||||
* @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the
|
||||
* Cordform task instead.
|
||||
* @param networkMapAddress Network map node address.
|
||||
* @param networkMapLegalName Network map node legal name.
|
||||
*/
|
||||
fun networkMapAddress(networkMapAddress: String, networkMapLegalName: String) {
|
||||
val networkMapService = mutableMapOf<String, String>()
|
||||
networkMapService.put("address", networkMapAddress)
|
||||
networkMapService.put("legalName", networkMapLegalName)
|
||||
config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService))
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables SSH access on given port
|
||||
*
|
||||
@ -104,14 +87,10 @@ class Node(private val project: Project) : CordformNode() {
|
||||
project.logger.error("Node has a null name - cannot create node")
|
||||
throw IllegalStateException("Node has a null name - cannot create node")
|
||||
}
|
||||
|
||||
val dirName = try {
|
||||
val o = X500Name(name).getRDNs(BCStyle.O)
|
||||
if (o.isNotEmpty()) o.first().first.value.toString() else name
|
||||
} catch (_ : IllegalArgumentException) {
|
||||
// Can't parse as an X500 name, use the full string
|
||||
name
|
||||
}
|
||||
// Parsing O= part directly because importing BouncyCastle provider in Cordformation causes problems
|
||||
// with loading our custom X509EdDSAEngine.
|
||||
val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=")
|
||||
val dirName = organizationName ?: name
|
||||
nodeDir = File(rootDir.toFile(), dirName)
|
||||
}
|
||||
|
||||
@ -129,7 +108,7 @@ class Node(private val project: Project) : CordformNode() {
|
||||
* Installs the corda fat JAR to the node directory.
|
||||
*/
|
||||
private fun installCordaJar() {
|
||||
val cordaJar = verifyAndGetCordaJar()
|
||||
val cordaJar = verifyAndGetRuntimeJar("corda")
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(cordaJar)
|
||||
@ -144,7 +123,7 @@ class Node(private val project: Project) : CordformNode() {
|
||||
* Installs the corda webserver JAR to the node directory
|
||||
*/
|
||||
private fun installWebserverJar() {
|
||||
val webJar = verifyAndGetWebserverJar()
|
||||
val webJar = verifyAndGetRuntimeJar("corda-webserver")
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(webJar)
|
||||
@ -250,34 +229,17 @@ class Node(private val project: Project) : CordformNode() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the corda JAR amongst the dependencies.
|
||||
* Find the given JAR amongst the dependencies
|
||||
* @param jarName JAR name without the version part, for example for corda-2.0-SNAPSHOT.jar provide only "corda" as jarName
|
||||
*
|
||||
* @return A file representing the Corda JAR.
|
||||
* @return A file representing found JAR
|
||||
*/
|
||||
private fun verifyAndGetCordaJar(): File {
|
||||
val maybeCordaJAR = project.configuration("runtime").filter {
|
||||
it.toString().contains("corda-$releaseVersion.jar") || it.toString().contains("corda-enterprise-$releaseVersion.jar")
|
||||
}
|
||||
if (maybeCordaJAR.isEmpty) {
|
||||
throw RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-$releaseVersion.jar\"")
|
||||
} else {
|
||||
val cordaJar = maybeCordaJAR.singleFile
|
||||
assert(cordaJar.isFile)
|
||||
return cordaJar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the corda JAR amongst the dependencies
|
||||
*
|
||||
* @return A file representing the Corda webserver JAR
|
||||
*/
|
||||
private fun verifyAndGetWebserverJar(): File {
|
||||
private fun verifyAndGetRuntimeJar(jarName: String): File {
|
||||
val maybeJar = project.configuration("runtime").filter {
|
||||
it.toString().contains("corda-webserver-$releaseVersion.jar")
|
||||
"$jarName-$releaseVersion.jar" in it.toString() || "$jarName-enterprise-$releaseVersion.jar" in it.toString()
|
||||
}
|
||||
if (maybeJar.isEmpty) {
|
||||
throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"")
|
||||
throw IllegalStateException("No $jarName JAR found. Have you deployed the Corda project to Maven? Looked for \"$jarName-$releaseVersion.jar\"")
|
||||
} else {
|
||||
val jar = maybeJar.singleFile
|
||||
require(jar.isFile)
|
||||
|
@ -0,0 +1,75 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.verify
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import java.security.SignatureException
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
// 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.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class NetworkMap(val nodeInfoHashes: List<SecureHash>, val networkParameterHash: SecureHash)
|
||||
|
||||
/**
|
||||
* @property minimumPlatformVersion
|
||||
* @property notaries
|
||||
* @property eventHorizon
|
||||
* @property maxMessageSize Maximum P2P message sent over the wire in bytes.
|
||||
* @property maxTransactionSize Maximum permitted transaction size in bytes.
|
||||
* @property modifiedTime
|
||||
* @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
|
||||
* of parameters.
|
||||
*/
|
||||
// TODO Wire up the parameters
|
||||
@CordaSerializable
|
||||
data class NetworkParameters(
|
||||
val minimumPlatformVersion: Int,
|
||||
val notaries: List<NotaryInfo>,
|
||||
val eventHorizon: Duration,
|
||||
val maxMessageSize: Int,
|
||||
val maxTransactionSize: Int,
|
||||
val modifiedTime: Instant,
|
||||
val epoch: Int
|
||||
) {
|
||||
init {
|
||||
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
|
||||
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
|
||||
require(epoch > 0) { "epoch must be at least 1" }
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
data class NotaryInfo(val identity: Party, val validating: Boolean)
|
||||
|
||||
/**
|
||||
* A serialized [NetworkMap] and its signature and certificate. Enforces signature validity in order to deserialize the data
|
||||
* contained within.
|
||||
*/
|
||||
@CordaSerializable
|
||||
class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val sig: DigitalSignatureWithCert) {
|
||||
/**
|
||||
* Return the deserialized NetworkMap if the signature and certificate can be verified.
|
||||
*
|
||||
* @throws CertPathValidatorException if the certificate path is invalid.
|
||||
* @throws SignatureException if the signature is invalid.
|
||||
*/
|
||||
@Throws(SignatureException::class)
|
||||
fun verified(): NetworkMap {
|
||||
sig.by.publicKey.verify(raw.bytes, sig)
|
||||
return raw.deserialize()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This class should reside in the [DigitalSignature] class.
|
||||
/** A digital signature that identifies who the public key is owned by, and the certificate which provides prove of the identity */
|
||||
class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
|
@ -0,0 +1,32 @@
|
||||
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 net.corda.nodeapi.internal.NetworkParameters
|
||||
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-parameters")
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
// Leave the file untouched if it already exists
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.core.crypto.SignedData
|
||||
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.KRYO_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(),
|
||||
eventHorizon = 10000.days,
|
||||
maxMessageSize = 40000,
|
||||
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<SignedData<NodeInfo>>()
|
||||
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() {
|
||||
val context = if (java.lang.Boolean.getBoolean("net.corda.testing.amqp.enable")) AMQP_P2P_CONTEXT else KRYO_P2P_CONTEXT
|
||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoParametersSerializationScheme)
|
||||
registerScheme(AMQPServerSerializationScheme())
|
||||
},
|
||||
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
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.internal.ThreadBox
|
@ -1,4 +1,4 @@
|
||||
package net.corda.node.utilities
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
@ -11,35 +11,40 @@ import net.corda.core.utilities.trace
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
object ServiceIdentityGenerator {
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
/**
|
||||
* Generates signing key pairs and a common distributed service identity for a set of nodes.
|
||||
* The key pairs and the group identity get serialized to disk in the corresponding node directories.
|
||||
* This method should be called *before* any of the nodes are started.
|
||||
*
|
||||
* @param dirs List of node directories to place the generated identity and key pairs in.
|
||||
* @param serviceName The legal name of the distributed service, with service id as CN.
|
||||
* @param serviceName The legal name of the distributed service.
|
||||
* @param threshold The threshold for the generated group [CompositeKey].
|
||||
* @param customRootCert the certificate to use a Corda root CA. If not specified the one in
|
||||
* certificates/cordadevcakeys.jks is used.
|
||||
*/
|
||||
fun generateToDisk(dirs: List<Path>,
|
||||
serviceName: CordaX500Name,
|
||||
threshold: Int = 1): Party {
|
||||
serviceId: String,
|
||||
threshold: Int = 1,
|
||||
customRootCert: X509Certificate? = null): Party {
|
||||
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
|
||||
val keyPairs = (1..dirs.size).map { generateKeyPair() }
|
||||
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
|
||||
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val issuer = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||
val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||
|
||||
keyPairs.zip(dirs) { keyPair, dir ->
|
||||
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public)
|
||||
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey)
|
||||
val certPath = (dir / "certificates").createDirectories() / "distributedService.jks"
|
||||
val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass")
|
||||
val serviceId = serviceName.commonName
|
||||
keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert)
|
||||
keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, issuer.certificate.cert, rootCert))
|
||||
keystore.save(certPath, "cordacadevpass")
|
@ -15,4 +15,5 @@ interface SSLConfiguration {
|
||||
interface NodeSSLConfiguration : SSLConfiguration {
|
||||
val baseDirectory: Path
|
||||
override val certificatesDirectory: Path get() = baseDirectory / "certificates"
|
||||
val rootCertFile: Path get() = certificatesDirectory / "rootcert.pem"
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ class KeyStoreWrapper(private val storePath: Path, private val storePassword: St
|
||||
val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
||||
// Create new keys and store in keystore.
|
||||
val cert = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
|
||||
val certPath = X509CertificateFactory().delegate.generateCertPath(listOf(cert.cert) + clientCertPath)
|
||||
val certPath = X509CertificateFactory().generateCertPath(cert.cert, *clientCertPath)
|
||||
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
|
||||
// TODO: X509Utilities.validateCertificateChain()
|
||||
return certPath
|
||||
|
@ -4,8 +4,8 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.read
|
||||
import net.corda.core.internal.write
|
||||
import net.corda.core.internal.x500Name
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.millis
|
||||
@ -27,10 +27,8 @@ import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
import java.io.FileWriter
|
||||
import java.io.InputStream
|
||||
import java.math.BigInteger
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
@ -153,7 +151,7 @@ object X509Utilities {
|
||||
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = X509CertificateFactory().delegate.generateCertPath(certificates.toList())
|
||||
val certPath = X509CertificateFactory().generateCertPath(*certificates)
|
||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||
pathValidator.validate(certPath, params)
|
||||
}
|
||||
@ -164,7 +162,7 @@ object X509Utilities {
|
||||
* @param file Target file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, file: Path) {
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, file: Path) {
|
||||
JcaPEMWriter(file.toFile().writer()).use {
|
||||
it.writeObject(x509Certificate)
|
||||
}
|
||||
@ -176,14 +174,14 @@ object X509Utilities {
|
||||
* @return The X509Certificate that was encoded in the file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun loadCertificateFromPEMFile(file: Path): X509CertificateHolder {
|
||||
val cert = file.read {
|
||||
fun loadCertificateFromPEMFile(file: Path): X509Certificate {
|
||||
return file.read {
|
||||
val reader = PemReader(it.reader())
|
||||
val pemObject = reader.readPemObject()
|
||||
X509CertificateHolder(pemObject.content)
|
||||
val certHolder = X509CertificateHolder(pemObject.content)
|
||||
certHolder.isValidOn(Date())
|
||||
certHolder.cert
|
||||
}
|
||||
cert.isValidOn(Date())
|
||||
return cert
|
||||
}
|
||||
|
||||
/**
|
||||
@ -310,9 +308,18 @@ object X509Utilities {
|
||||
*/
|
||||
class X509CertificateFactory {
|
||||
val delegate: CertificateFactory = CertificateFactory.getInstance("X.509")
|
||||
|
||||
fun generateCertificate(input: InputStream): X509Certificate {
|
||||
return delegate.generateCertificate(input) as X509Certificate
|
||||
}
|
||||
|
||||
fun generateCertPath(certificates: List<Certificate>): CertPath {
|
||||
return delegate.generateCertPath(certificates)
|
||||
}
|
||||
|
||||
fun generateCertPath(vararg certificates: Certificate): CertPath {
|
||||
return delegate.generateCertPath(certificates.asList())
|
||||
}
|
||||
}
|
||||
|
||||
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.utilities.NetworkHostAndPort
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import rx.Notification
|
||||
import rx.exceptions.OnErrorNotImplementedException
|
||||
import sun.security.x509.X509CertImpl
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -58,6 +59,9 @@ object DefaultWhitelist : SerializationWhitelist {
|
||||
java.util.LinkedHashMap::class.java,
|
||||
BitSet::class.java,
|
||||
OnErrorNotImplementedException::class.java,
|
||||
StackTraceElement::class.java
|
||||
)
|
||||
StackTraceElement::class.java,
|
||||
|
||||
// Implementation of X509Certificate.
|
||||
X509CertImpl::class.java
|
||||
)
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ import org.objenesis.strategy.StdInstantiatorStrategy
|
||||
import org.slf4j.Logger
|
||||
import sun.security.ec.ECPublicKeyImpl
|
||||
import sun.security.provider.certpath.X509CertPath
|
||||
import sun.security.x509.X509CertImpl
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.FileInputStream
|
||||
@ -75,6 +76,7 @@ object DefaultKryoCustomizer {
|
||||
addDefaultSerializer(InputStream::class.java, InputStreamSerializer)
|
||||
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
|
||||
addDefaultSerializer(Logger::class.java, LoggerSerializer)
|
||||
addDefaultSerializer(X509Certificate::class.java, X509CertificateSerializer)
|
||||
|
||||
// WARNING: reordering the registrations here will cause a change in the serialized form, since classes
|
||||
// with custom serializers get written as registration ids. This will break backwards-compatibility.
|
||||
@ -108,7 +110,6 @@ object DefaultKryoCustomizer {
|
||||
register(FileInputStream::class.java, InputStreamSerializer)
|
||||
register(CertPath::class.java, CertPathSerializer)
|
||||
register(X509CertPath::class.java, CertPathSerializer)
|
||||
register(X509Certificate::class.java, X509CertificateSerializer)
|
||||
register(BCECPrivateKey::class.java, PrivateKeySerializer)
|
||||
register(BCECPublicKey::class.java, publicKeySerializer)
|
||||
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.nodeapi
|
||||
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.nodeapi.internal.NodeInfoFilesCopier
|
||||
import net.corda.testing.eventually
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
|
@ -71,7 +71,7 @@ class X509UtilitiesTest {
|
||||
fun `load and save a PEM file certificate`() {
|
||||
val tmpCertificateFile = tempFile("cacert.pem")
|
||||
val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey)
|
||||
val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey).cert
|
||||
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
|
||||
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
|
||||
assertEquals(caCert, readCertificate)
|
||||
@ -433,7 +433,7 @@ class X509UtilitiesTest {
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
|
||||
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name.x500Name, BOB_PUBKEY)
|
||||
val expected = X509CertificateFactory().delegate.generateCertPath(listOf(certificate.cert, rootCACert.cert))
|
||||
val expected = X509CertificateFactory().generateCertPath(certificate.cert, rootCACert.cert)
|
||||
val serialized = expected.serialize(factory, context).bytes
|
||||
val actual: CertPath = serialized.deserialize(factory, context)
|
||||
assertEquals(expected, actual)
|
||||
|
@ -13,6 +13,7 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.deleteIfExists
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.node.services.NotaryService
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
@ -21,11 +22,13 @@ import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.node.services.config.BFTSMaRtConfiguration
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
|
||||
import net.corda.node.services.transactions.minClusterSize
|
||||
import net.corda.node.services.transactions.minCorrectReplicas
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.internal.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.internal.NotaryInfo
|
||||
import net.corda.testing.chooseIdentity
|
||||
import net.corda.nodeapi.internal.NetworkParametersCopier
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.dummyCommand
|
||||
import net.corda.testing.node.MockNetwork
|
||||
@ -54,19 +57,26 @@ class BFTNotaryServiceTests {
|
||||
|
||||
notary = ServiceIdentityGenerator.generateToDisk(
|
||||
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
|
||||
CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")
|
||||
)
|
||||
CordaX500Name("BFT", "Zurich", "CH"),
|
||||
NotaryService.constructId(validating = false, bft = true))
|
||||
|
||||
val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notary, false))))
|
||||
|
||||
val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) }
|
||||
|
||||
replicaIds.forEach { replicaId ->
|
||||
mockNet.createNode(MockNodeParameters(configOverrides = {
|
||||
val nodes = replicaIds.map { replicaId ->
|
||||
mockNet.createUnstartedNode(MockNodeParameters(configOverrides = {
|
||||
val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces))
|
||||
doReturn(notary).whenever(it).notary
|
||||
}))
|
||||
}
|
||||
} + mockNet.createUnstartedNode()
|
||||
|
||||
node = mockNet.createNode()
|
||||
// MockNetwork doesn't support BFT clusters, so we create all the nodes we need unstarted, and then install the
|
||||
// network-parameters in their directories before they're started.
|
||||
node = nodes.map { node ->
|
||||
networkParameters.install(mockNet.baseDirectory(node.id))
|
||||
node.start()
|
||||
}.last()
|
||||
}
|
||||
|
||||
/** Failure mode is the redundant replica gets stuck in startup, so we can't dispose it cleanly at the end. */
|
||||
|
@ -13,7 +13,6 @@ import net.corda.finance.flows.CashIssueFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
||||
import net.corda.node.services.Permissions.Companion.startFlow
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
@ -42,7 +41,7 @@ class DistributedServiceTests {
|
||||
|
||||
driver(
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance.contracts"),
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name.copy(commonName = RaftValidatingNotaryService.id), rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3))))
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name, rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3))))
|
||||
{
|
||||
alice = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser)).getOrThrow()
|
||||
raftNotaryIdentity = defaultNotaryIdentity
|
||||
|
@ -0,0 +1,100 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import net.corda.testing.internal.CompatibilityZoneParams
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.internal.internalDriver
|
||||
import net.corda.testing.node.network.NetworkMapServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.net.URL
|
||||
|
||||
class NetworkMapTest {
|
||||
private val cacheTimeout = 1.seconds
|
||||
private val portAllocation = PortAllocation.Incremental(10000)
|
||||
|
||||
private lateinit var networkMapServer: NetworkMapServer
|
||||
private lateinit var compatibilityZone: CompatibilityZoneParams
|
||||
|
||||
@Before
|
||||
fun start() {
|
||||
networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort())
|
||||
val address = networkMapServer.start()
|
||||
compatibilityZone = CompatibilityZoneParams(URL("http://$address"))
|
||||
}
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
networkMapServer.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nodes can see each other using the http network map`() {
|
||||
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
val bobNode = bob.get()
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nodes process network map add updates correctly when adding new node to network map`() {
|
||||
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
|
||||
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
val bobNode = bob.get()
|
||||
|
||||
// Wait for network map client to poll for the next update.
|
||||
Thread.sleep(cacheTimeout.toMillis() * 2)
|
||||
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nodes process network map remove updates correctly`() {
|
||||
internalDriver(portAllocation = portAllocation, compatibilityZone = compatibilityZone) {
|
||||
val alice = startNode(providedName = ALICE.name)
|
||||
val bob = startNode(providedName = BOB.name)
|
||||
|
||||
val notaryNode = defaultNotaryNode.get()
|
||||
val aliceNode = alice.get()
|
||||
val bobNode = bob.get()
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
|
||||
|
||||
networkMapServer.removeNodeInfo(aliceNode.nodeInfo)
|
||||
|
||||
// Wait for network map client to poll for the next update.
|
||||
Thread.sleep(cacheTimeout.toMillis() * 2)
|
||||
|
||||
notaryNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
|
||||
bobNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NodeHandle.onlySees(vararg nodes: NodeInfo) = assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes)
|
||||
}
|
@ -10,7 +10,7 @@ import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.nodeapi.NodeInfoFilesCopier
|
||||
import net.corda.nodeapi.internal.NodeInfoFilesCopier
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockKeyManagementService
|
||||
import net.corda.testing.node.makeTestIdentityService
|
||||
|
@ -0,0 +1,138 @@
|
||||
package net.corda.node.utilities.registration
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.toX509CertHolder
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.testing.internal.CompatibilityZoneParams
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.internal.internalDriver
|
||||
import net.corda.testing.node.network.NetworkMapServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.net.URL
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.Certificate
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import javax.ws.rs.*
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
class NodeRegistrationTest {
|
||||
private val portAllocation = PortAllocation.Incremental(13000)
|
||||
private val rootCertAndKeyPair = createSelfKeyAndSelfSignedCertificate()
|
||||
private val registrationHandler = RegistrationHandler(rootCertAndKeyPair)
|
||||
|
||||
private lateinit var server: NetworkMapServer
|
||||
private lateinit var compatibilityZone: CompatibilityZoneParams
|
||||
|
||||
@Before
|
||||
fun startServer() {
|
||||
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler)
|
||||
val address = server.start()
|
||||
compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCert = rootCertAndKeyPair.certificate.cert)
|
||||
}
|
||||
|
||||
@After
|
||||
fun stopServer() {
|
||||
server.close()
|
||||
}
|
||||
|
||||
// TODO Ideally this test should be checking that two nodes that register are able to transact with each other. However
|
||||
// starting a second node hangs so that needs to be fixed.
|
||||
@Test
|
||||
fun `node registration correct root cert`() {
|
||||
internalDriver(
|
||||
portAllocation = portAllocation,
|
||||
notarySpecs = emptyList(),
|
||||
compatibilityZone = compatibilityZone
|
||||
) {
|
||||
startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow()
|
||||
assertThat(registrationHandler.idsPolled).contains("Alice")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair {
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(
|
||||
CordaX500Name(
|
||||
commonName = "Integration Test Corda Node Root CA",
|
||||
organisation = "R3 Ltd",
|
||||
locality = "London",
|
||||
country = "GB"),
|
||||
rootCAKey)
|
||||
return CertificateAndKeyPair(rootCACert, rootCAKey)
|
||||
}
|
||||
}
|
||||
|
||||
@Path("certificate")
|
||||
class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) {
|
||||
private val certPaths = HashMap<String, CertPath>()
|
||||
val idsPolled = HashSet<String>()
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
fun registration(input: InputStream): Response {
|
||||
val certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) }
|
||||
val (certPath, name) = createSignedClientCertificate(
|
||||
certificationRequest,
|
||||
rootCertAndKeyPair.keyPair,
|
||||
arrayOf(rootCertAndKeyPair.certificate.cert))
|
||||
certPaths[name.organisation] = certPath
|
||||
return Response.ok(name.organisation).build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
fun reply(@PathParam("id") id: String): Response {
|
||||
idsPolled += id
|
||||
return buildResponse(certPaths[id]!!.certificates)
|
||||
}
|
||||
|
||||
private fun buildResponse(certificates: List<Certificate>): Response {
|
||||
val baos = ByteArrayOutputStream()
|
||||
ZipOutputStream(baos).use { zip ->
|
||||
listOf(CORDA_CLIENT_CA, CORDA_INTERMEDIATE_CA, CORDA_ROOT_CA).zip(certificates).forEach {
|
||||
zip.putNextEntry(ZipEntry("${it.first}.cer"))
|
||||
zip.write(it.second.encoded)
|
||||
zip.closeEntry()
|
||||
}
|
||||
}
|
||||
return Response.ok(baos.toByteArray())
|
||||
.type("application/zip")
|
||||
.header("Content-Disposition", "attachment; filename=\"certificates.zip\"").build()
|
||||
}
|
||||
|
||||
private fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest,
|
||||
caKeyPair: KeyPair,
|
||||
caCertPath: Array<Certificate>): Pair<CertPath, CordaX500Name> {
|
||||
val request = JcaPKCS10CertificationRequest(certificationRequest)
|
||||
val name = CordaX500Name.parse(request.subject.toString())
|
||||
val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.NODE_CA,
|
||||
caCertPath.first().toX509CertHolder(),
|
||||
caKeyPair,
|
||||
name,
|
||||
request.publicKey,
|
||||
nameConstraints = null)
|
||||
val certPath = X509CertificateFactory().generateCertPath(x509CertificateHolder.cert, *caCertPath)
|
||||
return Pair(certPath, name)
|
||||
}
|
||||
}
|
@ -89,11 +89,11 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
val legalName = MEGA_CORP.name
|
||||
certificatesDirectory.createDirectories()
|
||||
if (!trustStoreFile.exists()) {
|
||||
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
||||
javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
||||
}
|
||||
|
||||
val caKeyStore = loadKeyStore(
|
||||
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"),
|
||||
javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"),
|
||||
"cordacadevpass")
|
||||
|
||||
val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA).toX509CertHolder()
|
||||
|
@ -22,10 +22,7 @@ import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.messaging.*
|
||||
import net.corda.core.node.*
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.debug
|
||||
@ -46,7 +43,6 @@ import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
||||
import net.corda.node.services.events.NodeSchedulerService
|
||||
import net.corda.node.services.events.ScheduledActivityObserver
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.node.services.identity.PersistentIdentityService
|
||||
import net.corda.node.services.keys.PersistentKeyManagementService
|
||||
import net.corda.node.services.messaging.MessagingService
|
||||
@ -61,6 +57,7 @@ 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.NetworkParameters
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
@ -94,6 +91,8 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
|
||||
* Marked as SingletonSerializeAsToken to prevent the invisible reference to AbstractNode in the ServiceHub accidentally
|
||||
* sweeping up the Node into the Kryo checkpoint serialization via any flows holding a reference to ServiceHub.
|
||||
*/
|
||||
// TODO Log warning if this node is a notary but not one of the ones specified in the network parameters, both for core and custom
|
||||
|
||||
// In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the
|
||||
// AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in.
|
||||
abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
@ -123,6 +122,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
// low-performance prototyping period.
|
||||
protected abstract val serverThread: AffinityExecutor
|
||||
|
||||
protected lateinit var networkParameters: NetworkParameters
|
||||
private val cordappServices = MutableClassToInstanceMap.create<SerializeAsToken>()
|
||||
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
|
||||
|
||||
@ -135,7 +135,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
protected lateinit var network: MessagingService
|
||||
protected val runOnStop = ArrayList<() -> Any?>()
|
||||
protected val _nodeReadyFuture = openFuture<Unit>()
|
||||
protected val networkMapClient: NetworkMapClient? by lazy { configuration.compatibilityZoneURL?.let(::NetworkMapClient) }
|
||||
protected val networkMapClient: NetworkMapClient? by lazy {
|
||||
configuration.compatibilityZoneURL?.let {
|
||||
NetworkMapClient(it, services.identityService.trustRoot)
|
||||
}
|
||||
}
|
||||
|
||||
lateinit var securityManager: RPCSecurityManager get
|
||||
|
||||
@ -177,7 +181,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||
initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database ->
|
||||
val persistentNetworkMapCache = PersistentNetworkMapCache(database)
|
||||
// TODO The fact that we need to specify an empty list of notaries just to generate our node info looks like
|
||||
// a code smell.
|
||||
val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList())
|
||||
val (keyPairs, info) = initNodeInfo(persistentNetworkMapCache, identity, identityKeyPair)
|
||||
val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey }
|
||||
val serialisedNodeInfo = info.serialize()
|
||||
@ -191,12 +197,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
check(started == null) { "Node has already been started" }
|
||||
log.info("Node starting up ...")
|
||||
initCertificate()
|
||||
readNetworkParameters()
|
||||
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
|
||||
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||
val identityService = makeIdentityService(identity.certificate)
|
||||
// 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), identityService)
|
||||
val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries), identityService)
|
||||
val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair)
|
||||
identityService.loadIdentities(info.legalIdentitiesAndCerts)
|
||||
val transactionStorage = makeTransactionStorage(database)
|
||||
@ -274,14 +281,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
val keyPairs = mutableSetOf(identityKeyPair)
|
||||
|
||||
myNotaryIdentity = configuration.notary?.let {
|
||||
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
|
||||
keyPairs += notaryIdentityKeyPair
|
||||
notaryIdentity
|
||||
if (it.isClusterConfig) {
|
||||
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
|
||||
keyPairs += notaryIdentityKeyPair
|
||||
notaryIdentity
|
||||
} else {
|
||||
// In case of a single notary service myNotaryIdentity will be the node's single identity.
|
||||
identity
|
||||
}
|
||||
}
|
||||
|
||||
var info = NodeInfo(
|
||||
myAddresses(),
|
||||
listOf(identity, myNotaryIdentity).filterNotNull(),
|
||||
setOf(identity, myNotaryIdentity).filterNotNull(),
|
||||
versionInfo.platformVersion,
|
||||
platformClock.instant().toEpochMilli()
|
||||
)
|
||||
@ -623,6 +635,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
return PersistentKeyManagementService(identityService, keyPairs)
|
||||
}
|
||||
|
||||
private fun readNetworkParameters() {
|
||||
val file = configuration.baseDirectory / "network-parameters"
|
||||
networkParameters = file.readAll().deserialize<SignedData<NetworkParameters>>().verified()
|
||||
log.info(networkParameters.toString())
|
||||
check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node is too old for the network" }
|
||||
}
|
||||
|
||||
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService {
|
||||
val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
|
||||
return notaryConfig.run {
|
||||
@ -678,22 +697,16 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
|
||||
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
|
||||
val (id, singleName) = if (notaryConfig == null) {
|
||||
// Node's main identity
|
||||
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
|
||||
// Node's main identity or if it's a single node notary
|
||||
Pair("identity", myLegalName)
|
||||
} else {
|
||||
val notaryId = notaryConfig.run {
|
||||
NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom)
|
||||
}
|
||||
if (!notaryConfig.isClusterConfig) {
|
||||
// Node's notary identity
|
||||
Pair(notaryId, myLegalName.copy(commonName = notaryId))
|
||||
} else {
|
||||
// The node is part of a distributed notary whose identity must already be generated beforehand
|
||||
Pair(notaryId, null)
|
||||
}
|
||||
// The node is part of a distributed notary whose identity must already be generated beforehand.
|
||||
Pair(notaryId, null)
|
||||
}
|
||||
|
||||
// TODO: Integrate with Key management service?
|
||||
val privateKeyAlias = "$id-private-key"
|
||||
|
||||
@ -731,7 +744,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
throw ConfigurationException("The name '$singleName' for $id doesn't match what's in the key store: $subject")
|
||||
}
|
||||
|
||||
val certPath = X509CertificateFactory().delegate.generateCertPath(certificates)
|
||||
val certPath = X509CertificateFactory().generateCertPath(certificates)
|
||||
return Pair(PartyAndCertificate(certPath), keyPair)
|
||||
}
|
||||
|
||||
|
@ -190,11 +190,17 @@ open class Node(configuration: NodeConfiguration,
|
||||
return if (!AddressUtils.isPublic(host)) {
|
||||
val foundPublicIP = AddressUtils.tryDetectPublicIP()
|
||||
if (foundPublicIP == null) {
|
||||
val retrievedHostName = networkMapClient?.myPublicHostname()
|
||||
if (retrievedHostName != null) {
|
||||
log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.")
|
||||
try {
|
||||
val retrievedHostName = networkMapClient?.myPublicHostname()
|
||||
if (retrievedHostName != null) {
|
||||
log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.")
|
||||
}
|
||||
retrievedHostName
|
||||
} catch (ignore: Throwable) {
|
||||
// Cannot reach the network map service, ignore the exception and use provided P2P address instead.
|
||||
log.warn("Cannot connect to the network map service for public IP detection.")
|
||||
null
|
||||
}
|
||||
retrievedHostName
|
||||
} else {
|
||||
log.info("Detected public IP: ${foundPublicIP.hostAddress}. This will be used instead of the provided \"$host\" as the advertised address.")
|
||||
foundPublicIP.hostAddress
|
||||
|
@ -50,10 +50,10 @@ fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrust
|
||||
fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
|
||||
certificatesDirectory.createDirectories()
|
||||
if (!trustStoreFile.exists()) {
|
||||
loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
|
||||
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
|
||||
}
|
||||
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName)
|
||||
|
||||
// Move distributed service composite key (generated by ServiceIdentityGenerator.generateToDisk) to keystore if exists.
|
||||
|
@ -125,7 +125,6 @@ data class NodeConfigurationImpl(
|
||||
// This is a sanity feature do not remove.
|
||||
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
|
||||
require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" }
|
||||
require(myLegalName.commonName == null) { "Common name must be null: $myLegalName" }
|
||||
require(security == null || rpcUsers.isEmpty()) {
|
||||
"Cannot specify both 'rpcUsers' and 'security' in configuration"
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate>,
|
||||
if (firstCertWithThisName != identity.certificate) {
|
||||
val certificates = identity.certPath.certificates
|
||||
val idx = certificates.lastIndexOf(firstCertWithThisName)
|
||||
val firstPath = X509CertificateFactory().delegate.generateCertPath(certificates.slice(idx until certificates.size))
|
||||
val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size))
|
||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
||||
if (firstCertWithThisName != identity.certificate) {
|
||||
val certificates = identity.certPath.certificates
|
||||
val idx = certificates.lastIndexOf(firstCertWithThisName)
|
||||
val firstPath = X509CertificateFactory().delegate.generateCertPath(certificates.slice(idx until certificates.size))
|
||||
val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size))
|
||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ fun freshCertificate(identityService: IdentityServiceInternal,
|
||||
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert)
|
||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, issuerCert.subject,
|
||||
issuerSigner, issuer.name, subjectPublicKey, window)
|
||||
val ourCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
|
||||
val ourCertPath = X509CertificateFactory().generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
|
||||
val anonymisedIdentity = PartyAndCertificate(ourCertPath)
|
||||
identityService.justVerifyAndRegisterIdentity(anonymisedIdentity)
|
||||
return anonymisedIdentity
|
||||
|
@ -27,6 +27,7 @@ class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: Ne
|
||||
}
|
||||
|
||||
fun stop() = synchronized(this) {
|
||||
rpcServer?.close()
|
||||
artemis.stop()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignedData
|
||||
@ -13,6 +12,10 @@ 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.crypto.X509Utilities
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Headers
|
||||
import rx.Subscription
|
||||
@ -20,11 +23,12 @@ import java.io.BufferedReader
|
||||
import java.io.Closeable
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class NetworkMapClient(compatibilityZoneURL: URL) {
|
||||
class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509Certificate) {
|
||||
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
|
||||
|
||||
fun publish(signedNodeInfo: SignedData<NodeInfo>) {
|
||||
@ -42,14 +46,30 @@ class NetworkMapClient(compatibilityZoneURL: URL) {
|
||||
|
||||
fun getNetworkMap(): NetworkMapResponse {
|
||||
val conn = networkMapUrl.openHttpConnection()
|
||||
val response = conn.inputStream.bufferedReader().use(BufferedReader::readLine)
|
||||
val networkMap = ObjectMapper().readValue(response, List::class.java).map { SecureHash.parse(it.toString()) }
|
||||
val signedNetworkMap = conn.inputStream.use { it.readBytes() }.deserialize<SignedNetworkMap>()
|
||||
val networkMap = signedNetworkMap.verified()
|
||||
// Assume network map cert is issued by the root.
|
||||
X509Utilities.validateCertificateChain(trustedRoot, signedNetworkMap.sig.by, trustedRoot)
|
||||
val timeout = CacheControl.parse(Headers.of(conn.headerFields.filterKeys { it != null }.mapValues { it.value.first() })).maxAgeSeconds().seconds
|
||||
return NetworkMapResponse(networkMap, timeout)
|
||||
}
|
||||
|
||||
fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? {
|
||||
val conn = URL("$networkMapUrl/$nodeInfoHash").openHttpConnection()
|
||||
val conn = URL("$networkMapUrl/node-info/$nodeInfoHash").openHttpConnection()
|
||||
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
null
|
||||
} else {
|
||||
val signedNodeInfo = conn.inputStream.use { it.readBytes() }.deserialize<SignedData<NodeInfo>>()
|
||||
val nodeInfo = signedNodeInfo.verified()
|
||||
// Verify node info is signed by node identity
|
||||
// TODO : Validate multiple signatures when NodeInfo supports multiple identities.
|
||||
require(nodeInfo.legalIdentities.any { it.owningKey == signedNodeInfo.sig.by }) { "NodeInfo must be signed by the node owning key." }
|
||||
nodeInfo
|
||||
}
|
||||
}
|
||||
|
||||
fun getNetworkParameter(networkParameterHash: SecureHash): NetworkParameters? {
|
||||
val conn = URL("$networkMapUrl/network-parameter/$networkParameterHash").openHttpConnection()
|
||||
return if (conn.responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
null
|
||||
} else {
|
||||
@ -63,7 +83,7 @@ class NetworkMapClient(compatibilityZoneURL: URL) {
|
||||
}
|
||||
}
|
||||
|
||||
data class NetworkMapResponse(val networkMap: List<SecureHash>, val cacheMaxAge: Duration)
|
||||
data class NetworkMapResponse(val networkMap: NetworkMap, val cacheMaxAge: Duration)
|
||||
|
||||
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
private val fileWatcher: NodeInfoWatcher,
|
||||
@ -108,21 +128,28 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
val nextScheduleDelay = try {
|
||||
val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap()
|
||||
val currentNodeHashes = networkMapCache.allNodeHashes
|
||||
(networkMap - currentNodeHashes).mapNotNull {
|
||||
val hashesFromNetworkMap = networkMap.nodeInfoHashes
|
||||
(hashesFromNetworkMap - currentNodeHashes).mapNotNull {
|
||||
// Download new node info from network map
|
||||
networkMapClient.getNodeInfo(it)
|
||||
try {
|
||||
networkMapClient.getNodeInfo(it)
|
||||
} catch (t: Throwable) {
|
||||
// Failure to retrieve one node info shouldn't stop the whole update, log and return null instead.
|
||||
logger.warn("Error encountered when downloading node info '$it', skipping...", t)
|
||||
null
|
||||
}
|
||||
}.forEach {
|
||||
// Add new node info to the network map cache, these could be new node info or modification of node info for existing nodes.
|
||||
networkMapCache.addNode(it)
|
||||
}
|
||||
// Remove node info from network map.
|
||||
(currentNodeHashes - networkMap - fileWatcher.processedNodeInfoHashes)
|
||||
(currentNodeHashes - hashesFromNetworkMap - fileWatcher.processedNodeInfoHashes)
|
||||
.mapNotNull(networkMapCache::getNodeByHash)
|
||||
.forEach(networkMapCache::removeNode)
|
||||
|
||||
// TODO: Check NetworkParameter.
|
||||
cacheTimeout
|
||||
} catch (t: Throwable) {
|
||||
logger.warn("Error encountered while updating network map, will retry in $retryInterval", t)
|
||||
logger.warn("Error encountered while updating network map, will retry in ${retryInterval.seconds} seconds", t)
|
||||
retryInterval
|
||||
}
|
||||
// Schedule the next update.
|
||||
@ -138,7 +165,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
try {
|
||||
networkMapClient.publish(signedNodeInfo)
|
||||
} catch (t: Throwable) {
|
||||
logger.warn("Error encountered while publishing node info, will retry in $retryInterval.", t)
|
||||
logger.warn("Error encountered while publishing node info, will retry in ${retryInterval.seconds} seconds.", t)
|
||||
// TODO: Exponential backoff?
|
||||
executor.schedule(this, retryInterval.toMillis(), TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
@ -9,7 +9,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.NodeInfoFilesCopier
|
||||
import net.corda.nodeapi.internal.NodeInfoFilesCopier
|
||||
import rx.Observable
|
||||
import rx.Scheduler
|
||||
import java.io.IOException
|
||||
|
@ -14,7 +14,6 @@ import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||
import net.corda.core.node.services.NotaryService
|
||||
import net.corda.core.node.services.PartyInfo
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.serialize
|
||||
@ -26,6 +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 org.hibernate.Session
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
@ -33,6 +33,7 @@ import java.security.PublicKey
|
||||
import java.util.*
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
class NetworkMapCacheImpl(
|
||||
networkMapCacheBase: NetworkMapCacheBaseInternal,
|
||||
@ -71,13 +72,15 @@ class NetworkMapCacheImpl(
|
||||
* Extremely simple in-memory cache of the network map.
|
||||
*/
|
||||
@ThreadSafe
|
||||
open class PersistentNetworkMapCache(private val database: CordaPersistence) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
|
||||
open class PersistentNetworkMapCache(
|
||||
private val database: CordaPersistence,
|
||||
notaries: List<NotaryInfo>
|
||||
) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
|
||||
// TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in
|
||||
// next PR that gets rid of services. These maps are used only for queries by service.
|
||||
// TODO Cleanup registered and party nodes
|
||||
protected val registeredNodes: MutableMap<PublicKey, NodeInfo> = Collections.synchronizedMap(HashMap())
|
||||
protected val partyNodes: MutableList<NodeInfo> get() = registeredNodes.map { it.value }.toMutableList()
|
||||
private val _changed = PublishSubject.create<MapChange>()
|
||||
@ -91,22 +94,9 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S
|
||||
override val nodeReady: CordaFuture<Void?> get() = _registrationFuture
|
||||
private var _loadDBSuccess: Boolean = false
|
||||
override val loadDBSuccess get() = _loadDBSuccess
|
||||
// TODO From the NetworkMapService redesign doc: Remove the concept of network services.
|
||||
// As a temporary hack, just assume for now that every network has a notary service named "Notary Service" that can be looked up in the map.
|
||||
// This should eliminate the only required usage of services.
|
||||
// It is ensured on node startup when constructing a notary that the name contains "notary".
|
||||
override val notaryIdentities: List<Party>
|
||||
get() {
|
||||
return partyNodes
|
||||
.flatMap {
|
||||
// TODO: validate notary identity certificates before loading into network map cache.
|
||||
// Notary certificates have to be signed by the doorman directly
|
||||
it.legalIdentities
|
||||
}
|
||||
.filter { it.name.commonName?.startsWith(NotaryService.ID_PREFIX) ?: false }
|
||||
.toSet() // Distinct, because of distributed service nodes
|
||||
.sortedBy { it.name.toString() }
|
||||
}
|
||||
|
||||
override val notaryIdentities: List<Party> = notaries.map { it.identity }
|
||||
private val validatingNotaries = notaries.mapNotNullTo(HashSet()) { if (it.validating) it.identity else null }
|
||||
|
||||
init {
|
||||
database.transaction { loadFromDB(session) }
|
||||
@ -121,7 +111,7 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S
|
||||
select(get<String>(NodeInfoSchemaV1.PersistentNodeInfo::hash.name))
|
||||
}
|
||||
}
|
||||
session.createQuery(query).resultList.map { SecureHash.sha256(it) }
|
||||
session.createQuery(query).resultList.map { SecureHash.parse(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +127,7 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S
|
||||
}
|
||||
}
|
||||
|
||||
override fun isValidatingNotary(party: Party): Boolean = isNotary(party) && "validating" in party.name.commonName!!
|
||||
override fun isValidatingNotary(party: Party): Boolean = party in validatingNotaries
|
||||
|
||||
override fun getPartyInfo(party: Party): PartyInfo? {
|
||||
val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) }
|
||||
@ -309,7 +299,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence) : S
|
||||
id = 0,
|
||||
hash = nodeInfo.serialize().hash.toString(),
|
||||
addresses = nodeInfo.addresses.map { NodeInfoSchemaV1.DBHostAndPort.fromHostAndPort(it) },
|
||||
// TODO Another ugly hack with special first identity...
|
||||
legalIdentitiesAndCerts = nodeInfo.legalIdentitiesAndCerts.mapIndexed { idx, elem ->
|
||||
NodeInfoSchemaV1.DBPartyAndCertificate(elem, isMain = idx == 0)
|
||||
},
|
||||
|
@ -12,6 +12,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
import org.bouncycastle.util.io.pem.PemObject
|
||||
import java.io.StringWriter
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.Certificate
|
||||
@ -26,10 +27,18 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key"
|
||||
}
|
||||
|
||||
init {
|
||||
require(config.rootCertFile.exists()) {
|
||||
"${config.rootCertFile} does not exist. This file must contain the root CA cert of your compatibility zone. " +
|
||||
"Please contact your CZ operator."
|
||||
}
|
||||
}
|
||||
|
||||
private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
|
||||
private val keystorePassword = config.keyStorePassword
|
||||
// TODO: Use different password for private key.
|
||||
private val privateKeyPassword = config.keyStorePassword
|
||||
private val rootCert = X509Utilities.loadCertificateFromPEMFile(config.rootCertFile)
|
||||
|
||||
/**
|
||||
* Ensure the initial keystore for a node is set up.
|
||||
@ -74,10 +83,15 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
|
||||
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
|
||||
caKeyStore.save(config.nodeKeystore, keystorePassword)
|
||||
|
||||
// Check the root certificate.
|
||||
val returnedRootCa = certificates.last()
|
||||
checkReturnedRootCaMatchesExpectedCa(returnedRootCa)
|
||||
|
||||
// Save root certificates to trust store.
|
||||
val trustStore = loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
// Assumes certificate chain always starts with client certificate and end with root certificate.
|
||||
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last())
|
||||
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, returnedRootCa)
|
||||
trustStore.save(config.trustStoreFile, config.trustStorePassword)
|
||||
println("Node private key and certificate stored in ${config.nodeKeystore}.")
|
||||
|
||||
@ -97,6 +111,16 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the passed Certificate is the expected root CA.
|
||||
* @throws WrongRootCertException if the certificates don't match.
|
||||
*/
|
||||
private fun checkReturnedRootCaMatchesExpectedCa(returnedRootCa: Certificate) {
|
||||
if (rootCert != returnedRootCa) {
|
||||
throw WrongRootCertException(rootCert, returnedRootCa, config.rootCertFile)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll Certificate Signing Server for approved certificate,
|
||||
* enter a slow polling loop if server return null.
|
||||
@ -150,3 +174,17 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when the doorman root certificate doesn't match the expected (out-of-band) root certificate.
|
||||
* This usually means the has been a Man-in-the-middle attack when contacting the doorman.
|
||||
*/
|
||||
class WrongRootCertException(expected: Certificate,
|
||||
actual: Certificate,
|
||||
expectedFilePath: Path):
|
||||
Exception("""
|
||||
The Root CA returned back from the registration process does not match the expected Root CA
|
||||
expected: $expected
|
||||
actual: $actual
|
||||
the expected certificate is stored in: $expectedFilePath
|
||||
""".trimMargin())
|
||||
|
@ -28,10 +28,6 @@ import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class NotaryChangeTests {
|
||||
companion object {
|
||||
private val DUMMY_NOTARY_SERVICE_NAME: CordaX500Name = DUMMY_NOTARY.name.copy(commonName = "corda.notary.validating")
|
||||
}
|
||||
|
||||
private lateinit var mockNet: MockNetwork
|
||||
private lateinit var oldNotaryNode: StartedNode<MockNetwork.MockNode>
|
||||
private lateinit var clientNodeA: StartedNode<MockNetwork.MockNode>
|
||||
@ -42,7 +38,7 @@ class NotaryChangeTests {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val oldNotaryName = DUMMY_NOTARY.name.copy(organisation = "Old Dummy Notary")
|
||||
val oldNotaryName = DUMMY_REGULATOR.name
|
||||
mockNet = MockNetwork(
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name), NotarySpec(oldNotaryName)),
|
||||
cordappPackages = listOf("net.corda.testing.contracts")
|
||||
@ -51,8 +47,8 @@ class NotaryChangeTests {
|
||||
clientNodeB = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
|
||||
clientA = clientNodeA.info.singleIdentity()
|
||||
oldNotaryNode = mockNet.notaryNodes[1]
|
||||
newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!!
|
||||
oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Old Dummy Notary"))!!
|
||||
newNotaryParty = clientNodeA.services.networkMapCache.getNotary(DUMMY_NOTARY.name)!!
|
||||
oldNotaryParty = clientNodeA.services.networkMapCache.getNotary(oldNotaryName)!!
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -161,7 +161,7 @@ class InMemoryIdentityServiceTests {
|
||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
||||
val txKey = Crypto.generateKeyPair()
|
||||
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
||||
val txCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||
}
|
||||
|
||||
|
@ -256,7 +256,7 @@ class PersistentIdentityServiceTests {
|
||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
||||
val txKey = Crypto.generateKeyPair()
|
||||
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
||||
val txCertPath = X509CertificateFactory().delegate.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ class ArtemisMessagingTests {
|
||||
myLegalName = ALICE.name)
|
||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock())
|
||||
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database), rigorousMock())
|
||||
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()), rigorousMock())
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -1,86 +1,47 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.testing.DEV_CA
|
||||
import net.corda.testing.DEV_TRUST_ROOT
|
||||
import net.corda.testing.ROOT_CA
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.node.network.NetworkMapServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.server.ServerConnector
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.servlet.ServletHolder
|
||||
import org.glassfish.jersey.server.ResourceConfig
|
||||
import org.glassfish.jersey.servlet.ServletContainer
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.URL
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.ws.rs.*
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
import javax.ws.rs.core.Response.ok
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class NetworkMapClientTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule(true)
|
||||
private lateinit var server: Server
|
||||
|
||||
private lateinit var server: NetworkMapServer
|
||||
private lateinit var networkMapClient: NetworkMapClient
|
||||
private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", organisation = "R3 LTD", locality = "London", country = "GB"), rootCAKey)
|
||||
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
|
||||
|
||||
companion object {
|
||||
private val cacheTimeout = 100000.seconds
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
server = Server(InetSocketAddress("localhost", 0)).apply {
|
||||
handler = HandlerCollection().apply {
|
||||
addHandler(ServletContextHandler().apply {
|
||||
contextPath = "/"
|
||||
val resourceConfig = ResourceConfig().apply {
|
||||
// Add your API provider classes (annotated for JAX-RS) here
|
||||
register(MockNetworkMapServer())
|
||||
}
|
||||
val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start
|
||||
addServlet(jerseyServlet, "/*")
|
||||
})
|
||||
}
|
||||
}
|
||||
server.start()
|
||||
|
||||
while (!server.isStarted) {
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
val hostAndPort = server.connectors.mapNotNull { it as? ServerConnector }.first()
|
||||
networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.localPort}"))
|
||||
server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort())
|
||||
val hostAndPort = server.start()
|
||||
networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"), DEV_TRUST_ROOT.cert)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
server.stop()
|
||||
server.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -93,7 +54,7 @@ class NetworkMapClientTest {
|
||||
|
||||
val nodeInfoHash = nodeInfo.serialize().sha256()
|
||||
|
||||
assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash)
|
||||
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash)
|
||||
assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash))
|
||||
|
||||
val signedNodeInfo2 = createNodeInfo("Test2")
|
||||
@ -101,53 +62,22 @@ class NetworkMapClientTest {
|
||||
networkMapClient.publish(signedNodeInfo2)
|
||||
|
||||
val nodeInfoHash2 = nodeInfo2.serialize().sha256()
|
||||
assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash, nodeInfoHash2)
|
||||
assertEquals(100000.seconds, networkMapClient.getNetworkMap().cacheMaxAge)
|
||||
assertThat(networkMapClient.getNetworkMap().networkMap.nodeInfoHashes).containsExactly(nodeInfoHash, nodeInfoHash2)
|
||||
assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge)
|
||||
assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `download NetworkParameter correctly`() {
|
||||
// The test server returns same network parameter for any hash.
|
||||
val networkParameter = networkMapClient.getNetworkParameter(SecureHash.randomSHA256())
|
||||
assertNotNull(networkParameter)
|
||||
assertEquals(NetworkMapServer.stubNetworkParameter, networkParameter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get hostname string from http response correctly`() {
|
||||
assertEquals("test.host.name", networkMapClient.myPublicHostname())
|
||||
}
|
||||
}
|
||||
|
||||
@Path("network-map")
|
||||
// This is a stub implementation of the network map rest API.
|
||||
internal class MockNetworkMapServer {
|
||||
val nodeInfoMap = mutableMapOf<SecureHash, NodeInfo>()
|
||||
@POST
|
||||
@Path("publish")
|
||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun publishNodeInfo(input: InputStream): Response {
|
||||
val registrationData = input.readBytes().deserialize<SignedData<NodeInfo>>()
|
||||
val nodeInfo = registrationData.verified()
|
||||
val nodeInfoHash = nodeInfo.serialize().sha256()
|
||||
nodeInfoMap.put(nodeInfoHash, nodeInfo)
|
||||
return ok().build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
fun getNetworkMap(): Response {
|
||||
return Response.ok(ObjectMapper().writeValueAsString(nodeInfoMap.keys.map { it.toString() })).header("Cache-Control", "max-age=100000").build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{var}")
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response {
|
||||
val nodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)]
|
||||
return if (nodeInfo != null) {
|
||||
Response.ok(nodeInfo.serialize().bytes)
|
||||
} else {
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
}.build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("my-hostname")
|
||||
fun getHostName(): Response {
|
||||
return Response.ok("test.host.name").build()
|
||||
assertEquals("test.host.name", networkMapClient.myPublicHostname())
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.nodeapi.internal.NetworkMap
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
@ -95,7 +96,7 @@ class NetworkMapUpdaterTest {
|
||||
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
||||
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
|
||||
}
|
||||
on { getNetworkMap() }.then { NetworkMapResponse(nodeInfoMap.keys.toList(), 100.millis) }
|
||||
on { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), SecureHash.randomSHA256()), 100.millis) }
|
||||
on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
|
||||
}
|
||||
|
||||
@ -149,7 +150,7 @@ class NetworkMapUpdaterTest {
|
||||
val signedNodeInfo: SignedData<NodeInfo> = uncheckedCast(it.arguments.first())
|
||||
nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo)
|
||||
}
|
||||
on { getNetworkMap() }.then { NetworkMapResponse(nodeInfoMap.keys.toList(), 100.millis) }
|
||||
on { getNetworkMap() }.then { NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), SecureHash.randomSHA256()), 100.millis) }
|
||||
on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments.first()]?.verified() }
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ object TestNodeInfoFactory {
|
||||
}
|
||||
|
||||
private fun buildCertPath(vararg certificates: Certificate): CertPath {
|
||||
return X509CertificateFactory().delegate.generateCertPath(certificates.asList())
|
||||
return X509CertificateFactory().generateCertPath(*certificates)
|
||||
}
|
||||
|
||||
private fun X509CertificateHolder.toX509Certificate(): X509Certificate {
|
||||
|
@ -1,50 +1,56 @@
|
||||
package net.corda.node.utilities.registration
|
||||
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.eq
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.getX509Certificate
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.rigorousMock
|
||||
import net.corda.testing.testNodeConfiguration
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.security.cert.Certificate
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
val X500Name.commonName: String? get() = getRDNs(BCStyle.CN).firstOrNull()?.first?.value?.toString()
|
||||
|
||||
class NetworkRegistrationHelperTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun buildKeyStore() {
|
||||
val id = SecureHash.randomSHA256().toString()
|
||||
private val requestId = SecureHash.randomSHA256().toString()
|
||||
private lateinit var config: NodeConfiguration
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
config = testNodeConfiguration(baseDirectory = tempFolder.root.toPath(), myLegalName = ALICE.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `successful registration`() {
|
||||
val identities = listOf("CORDA_CLIENT_CA",
|
||||
"CORDA_INTERMEDIATE_CA",
|
||||
"CORDA_ROOT_CA")
|
||||
.map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") }
|
||||
val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
|
||||
.map { it.cert }.toTypedArray()
|
||||
val certService = rigorousMock<NetworkRegistrationService>().also {
|
||||
doReturn(id).whenever(it).submitRequest(any())
|
||||
doReturn(certs).whenever(it).retrieveCertificates(eq(id))
|
||||
}
|
||||
|
||||
val config = testNodeConfiguration(
|
||||
baseDirectory = tempFolder.root.toPath(),
|
||||
myLegalName = ALICE.name)
|
||||
val certService = mockRegistrationResponse(*certs)
|
||||
|
||||
config.rootCertFile.parent.createDirectories()
|
||||
X509Utilities.saveCertificateAsPEMFile(certs.last(), config.rootCertFile)
|
||||
|
||||
assertFalse(config.nodeKeystore.exists())
|
||||
assertFalse(config.sslKeystore.exists())
|
||||
@ -89,4 +95,44 @@ class NetworkRegistrationHelperTest {
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rootCertFile doesn't exist`() {
|
||||
val certService = rigorousMock<NetworkRegistrationService>()
|
||||
|
||||
assertThatThrownBy {
|
||||
NetworkRegistrationHelper(config, certService)
|
||||
}.hasMessageContaining(config.rootCertFile.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `root cert in response doesn't match expected`() {
|
||||
val identities = listOf("CORDA_CLIENT_CA",
|
||||
"CORDA_INTERMEDIATE_CA",
|
||||
"CORDA_ROOT_CA")
|
||||
.map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") }
|
||||
val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
|
||||
.map { it.cert }.toTypedArray()
|
||||
|
||||
val certService = mockRegistrationResponse(*certs)
|
||||
|
||||
config.rootCertFile.parent.createDirectories()
|
||||
X509Utilities.saveCertificateAsPEMFile(
|
||||
X509Utilities.createSelfSignedCACertificate(
|
||||
CordaX500Name("CORDA_ROOT_CA", "R3 Ltd", "London", "GB"),
|
||||
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert,
|
||||
config.rootCertFile
|
||||
)
|
||||
|
||||
assertThatThrownBy {
|
||||
NetworkRegistrationHelper(config, certService).buildKeystore()
|
||||
}.isInstanceOf(WrongRootCertException::class.java)
|
||||
}
|
||||
|
||||
private fun mockRegistrationResponse(vararg response: Certificate): NetworkRegistrationService {
|
||||
return rigorousMock<NetworkRegistrationService>().also {
|
||||
doReturn(requestId).whenever(it).submitRequest(any())
|
||||
doReturn(response).whenever(it).retrieveCertificates(eq(requestId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,9 @@ dependencies {
|
||||
|
||||
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': ["StartFlow.net.corda.attachmentdemo.AttachmentDemoFlow",
|
||||
"InvokeRpc.wellKnownPartyFromX500Name",
|
||||
"InvokeRpc.attachmentExists",
|
||||
"InvokeRpc.openAttachment",
|
||||
"InvokeRpc.uploadAttachment",
|
||||
"InvokeRpc.internalVerifiedTransactionsFeed"]]]
|
||||
|
||||
|
@ -12,7 +12,6 @@ import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.Permissions.Companion.all
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.testing.BOC
|
||||
import net.corda.testing.internal.demorun.*
|
||||
@ -102,7 +101,7 @@ object IssueCash {
|
||||
}
|
||||
|
||||
private fun createParams(amount: Amount<Currency>, notaryName: CordaX500Name): IssueRequestParams {
|
||||
return IssueRequestParams(amount, BIGCORP_NAME, "1", BOC.name, notaryName.copy(commonName = ValidatingNotaryService.id))
|
||||
return IssueRequestParams(amount, BIGCORP_NAME, "1", BOC.name, notaryName)
|
||||
}
|
||||
|
||||
private fun printHelp(parser: OptionParser) {
|
||||
|
@ -38,7 +38,7 @@ dependencies {
|
||||
// Specify your cordapp's dependencies below, including dependent cordapps
|
||||
compile group: 'commons-io', name: 'commons-io', version: '2.5'
|
||||
|
||||
testCompile project(':node-driver')
|
||||
cordaCompile project(':node-driver')
|
||||
testCompile "junit:junit:$junit_version"
|
||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ fun <A> springDriver(
|
||||
useTestClock: Boolean = defaultParameters.useTestClock,
|
||||
initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
|
||||
startNodesInProcess: Boolean = defaultParameters.startNodesInProcess,
|
||||
notarySpecs: List<NotarySpec>,
|
||||
notarySpecs: List<NotarySpec> = defaultParameters.notarySpecs,
|
||||
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
|
||||
dsl: SpringBootDriverDSL.() -> A
|
||||
): A {
|
||||
@ -91,7 +91,7 @@ data class SpringBootDriverDSL(private val driverDSL: DriverDSLImpl) : InternalD
|
||||
log.debug("Retrying webserver info at ${handle.webAddress}")
|
||||
}
|
||||
|
||||
throw IllegalStateException("Webserver at ${handle.webAddress} has died or was not reachable at URL ${url}")
|
||||
throw IllegalStateException("Webserver at ${handle.webAddress} has died or was not reachable at URL $url")
|
||||
}
|
||||
|
||||
private fun startApplication(handle: NodeHandle, debugPort: Int?, clazz: Class<*>): Process {
|
||||
|
@ -4,12 +4,13 @@ import net.corda.cordform.CordformContext
|
||||
import net.corda.cordform.CordformDefinition
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.node.services.NotaryService
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.config.BFTSMaRtConfiguration
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
|
||||
import net.corda.node.services.transactions.minCorrectReplicas
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.internal.ServiceIdentityGenerator
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import net.corda.testing.internal.demorun.*
|
||||
@ -63,6 +64,10 @@ class BFTNotaryCordform : CordformDefinition() {
|
||||
}
|
||||
|
||||
override fun setup(context: CordformContext) {
|
||||
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it.toString()) }, clusterName, threshold = minCorrectReplicas(clusterSize))
|
||||
ServiceIdentityGenerator.generateToDisk(
|
||||
notaryNames.map { context.baseDirectory(it.toString()) },
|
||||
clusterName,
|
||||
NotaryService.constructId(validating = false, bft = true),
|
||||
minCorrectReplicas(clusterSize))
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,12 @@ import net.corda.cordform.CordformDefinition
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.node.services.NotaryService
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.config.RaftConfig
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.internal.ServiceIdentityGenerator
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import net.corda.testing.internal.demorun.*
|
||||
@ -60,6 +61,9 @@ class RaftNotaryCordform : CordformDefinition() {
|
||||
}
|
||||
|
||||
override fun setup(context: CordformContext) {
|
||||
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it.toString()) }, clusterName)
|
||||
ServiceIdentityGenerator.generateToDisk(
|
||||
notaryNames.map { context.baseDirectory(it.toString()) },
|
||||
clusterName,
|
||||
NotaryService.constructId(validating = true, raft = true))
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,17 @@ dependencies {
|
||||
// Integration test helpers
|
||||
integrationTestCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
integrationTestCompile "junit:junit:$junit_version"
|
||||
|
||||
// Jetty dependencies for NetworkMapClient test.
|
||||
// Web stuff: for HTTP[S] servlets
|
||||
compile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
|
||||
compile "org.eclipse.jetty:jetty-webapp:${jetty_version}"
|
||||
compile "javax.servlet:javax.servlet-api:3.1.0"
|
||||
|
||||
// Jersey for JAX-RS implementation for use in Jetty
|
||||
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
|
||||
compile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}"
|
||||
compile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}"
|
||||
}
|
||||
|
||||
task integrationTest(type: Test) {
|
||||
|
@ -14,6 +14,7 @@ import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||
import net.corda.testing.http.HttpApi
|
||||
import net.corda.testing.internal.addressMustBeBound
|
||||
import net.corda.testing.internal.addressMustNotBeBound
|
||||
import net.corda.testing.internal.internalDriver
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.json.simple.JSONObject
|
||||
@ -92,7 +93,7 @@ class DriverTests {
|
||||
assertThat(baseDirectory / "process-id").exists()
|
||||
}
|
||||
|
||||
val baseDirectory = driver(notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name))) {
|
||||
val baseDirectory = internalDriver(notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name))) {
|
||||
baseDirectory(DUMMY_NOTARY.name)
|
||||
}
|
||||
assertThat(baseDirectory / "process-id").doesNotExist()
|
||||
|
@ -15,7 +15,6 @@ import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.VerifierType
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.internal.InProcessNode
|
||||
import net.corda.testing.internal.DriverDSLImpl
|
||||
import net.corda.testing.internal.genericDriver
|
||||
import net.corda.testing.internal.getTimestampAsDirectoryName
|
||||
@ -178,7 +177,7 @@ fun <A> driver(
|
||||
waitForAllNodesToFinish: Boolean = defaultParameters.waitForAllNodesToFinish,
|
||||
notarySpecs: List<NotarySpec> = defaultParameters.notarySpecs,
|
||||
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
|
||||
jmxPolicy: JmxPolicy = JmxPolicy(),
|
||||
jmxPolicy: JmxPolicy = defaultParameters.jmxPolicy,
|
||||
dsl: DriverDSL.() -> A
|
||||
): A {
|
||||
return genericDriver(
|
||||
@ -193,7 +192,8 @@ fun <A> driver(
|
||||
waitForNodesToFinish = waitForAllNodesToFinish,
|
||||
notarySpecs = notarySpecs,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
jmxPolicy = jmxPolicy
|
||||
jmxPolicy = jmxPolicy,
|
||||
compatibilityZone = null
|
||||
),
|
||||
coerce = { it },
|
||||
dsl = dsl,
|
||||
@ -230,7 +230,6 @@ data class DriverParameters(
|
||||
val notarySpecs: List<NotarySpec> = listOf(NotarySpec(DUMMY_NOTARY.name)),
|
||||
val extraCordappPackagesToScan: List<String> = emptyList(),
|
||||
val jmxPolicy: JmxPolicy = JmxPolicy()
|
||||
|
||||
) {
|
||||
fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug)
|
||||
fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory)
|
||||
@ -241,7 +240,7 @@ data class DriverParameters(
|
||||
fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization)
|
||||
fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess)
|
||||
fun setWaitForAllNodesToFinish(waitForAllNodesToFinish: Boolean) = copy(waitForAllNodesToFinish = waitForAllNodesToFinish)
|
||||
fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List<String>) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan)
|
||||
fun setNotarySpecs(notarySpecs: List<NotarySpec>) = copy(notarySpecs = notarySpecs)
|
||||
fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List<String>) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan)
|
||||
fun setJmxPolicy(jmxPolicy: JmxPolicy) = copy(jmxPolicy = jmxPolicy)
|
||||
}
|
||||
|
@ -64,7 +64,8 @@ interface DriverDSL {
|
||||
verifierType: VerifierType = defaultParameters.verifierType,
|
||||
customOverrides: Map<String, Any?> = defaultParameters.customOverrides,
|
||||
startInSameProcess: Boolean? = defaultParameters.startInSameProcess,
|
||||
maximumHeapSize: String = defaultParameters.maximumHeapSize): CordaFuture<NodeHandle>
|
||||
maximumHeapSize: String = defaultParameters.maximumHeapSize
|
||||
): CordaFuture<NodeHandle>
|
||||
|
||||
/**
|
||||
* Helper function for starting a [Node] with custom parameters from Java.
|
||||
|
@ -1,7 +1,9 @@
|
||||
package net.corda.testing.internal
|
||||
|
||||
import com.google.common.collect.HashMultimap
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.cordform.CordformContext
|
||||
@ -9,7 +11,6 @@ import net.corda.cordform.CordformNode
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.concurrent.firstOf
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.ThreadBox
|
||||
import net.corda.core.internal.concurrent.*
|
||||
import net.corda.core.internal.copyTo
|
||||
@ -28,15 +29,23 @@ import net.corda.node.internal.NodeStartup
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.node.services.Permissions
|
||||
import net.corda.node.services.config.*
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.NodeInfoFilesCopier
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
|
||||
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.config.User
|
||||
import net.corda.nodeapi.internal.config.parseAs
|
||||
import net.corda.nodeapi.internal.config.toConfig
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import net.corda.testing.DUMMY_BANK_A
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.driver.*
|
||||
import net.corda.testing.internal.DriverDSLImpl.ClusterType.NON_VALIDATING_RAFT
|
||||
import net.corda.testing.internal.DriverDSLImpl.ClusterType.VALIDATING_RAFT
|
||||
import net.corda.testing.node.ClusterSpec
|
||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import net.corda.testing.node.NotarySpec
|
||||
@ -45,12 +54,14 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import rx.Observable
|
||||
import rx.observables.ConnectableObservable
|
||||
import rx.schedulers.Schedulers
|
||||
import java.net.ConnectException
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
@ -73,7 +84,8 @@ class DriverDSLImpl(
|
||||
val waitForNodesToFinish: Boolean,
|
||||
extraCordappPackagesToScan: List<String>,
|
||||
val jmxPolicy: JmxPolicy,
|
||||
val notarySpecs: List<NotarySpec>
|
||||
val notarySpecs: List<NotarySpec>,
|
||||
val compatibilityZone: CompatibilityZoneParams?
|
||||
) : InternalDriverDSL {
|
||||
private var _executorService: ScheduledExecutorService? = null
|
||||
val executorService get() = _executorService!!
|
||||
@ -83,11 +95,12 @@ class DriverDSLImpl(
|
||||
// TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/
|
||||
// This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system.
|
||||
// Investigate whether we can avoid that.
|
||||
private val nodeInfoFilesCopier = NodeInfoFilesCopier()
|
||||
private var nodeInfoFilesCopier: NodeInfoFilesCopier? = null
|
||||
// Map from a nodes legal name to an observable emitting the number of nodes in its network map.
|
||||
private val countObservables = mutableMapOf<CordaX500Name, Observable<Int>>()
|
||||
private lateinit var _notaries: List<NotaryHandle>
|
||||
override val notaryHandles: List<NotaryHandle> get() = _notaries
|
||||
private var networkParameters: NetworkParametersCopier? = null
|
||||
|
||||
class State {
|
||||
val processes = ArrayList<Process>()
|
||||
@ -160,28 +173,127 @@ class DriverDSLImpl(
|
||||
maximumHeapSize: String
|
||||
): CordaFuture<NodeHandle> {
|
||||
val p2pAddress = portAllocation.nextHostAndPort()
|
||||
val rpcAddress = portAllocation.nextHostAndPort()
|
||||
val webAddress = portAllocation.nextHostAndPort()
|
||||
// TODO: Derive name from the full picked name, don't just wrap the common name
|
||||
val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB")
|
||||
val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) }
|
||||
val config = ConfigHelper.loadConfig(
|
||||
baseDirectory = baseDirectory(name),
|
||||
allowMissingConfig = true,
|
||||
configOverrides = configOf(
|
||||
"myLegalName" to name.toString(),
|
||||
"p2pAddress" to p2pAddress.toString(),
|
||||
"rpcAddress" to rpcAddress.toString(),
|
||||
"webAddress" to webAddress.toString(),
|
||||
"useTestClock" to useTestClock,
|
||||
"rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() },
|
||||
"verifierType" to verifierType.name
|
||||
) + customOverrides
|
||||
)
|
||||
return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize)
|
||||
|
||||
val registrationFuture = if (compatibilityZone?.rootCert != null) {
|
||||
nodeRegistration(name, compatibilityZone.rootCert, compatibilityZone.url)
|
||||
} else {
|
||||
doneFuture(Unit)
|
||||
}
|
||||
|
||||
return registrationFuture.flatMap {
|
||||
val rpcAddress = portAllocation.nextHostAndPort()
|
||||
val webAddress = portAllocation.nextHostAndPort()
|
||||
val users = rpcUsers.map { it.copy(permissions = it.permissions + DRIVER_REQUIRED_PERMISSIONS) }
|
||||
val configMap = configOf(
|
||||
"myLegalName" to name.toString(),
|
||||
"p2pAddress" to p2pAddress.toString(),
|
||||
"rpcAddress" to rpcAddress.toString(),
|
||||
"webAddress" to webAddress.toString(),
|
||||
"useTestClock" to useTestClock,
|
||||
"rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() },
|
||||
"verifierType" to verifierType.name
|
||||
) + customOverrides
|
||||
val config = ConfigHelper.loadConfig(
|
||||
baseDirectory = baseDirectory(name),
|
||||
allowMissingConfig = true,
|
||||
configOverrides = if (compatibilityZone != null) {
|
||||
configMap + mapOf("compatibilityZoneURL" to compatibilityZone.url.toString())
|
||||
} else {
|
||||
configMap
|
||||
}
|
||||
)
|
||||
startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun startCordformNode(cordform: CordformNode): CordaFuture<NodeHandle> {
|
||||
private fun nodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture<Unit> {
|
||||
val baseDirectory = baseDirectory(providedName).createDirectories()
|
||||
val config = ConfigHelper.loadConfig(
|
||||
baseDirectory = baseDirectory,
|
||||
allowMissingConfig = true,
|
||||
configOverrides = configOf(
|
||||
"p2pAddress" to "localhost:1222", // required argument, not really used
|
||||
"compatibilityZoneURL" to compatibilityZoneURL.toString(),
|
||||
"myLegalName" to providedName.toString())
|
||||
)
|
||||
val configuration = config.parseAsNodeConfiguration()
|
||||
|
||||
configuration.rootCertFile.parent.createDirectories()
|
||||
X509Utilities.saveCertificateAsPEMFile(rootCert, configuration.rootCertFile)
|
||||
|
||||
return if (startNodesInProcess) {
|
||||
// This is a bit cheating, we're not starting a full node, we're just calling the code nodes call
|
||||
// when registering.
|
||||
NetworkRegistrationHelper(configuration, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore()
|
||||
doneFuture(Unit)
|
||||
} else {
|
||||
startOutOfProcessNodeRegistration(config, configuration)
|
||||
}
|
||||
}
|
||||
|
||||
private enum class ClusterType(val validating: Boolean, val clusterName: CordaX500Name) {
|
||||
VALIDATING_RAFT(true, CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH")),
|
||||
NON_VALIDATING_RAFT(false, CordaX500Name(RaftNonValidatingNotaryService.id, "Raft", "Zurich", "CH")),
|
||||
NON_VALIDATING_BFT(false, CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH"))
|
||||
}
|
||||
|
||||
internal fun startCordformNodes(cordforms: List<CordformNode>): CordaFuture<*> {
|
||||
val clusterNodes = HashMultimap.create<ClusterType, CordaX500Name>()
|
||||
val notaryInfos = ArrayList<NotaryInfo>()
|
||||
|
||||
// Go though the node definitions and pick out the notaries so that we can generate their identities to be used
|
||||
// in the network parameters
|
||||
for (cordform in cordforms) {
|
||||
if (cordform.notary == null) continue
|
||||
val name = CordaX500Name.parse(cordform.name)
|
||||
val notaryConfig = ConfigFactory.parseMap(cordform.notary).parseAs<NotaryConfig>()
|
||||
// We need to first group the nodes that form part of a cluser. We assume for simplicity that nodes of the
|
||||
// same cluster type and validating flag are part of the same cluster.
|
||||
if (notaryConfig.raft != null) {
|
||||
val key = if (notaryConfig.validating) VALIDATING_RAFT else NON_VALIDATING_RAFT
|
||||
clusterNodes.put(key, name)
|
||||
} else if (notaryConfig.bftSMaRt != null) {
|
||||
clusterNodes.put(ClusterType.NON_VALIDATING_BFT, name)
|
||||
} else {
|
||||
// We have all we need here to generate the identity for single node notaries
|
||||
val identity = ServiceIdentityGenerator.generateToDisk(
|
||||
dirs = listOf(baseDirectory(name)),
|
||||
serviceName = name,
|
||||
serviceId = "identity"
|
||||
)
|
||||
notaryInfos += NotaryInfo(identity, notaryConfig.validating)
|
||||
}
|
||||
}
|
||||
|
||||
clusterNodes.asMap().forEach { type, nodeNames ->
|
||||
val identity = ServiceIdentityGenerator.generateToDisk(
|
||||
dirs = nodeNames.map { baseDirectory(it) },
|
||||
serviceName = type.clusterName,
|
||||
serviceId = NotaryService.constructId(
|
||||
validating = type.validating,
|
||||
raft = type in setOf(VALIDATING_RAFT, NON_VALIDATING_RAFT),
|
||||
bft = type == ClusterType.NON_VALIDATING_BFT
|
||||
)
|
||||
)
|
||||
notaryInfos += NotaryInfo(identity, type.validating)
|
||||
}
|
||||
|
||||
networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos))
|
||||
|
||||
return cordforms.map {
|
||||
val startedNode = startCordformNode(it)
|
||||
if (it.webAddress != null) {
|
||||
// Start a webserver if an address for it was specified
|
||||
startedNode.flatMap { startWebserver(it) }
|
||||
} else {
|
||||
startedNode
|
||||
}
|
||||
}.transpose()
|
||||
}
|
||||
|
||||
private fun startCordformNode(cordform: CordformNode): CordaFuture<NodeHandle> {
|
||||
val name = CordaX500Name.parse(cordform.name)
|
||||
// TODO We shouldn't have to allocate an RPC or web address if they're not specified. We're having to do this because of startNodeInternal
|
||||
val rpcAddress = if (cordform.rpcAddress == null) mapOf("rpcAddress" to portAllocation.nextHostAndPort().toString()) else emptyMap()
|
||||
@ -224,33 +336,50 @@ class DriverDSLImpl(
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
if (startNodesInProcess) {
|
||||
Schedulers.reset()
|
||||
}
|
||||
_executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build())
|
||||
_shutdownManager = ShutdownManager(executorService)
|
||||
shutdownManager.registerShutdown { nodeInfoFilesCopier.close() }
|
||||
if (compatibilityZone == null) {
|
||||
// Without a compatibility zone URL we have to copy the node info files ourselves to make sure the nodes see each other
|
||||
nodeInfoFilesCopier = NodeInfoFilesCopier().also {
|
||||
shutdownManager.registerShutdown(it::close)
|
||||
}
|
||||
}
|
||||
val notaryInfos = generateNotaryIdentities()
|
||||
// The network parameters must be serialised before starting any of the nodes
|
||||
networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos))
|
||||
val nodeHandles = startNotaries()
|
||||
_notaries = notaryInfos.zip(nodeHandles) { (identity, validating), nodes -> NotaryHandle(identity, validating, nodes) }
|
||||
}
|
||||
|
||||
private fun generateNotaryIdentities(): List<Pair<Party, Boolean>> {
|
||||
private fun generateNotaryIdentities(): List<NotaryInfo> {
|
||||
return notarySpecs.map { spec ->
|
||||
val identity = if (spec.cluster == null) {
|
||||
ServiceIdentityGenerator.generateToDisk(
|
||||
dirs = listOf(baseDirectory(spec.name)),
|
||||
serviceName = spec.name.copy(commonName = NotaryService.constructId(validating = spec.validating))
|
||||
serviceName = spec.name,
|
||||
serviceId = "identity",
|
||||
customRootCert = compatibilityZone?.rootCert
|
||||
)
|
||||
} else {
|
||||
ServiceIdentityGenerator.generateToDisk(
|
||||
dirs = generateNodeNames(spec).map { baseDirectory(it) },
|
||||
serviceName = spec.name
|
||||
serviceName = spec.name,
|
||||
serviceId = NotaryService.constructId(
|
||||
validating = spec.validating,
|
||||
raft = spec.cluster is ClusterSpec.Raft
|
||||
),
|
||||
customRootCert = compatibilityZone?.rootCert
|
||||
)
|
||||
}
|
||||
Pair(identity, spec.validating)
|
||||
NotaryInfo(identity, spec.validating)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateNodeNames(spec: NotarySpec): List<CordaX500Name> {
|
||||
return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(commonName = null, organisation = "${spec.name.organisation}-$it") }
|
||||
return (0 until spec.cluster!!.clusterSize).map { spec.name.copy(organisation = "${spec.name.organisation}-$it") }
|
||||
}
|
||||
|
||||
private fun startNotaries(): List<CordaFuture<List<NodeHandle>>> {
|
||||
@ -321,6 +450,8 @@ class DriverDSLImpl(
|
||||
return driverDirectory / nodeDirectoryName
|
||||
}
|
||||
|
||||
override fun baseDirectory(nodeName: String): Path = baseDirectory(CordaX500Name.parse(nodeName))
|
||||
|
||||
/**
|
||||
* @param initial number of nodes currently in the network map of a running node.
|
||||
* @param networkMapCacheChangeObservable an observable returning the updates to the node network map.
|
||||
@ -365,15 +496,27 @@ class DriverDSLImpl(
|
||||
return future
|
||||
}
|
||||
|
||||
private fun startOutOfProcessNodeRegistration(config: Config, configuration: NodeConfiguration): CordaFuture<Unit> {
|
||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||
val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null
|
||||
val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort,
|
||||
systemProperties, cordappPackages, "200m", initialRegistration = true)
|
||||
|
||||
return poll(executorService, "node registration (${configuration.myLegalName})") {
|
||||
if (process.isAlive) null else Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun startNodeInternal(config: Config,
|
||||
webAddress: NetworkHostAndPort,
|
||||
startInProcess: Boolean?,
|
||||
maximumHeapSize: String): CordaFuture<NodeHandle> {
|
||||
val configuration = config.parseAsNodeConfiguration()
|
||||
val baseDirectory = configuration.baseDirectory.createDirectories()
|
||||
nodeInfoFilesCopier.addConfig(baseDirectory)
|
||||
nodeInfoFilesCopier?.addConfig(baseDirectory)
|
||||
networkParameters!!.install(baseDirectory)
|
||||
val onNodeExit: () -> Unit = {
|
||||
nodeInfoFilesCopier.removeConfig(baseDirectory)
|
||||
nodeInfoFilesCopier?.removeConfig(baseDirectory)
|
||||
countObservables.remove(configuration.myLegalName)
|
||||
}
|
||||
if (startInProcess ?: startNodesInProcess) {
|
||||
@ -396,7 +539,7 @@ class DriverDSLImpl(
|
||||
} else {
|
||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||
val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null
|
||||
val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, systemProperties, cordappPackages, maximumHeapSize)
|
||||
val process = startOutOfProcessNode(configuration, config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, systemProperties, cordappPackages, maximumHeapSize, initialRegistration = false)
|
||||
if (waitForNodesToFinish) {
|
||||
state.locked {
|
||||
processes += process
|
||||
@ -406,7 +549,7 @@ class DriverDSLImpl(
|
||||
}
|
||||
val p2pReadyFuture = addressMustBeBoundFuture(executorService, configuration.p2pAddress, process)
|
||||
return p2pReadyFuture.flatMap {
|
||||
val processDeathFuture = poll(executorService, "process death") {
|
||||
val processDeathFuture = poll(executorService, "process death while waiting for RPC (${configuration.myLegalName})") {
|
||||
if (process.isAlive) null else process
|
||||
}
|
||||
establishRpc(configuration, processDeathFuture).flatMap { rpc ->
|
||||
@ -479,8 +622,8 @@ class DriverDSLImpl(
|
||||
node.internals.run()
|
||||
}
|
||||
node to nodeThread
|
||||
}.flatMap {
|
||||
nodeAndThread -> addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread }
|
||||
}.flatMap { nodeAndThread ->
|
||||
addressMustBeBoundFuture(executorService, nodeConf.p2pAddress).map { nodeAndThread }
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,19 +636,26 @@ class DriverDSLImpl(
|
||||
monitorPort: Int?,
|
||||
overriddenSystemProperties: Map<String, String>,
|
||||
cordappPackages: List<String>,
|
||||
maximumHeapSize: String
|
||||
maximumHeapSize: String,
|
||||
initialRegistration: Boolean
|
||||
): Process {
|
||||
log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + (debugPort ?: "not enabled") + ", jolokia monitoring port is " + (monitorPort ?: "not enabled"))
|
||||
// Write node.conf
|
||||
writeConfig(nodeConf.baseDirectory, "node.conf", config)
|
||||
|
||||
val systemProperties = overriddenSystemProperties + mapOf(
|
||||
val systemProperties = mutableMapOf(
|
||||
"name" to nodeConf.myLegalName,
|
||||
"visualvm.display.name" to "corda-${nodeConf.myLegalName}",
|
||||
Node.scanPackagesSystemProperty to cordappPackages.joinToString(Node.scanPackagesSeparator),
|
||||
"java.io.tmpdir" to System.getProperty("java.io.tmpdir"), // Inherit from parent process
|
||||
"log4j2.debug" to if(debugPort != null) "true" else "false"
|
||||
)
|
||||
|
||||
if (cordappPackages.isNotEmpty()) {
|
||||
systemProperties += Node.scanPackagesSystemProperty to cordappPackages.joinToString(Node.scanPackagesSeparator)
|
||||
}
|
||||
|
||||
systemProperties += overriddenSystemProperties
|
||||
|
||||
// See experimental/quasar-hook/README.md for how to generate.
|
||||
val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;" +
|
||||
"com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;" +
|
||||
@ -519,13 +669,18 @@ class DriverDSLImpl(
|
||||
val jolokiaAgent = monitorPort?.let { "-javaagent:$jolokiaJarPath=port=$monitorPort,host=localhost" }
|
||||
val loggingLevel = if (debugPort == null) "INFO" else "DEBUG"
|
||||
|
||||
val arguments = mutableListOf(
|
||||
"--base-directory=${nodeConf.baseDirectory}",
|
||||
"--logging-level=$loggingLevel",
|
||||
"--no-local-shell").also {
|
||||
if (initialRegistration) {
|
||||
it += "--initial-registration"
|
||||
}
|
||||
}.toList()
|
||||
|
||||
return ProcessUtilities.startCordaProcess(
|
||||
className = "net.corda.node.Corda", // cannot directly get class for this, so just use string
|
||||
arguments = listOf(
|
||||
"--base-directory=${nodeConf.baseDirectory}",
|
||||
"--logging-level=$loggingLevel",
|
||||
"--no-local-shell"
|
||||
),
|
||||
arguments = arguments,
|
||||
jdwpPort = debugPort,
|
||||
extraJvmArguments = extraJvmArguments + listOfNotNull(jolokiaAgent),
|
||||
errorLogPath = nodeConf.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME / "error.log",
|
||||
@ -677,7 +832,8 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
|
||||
waitForNodesToFinish = waitForNodesToFinish,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
jmxPolicy = jmxPolicy,
|
||||
notarySpecs = notarySpecs
|
||||
notarySpecs = notarySpecs,
|
||||
compatibilityZone = null
|
||||
)
|
||||
)
|
||||
val shutdownHook = addShutdownHook(driverDsl::shutdown)
|
||||
@ -694,6 +850,50 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @property url The base CZ URL for registration and network map updates
|
||||
* @property rootCert If specified then the node will register itself using [url] and expect the registration response
|
||||
* to be rooted at this cert.
|
||||
*/
|
||||
data class CompatibilityZoneParams(val url: URL, val rootCert: X509Certificate? = null)
|
||||
|
||||
fun <A> internalDriver(
|
||||
isDebug: Boolean = DriverParameters().isDebug,
|
||||
driverDirectory: Path = DriverParameters().driverDirectory,
|
||||
portAllocation: PortAllocation = DriverParameters().portAllocation,
|
||||
debugPortAllocation: PortAllocation = DriverParameters().debugPortAllocation,
|
||||
systemProperties: Map<String, String> = DriverParameters().systemProperties,
|
||||
useTestClock: Boolean = DriverParameters().useTestClock,
|
||||
initialiseSerialization: Boolean = DriverParameters().initialiseSerialization,
|
||||
startNodesInProcess: Boolean = DriverParameters().startNodesInProcess,
|
||||
waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish,
|
||||
notarySpecs: List<NotarySpec> = DriverParameters().notarySpecs,
|
||||
extraCordappPackagesToScan: List<String> = DriverParameters().extraCordappPackagesToScan,
|
||||
jmxPolicy: JmxPolicy = DriverParameters().jmxPolicy,
|
||||
compatibilityZone: CompatibilityZoneParams? = null,
|
||||
dsl: DriverDSLImpl.() -> A
|
||||
): A {
|
||||
return genericDriver(
|
||||
driverDsl = DriverDSLImpl(
|
||||
portAllocation = portAllocation,
|
||||
debugPortAllocation = debugPortAllocation,
|
||||
systemProperties = systemProperties,
|
||||
driverDirectory = driverDirectory.toAbsolutePath(),
|
||||
useTestClock = useTestClock,
|
||||
isDebug = isDebug,
|
||||
startNodesInProcess = startNodesInProcess,
|
||||
waitForNodesToFinish = waitForAllNodesToFinish,
|
||||
notarySpecs = notarySpecs,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
jmxPolicy = jmxPolicy,
|
||||
compatibilityZone = compatibilityZone
|
||||
),
|
||||
coerce = { it },
|
||||
dsl = dsl,
|
||||
initialiseSerialization = initialiseSerialization
|
||||
)
|
||||
}
|
||||
|
||||
fun getTimestampAsDirectoryName(): String {
|
||||
return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC).format(Instant.now())
|
||||
}
|
||||
|
@ -12,12 +12,15 @@ 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.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.getFreeLocalPorts
|
||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import org.apache.logging.log4j.Level
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import rx.internal.schedulers.CachedThreadScheduler
|
||||
@ -38,6 +41,7 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
private lateinit var defaultNetworkParameters: NetworkParametersCopier
|
||||
private val nodes = mutableListOf<StartedNode<Node>>()
|
||||
private val nodeInfos = mutableListOf<NodeInfo>()
|
||||
|
||||
@ -45,6 +49,11 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
|
||||
System.setProperty("consoleLogLevel", Level.DEBUG.name().toLowerCase())
|
||||
}
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
defaultNetworkParameters = NetworkParametersCopier(testNetworkParameters(emptyList()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the network map node and all the nodes started by [startNode]. This is called automatically after each test
|
||||
* but can also be called manually within a test.
|
||||
@ -88,6 +97,7 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
|
||||
)
|
||||
|
||||
val parsedConfig = config.parseAsNodeConfiguration()
|
||||
defaultNetworkParameters.install(baseDirectory)
|
||||
val node = InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), cordappPackages).start()
|
||||
nodes += node
|
||||
ensureAllNetworkMapCachesHaveAllNodeInfos()
|
||||
|
@ -25,7 +25,8 @@ import net.corda.nodeapi.ConnectionDirection
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT
|
||||
import net.corda.testing.driver.*
|
||||
import net.corda.testing.driver.JmxPolicy
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.apache.activemq.artemis.api.core.TransportConfiguration
|
||||
@ -90,6 +91,7 @@ val fakeNodeLegalName = CordaX500Name(organisation = "Not:a:real:name", locality
|
||||
private val globalPortAllocation = PortAllocation.Incremental(10000)
|
||||
private val globalDebugPortAllocation = PortAllocation.Incremental(5005)
|
||||
private val globalMonitorPortAllocation = PortAllocation.Incremental(7005)
|
||||
|
||||
fun <A> rpcDriver(
|
||||
isDebug: Boolean = false,
|
||||
driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
|
||||
@ -106,25 +108,27 @@ fun <A> rpcDriver(
|
||||
dsl: RPCDriverDSL.() -> A
|
||||
) : A {
|
||||
return genericDriver(
|
||||
driverDsl = RPCDriverDSL(
|
||||
DriverDSLImpl(
|
||||
portAllocation = portAllocation,
|
||||
debugPortAllocation = debugPortAllocation,
|
||||
systemProperties = systemProperties,
|
||||
driverDirectory = driverDirectory.toAbsolutePath(),
|
||||
useTestClock = useTestClock,
|
||||
isDebug = isDebug,
|
||||
startNodesInProcess = startNodesInProcess,
|
||||
waitForNodesToFinish = waitForNodesToFinish,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
notarySpecs = notarySpecs,
|
||||
jmxPolicy = jmxPolicy
|
||||
), externalTrace
|
||||
),
|
||||
coerce = { it },
|
||||
dsl = dsl,
|
||||
initialiseSerialization = false
|
||||
)}
|
||||
driverDsl = RPCDriverDSL(
|
||||
DriverDSLImpl(
|
||||
portAllocation = portAllocation,
|
||||
debugPortAllocation = debugPortAllocation,
|
||||
systemProperties = systemProperties,
|
||||
driverDirectory = driverDirectory.toAbsolutePath(),
|
||||
useTestClock = useTestClock,
|
||||
isDebug = isDebug,
|
||||
startNodesInProcess = startNodesInProcess,
|
||||
waitForNodesToFinish = waitForNodesToFinish,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
notarySpecs = notarySpecs,
|
||||
jmxPolicy = jmxPolicy,
|
||||
compatibilityZone = null
|
||||
), externalTrace
|
||||
),
|
||||
coerce = { it },
|
||||
dsl = dsl,
|
||||
initialiseSerialization = false
|
||||
)
|
||||
}
|
||||
|
||||
private class SingleUserSecurityManager(val rpcUser: User) : ActiveMQSecurityManager3 {
|
||||
override fun validateUser(user: String?, password: String?) = isValid(user, password)
|
||||
@ -132,6 +136,7 @@ private class SingleUserSecurityManager(val rpcUser: User) : ActiveMQSecurityMan
|
||||
override fun validateUser(user: String?, password: String?, remotingConnection: RemotingConnection?): String? {
|
||||
return validate(user, password)
|
||||
}
|
||||
|
||||
override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet<Role>?, checkType: CheckType?, address: String?, connection: RemotingConnection?): String? {
|
||||
return validate(user, password)
|
||||
}
|
||||
|
@ -4,14 +4,11 @@ package net.corda.testing.internal.demorun
|
||||
|
||||
import net.corda.cordform.CordformDefinition
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.internal.concurrent.flatMap
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.testing.driver.JmxPolicy
|
||||
import net.corda.testing.internal.DriverDSLImpl
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.internal.internalDriver
|
||||
|
||||
fun CordformDefinition.clean() {
|
||||
System.err.println("Deleting: $nodesDirectory")
|
||||
@ -35,12 +32,13 @@ fun CordformDefinition.deployNodesThen(block: () -> Unit) {
|
||||
}
|
||||
|
||||
private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block: () -> Unit) {
|
||||
clean()
|
||||
val nodes = nodeConfigurers.map { configurer -> CordformNode().also { configurer.accept(it) } }
|
||||
val maxPort = nodes
|
||||
.flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) }
|
||||
.mapNotNull { address -> address?.let { NetworkHostAndPort.parse(it).port } }
|
||||
.max()!!
|
||||
driver(
|
||||
internalDriver(
|
||||
isDebug = true,
|
||||
jmxPolicy = JmxPolicy(true),
|
||||
driverDirectory = nodesDirectory,
|
||||
@ -51,17 +49,8 @@ private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block:
|
||||
portAllocation = PortAllocation.Incremental(maxPort + 1),
|
||||
waitForAllNodesToFinish = waitForAllNodesToFinish
|
||||
) {
|
||||
this as DriverDSLImpl // access internal API
|
||||
setup(this)
|
||||
nodes.map {
|
||||
val startedNode = startCordformNode(it)
|
||||
if (it.webAddress != null) {
|
||||
// Start a webserver if an address for it was specified
|
||||
startedNode.flatMap { startWebserver(it) }
|
||||
} else {
|
||||
startedNode
|
||||
}
|
||||
}.transpose().getOrThrow() // Only proceed once everything is up and running
|
||||
startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running
|
||||
println("All nodes and webservers are ready...")
|
||||
block()
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.internal.concurrent.doneFuture
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.network.PersistentNetworkMapCache
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.testing.getTestPartyAndCertificate
|
||||
@ -17,7 +18,7 @@ import java.math.BigInteger
|
||||
/**
|
||||
* Network map cache with no backing map service.
|
||||
*/
|
||||
class MockNetworkMapCache(database: CordaPersistence) : PersistentNetworkMapCache(database) {
|
||||
class MockNetworkMapCache(database: CordaPersistence) : PersistentNetworkMapCache(database, emptyList()) {
|
||||
private companion object {
|
||||
val BANK_C = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank C", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(1000)).public)
|
||||
val BANK_D = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank D", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(2000)).public)
|
||||
|
@ -38,7 +38,11 @@ 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.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.internal.NotaryInfo
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.nodeapi.internal.NetworkParametersCopier
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.internal.testThreadFactory
|
||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
@ -143,6 +147,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
val messagingNetwork = InMemoryMessagingNetwork(networkSendManuallyPumped, servicePeerAllocationStrategy, busyLatch)
|
||||
// A unique identifier for this network to segregate databases with the same nodeID but different networks.
|
||||
private val networkId = random63BitValue()
|
||||
private val networkParameters: NetworkParametersCopier
|
||||
private val _nodes = mutableListOf<MockNode>()
|
||||
private val serializationEnv = setGlobalSerialization(initialiseSerialization)
|
||||
private val sharedUserCount = AtomicInteger(0)
|
||||
@ -159,29 +164,32 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
* Returns the single notary node on the network. Throws if there are none or more than one.
|
||||
* @see notaryNodes
|
||||
*/
|
||||
val defaultNotaryNode: StartedNode<MockNode> get() {
|
||||
return when (notaryNodes.size) {
|
||||
0 -> throw IllegalStateException("There are no notaries defined on the network")
|
||||
1 -> notaryNodes[0]
|
||||
else -> throw IllegalStateException("There is more than one notary defined on the network")
|
||||
val defaultNotaryNode: StartedNode<MockNode>
|
||||
get() {
|
||||
return when (notaryNodes.size) {
|
||||
0 -> throw IllegalStateException("There are no notaries defined on the network")
|
||||
1 -> notaryNodes[0]
|
||||
else -> throw IllegalStateException("There is more than one notary defined on the network")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the identity of the default notary node.
|
||||
* @see defaultNotaryNode
|
||||
*/
|
||||
val defaultNotaryIdentity: Party get() {
|
||||
return defaultNotaryNode.info.legalIdentities[1] // TODO Resolve once network parameters is merged back in
|
||||
}
|
||||
val defaultNotaryIdentity: Party
|
||||
get() {
|
||||
return defaultNotaryNode.info.legalIdentities.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the identity of the default notary node.
|
||||
* @see defaultNotaryNode
|
||||
*/
|
||||
val defaultNotaryIdentityAndCert: PartyAndCertificate get() {
|
||||
return defaultNotaryNode.info.legalIdentitiesAndCerts[1] // TODO Resolve once network parameters is merged back in
|
||||
}
|
||||
val defaultNotaryIdentityAndCert: PartyAndCertificate
|
||||
get() {
|
||||
return defaultNotaryNode.info.legalIdentitiesAndCerts.singleOrNull() ?: throw IllegalStateException("Default notary has multiple identities")
|
||||
}
|
||||
|
||||
/**
|
||||
* Because this executor is shared, we need to be careful about nodes shutting it down.
|
||||
@ -207,9 +215,22 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
|
||||
init {
|
||||
filesystem.getPath("/nodes").createDirectory()
|
||||
val notaryInfos = generateNotaryIdentities()
|
||||
// The network parameters must be serialised before starting any of the nodes
|
||||
networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos))
|
||||
notaryNodes = createNotaries()
|
||||
}
|
||||
|
||||
private fun generateNotaryIdentities(): List<NotaryInfo> {
|
||||
return notarySpecs.mapIndexed { index, spec ->
|
||||
val identity = ServiceIdentityGenerator.generateToDisk(
|
||||
dirs = listOf(baseDirectory(nextNodeId + index)),
|
||||
serviceName = spec.name,
|
||||
serviceId = "identity")
|
||||
NotaryInfo(identity, spec.validating)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNotaries(): List<StartedNode<MockNode>> {
|
||||
return notarySpecs.map { spec ->
|
||||
createNode(MockNodeParameters(legalName = spec.name, configOverrides = {
|
||||
@ -245,6 +266,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
override val started: StartedNode<MockNode>? get() = uncheckedCast(super.started)
|
||||
|
||||
override fun start(): StartedNode<MockNode> {
|
||||
mockNet.networkParameters.install(configuration.baseDirectory)
|
||||
val started: StartedNode<MockNode> = uncheckedCast(super.start())
|
||||
advertiseNodeToNetwork(started)
|
||||
return started
|
||||
|
@ -2,7 +2,6 @@ package net.corda.testing.node
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.node.services.config.VerifierType
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
|
||||
data class NotarySpec(
|
||||
@ -11,15 +10,7 @@ data class NotarySpec(
|
||||
val rpcUsers: List<User> = emptyList(),
|
||||
val verifierType: VerifierType = VerifierType.InMemory,
|
||||
val cluster: ClusterSpec? = null
|
||||
) {
|
||||
init {
|
||||
// TODO This will be removed once network parameters define the notaries
|
||||
when (cluster) {
|
||||
is ClusterSpec.Raft -> require(name.commonName == RaftValidatingNotaryService.id)
|
||||
null -> require(name.commonName == null)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
sealed class ClusterSpec {
|
||||
abstract val clusterSize: Int
|
||||
|
@ -0,0 +1,155 @@
|
||||
package net.corda.testing.node.network
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.toX509CertHolder
|
||||
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.core.utilities.hours
|
||||
import net.corda.nodeapi.internal.DigitalSignatureWithCert
|
||||
import net.corda.nodeapi.internal.NetworkMap
|
||||
import net.corda.nodeapi.internal.NetworkParameters
|
||||
import net.corda.nodeapi.internal.SignedNetworkMap
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.testing.ROOT_CA
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.server.ServerConnector
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.servlet.ServletHolder
|
||||
import org.glassfish.jersey.server.ResourceConfig
|
||||
import org.glassfish.jersey.servlet.ServletContainer
|
||||
import java.io.Closeable
|
||||
import java.io.InputStream
|
||||
import java.net.InetSocketAddress
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import javax.ws.rs.*
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
import javax.ws.rs.core.Response.ok
|
||||
|
||||
class NetworkMapServer(cacheTimeout: Duration,
|
||||
hostAndPort: NetworkHostAndPort,
|
||||
vararg additionalServices: Any) : Closeable {
|
||||
companion object {
|
||||
val stubNetworkParameter = NetworkParameters(1, emptyList(), 1.hours, 10, 10, Instant.now(), 10)
|
||||
|
||||
private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair {
|
||||
val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val networkMapCert = X509Utilities.createCertificate(
|
||||
CertificateType.INTERMEDIATE_CA,
|
||||
rootCAKeyAndCert.certificate,
|
||||
rootCAKeyAndCert.keyPair,
|
||||
X500Name("CN=Corda Network Map,L=London"),
|
||||
networkMapKey.public).cert
|
||||
return CertificateAndKeyPair(networkMapCert.toX509CertHolder(), networkMapKey)
|
||||
}
|
||||
}
|
||||
|
||||
private val server: Server
|
||||
// Default to ROOT_CA for testing.
|
||||
// TODO: make this configurable?
|
||||
private val service = InMemoryNetworkMapService(cacheTimeout, networkMapKeyAndCert(ROOT_CA))
|
||||
|
||||
init {
|
||||
server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
|
||||
handler = HandlerCollection().apply {
|
||||
addHandler(ServletContextHandler().apply {
|
||||
contextPath = "/"
|
||||
val resourceConfig = ResourceConfig().apply {
|
||||
// Add your API provider classes (annotated for JAX-RS) here
|
||||
register(service)
|
||||
additionalServices.forEach { register(it) }
|
||||
}
|
||||
val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 } // Initialise at server start
|
||||
addServlet(jerseyServlet, "/*")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun start(): NetworkHostAndPort {
|
||||
server.start()
|
||||
// Wait until server is up to obtain the host and port.
|
||||
while (!server.isStarted) {
|
||||
Thread.sleep(500)
|
||||
}
|
||||
return server.connectors
|
||||
.mapNotNull { it as? ServerConnector }
|
||||
.first()
|
||||
.let { NetworkHostAndPort(it.host, it.localPort) }
|
||||
}
|
||||
|
||||
fun removeNodeInfo(nodeInfo: NodeInfo) {
|
||||
service.removeNodeInfo(nodeInfo)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
server.stop()
|
||||
}
|
||||
|
||||
@Path("network-map")
|
||||
class InMemoryNetworkMapService(private val cacheTimeout: Duration, private val networkMapKeyAndCert: CertificateAndKeyPair) {
|
||||
private val nodeInfoMap = mutableMapOf<SecureHash, SignedData<NodeInfo>>()
|
||||
|
||||
@POST
|
||||
@Path("publish")
|
||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun publishNodeInfo(input: InputStream): Response {
|
||||
val registrationData = input.readBytes().deserialize<SignedData<NodeInfo>>()
|
||||
val nodeInfo = registrationData.verified()
|
||||
val nodeInfoHash = nodeInfo.serialize().sha256()
|
||||
nodeInfoMap.put(nodeInfoHash, registrationData)
|
||||
return ok().build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun getNetworkMap(): Response {
|
||||
val networkMap = NetworkMap(nodeInfoMap.keys.map { it }, SecureHash.randomSHA256())
|
||||
val serializedNetworkMap = networkMap.serialize()
|
||||
val signature = Crypto.doSign(networkMapKeyAndCert.keyPair.private, serializedNetworkMap.bytes)
|
||||
val signedNetworkMap = SignedNetworkMap(networkMap.serialize(), DigitalSignatureWithCert(networkMapKeyAndCert.certificate.cert, signature))
|
||||
return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${cacheTimeout.seconds}").build()
|
||||
}
|
||||
|
||||
// Remove nodeInfo for testing.
|
||||
fun removeNodeInfo(nodeInfo: NodeInfo) {
|
||||
nodeInfoMap.remove(nodeInfo.serialize().hash)
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("node-info/{var}")
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response {
|
||||
val signedNodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)]
|
||||
return if (signedNodeInfo != null) {
|
||||
Response.ok(signedNodeInfo.serialize().bytes)
|
||||
} else {
|
||||
Response.status(Response.Status.NOT_FOUND)
|
||||
}.build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("network-parameter/{var}")
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
fun getNetworkParameter(@PathParam("var") networkParameterHash: String): Response {
|
||||
return Response.ok(stubNetworkParameter.serialize().bytes).build()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("my-hostname")
|
||||
fun getHostName(): Response {
|
||||
return Response.ok("test.host.name").build()
|
||||
}
|
||||
}
|
||||
}
|
@ -14,8 +14,7 @@ class NodeConfig(
|
||||
val rpcPort: Int,
|
||||
val webPort: Int,
|
||||
val isNotary: Boolean,
|
||||
val users: List<User>,
|
||||
var networkMap: NodeConfig? = null
|
||||
val users: List<User>
|
||||
) {
|
||||
companion object {
|
||||
val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
|
||||
@ -32,10 +31,6 @@ class NodeConfig(
|
||||
val config = empty()
|
||||
.withValue("myLegalName", valueFor(legalName.toString()))
|
||||
.withValue("p2pAddress", addressValueFor(p2pPort))
|
||||
.withFallback(optional("networkMapService", networkMap, { c, n ->
|
||||
c.withValue("address", addressValueFor(n.p2pPort))
|
||||
.withValue("legalName", valueFor(n.legalName.toString()))
|
||||
}))
|
||||
.withValue("webAddress", addressValueFor(webPort))
|
||||
.withValue("rpcAddress", addressValueFor(rpcPort))
|
||||
.withValue("rpcUsers", valueFor(users.map(User::toMap).toList()))
|
||||
@ -50,8 +45,6 @@ class NodeConfig(
|
||||
fun toText(): String = toFileConfig().root().render(renderOptions)
|
||||
|
||||
private fun <T> valueFor(any: T): ConfigValue? = ConfigValueFactory.fromAnyRef(any)
|
||||
|
||||
private fun addressValueFor(port: Int) = valueFor("localhost:$port")
|
||||
private inline fun <T> optional(path: String, obj: T?, body: (Config, T) -> Config): Config {
|
||||
return if (obj == null) empty() else body(empty(), obj).atPath(path)
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,15 @@ package net.corda.smoketesting
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.CordaRPCConnection
|
||||
import net.corda.client.rpc.internal.KryoClientSerializationScheme
|
||||
import net.corda.core.internal.copyTo
|
||||
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.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.common.internal.asContextEnv
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.Instant
|
||||
@ -52,6 +56,14 @@ class NodeProcess(
|
||||
private companion object {
|
||||
val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java")
|
||||
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault())
|
||||
val defaultNetworkParameters = run {
|
||||
KryoClientSerializationScheme.createSerializationEnv().asContextEnv {
|
||||
// There are no notaries in the network parameters for smoke test nodes. If this is required then we would
|
||||
// need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork
|
||||
NetworkParametersCopier(testNetworkParameters(emptyList()))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
try {
|
||||
Class.forName("net.corda.node.Corda")
|
||||
@ -71,6 +83,7 @@ class NodeProcess(
|
||||
log.info("Node directory: {}", nodeDir)
|
||||
|
||||
config.toText().byteInputStream().copyTo(nodeDir / "node.conf")
|
||||
defaultNetworkParameters.install(nodeDir)
|
||||
|
||||
val process = startNode(nodeDir)
|
||||
val client = CordaRPCClient(NetworkHostAndPort("localhost", config.rpcPort))
|
||||
|
@ -3,6 +3,7 @@ apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile project(':node-api')
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -0,0 +1,18 @@
|
||||
package net.corda.testing.common.internal
|
||||
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.nodeapi.internal.NetworkParameters
|
||||
import net.corda.nodeapi.internal.NotaryInfo
|
||||
import java.time.Instant
|
||||
|
||||
fun testNetworkParameters(notaries: List<NotaryInfo>): NetworkParameters {
|
||||
return NetworkParameters(
|
||||
minimumPlatformVersion = 1,
|
||||
notaries = notaries,
|
||||
modifiedTime = Instant.now(),
|
||||
eventHorizon = 10000.days,
|
||||
maxMessageSize = 40000,
|
||||
maxTransactionSize = 40000,
|
||||
epoch = 1
|
||||
)
|
||||
}
|
@ -140,7 +140,7 @@ fun getTestPartyAndCertificate(party: Party): PartyAndCertificate {
|
||||
|
||||
val certHolder = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, issuerCertificate, issuerKeyPair, party.name, party.owningKey)
|
||||
val pathElements = listOf(certHolder, issuerCertificate, intermediate.certificate, trustRoot)
|
||||
val certPath = X509CertificateFactory().delegate.generateCertPath(pathElements.map(X509CertificateHolder::cert))
|
||||
val certPath = X509CertificateFactory().generateCertPath(pathElements.map(X509CertificateHolder::cert))
|
||||
return PartyAndCertificate(certPath)
|
||||
}
|
||||
|
||||
|
@ -66,12 +66,18 @@ val DUMMY_REGULATOR: Party get() = Party(CordaX500Name(organisation = "Regulator
|
||||
|
||||
val DEV_CA: CertificateAndKeyPair by lazy {
|
||||
// TODO: Should be identity scheme
|
||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||
}
|
||||
|
||||
val ROOT_CA: CertificateAndKeyPair by lazy {
|
||||
// TODO: Should be identity scheme
|
||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, "cordacadevkeypass")
|
||||
}
|
||||
val DEV_TRUST_ROOT: X509CertificateHolder by lazy {
|
||||
// TODO: Should be identity scheme
|
||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val caKeyStore = loadKeyStore(ClassLoader.getSystemResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
caKeyStore.getCertificateChain(X509Utilities.CORDA_INTERMEDIATE_CA).last().toX509CertHolder()
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,13 @@
|
||||
package net.corda.demobench
|
||||
|
||||
import javafx.scene.image.Image
|
||||
import net.corda.client.rpc.internal.KryoClientSerializationScheme
|
||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||
import net.corda.demobench.views.DemoBenchView
|
||||
import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||
import tornadofx.*
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets.UTF_8
|
||||
@ -47,6 +53,17 @@ class DemoBench : App(DemoBenchView::class) {
|
||||
|
||||
init {
|
||||
addStageIcon(Image("cordalogo.png"))
|
||||
initialiseSerialization()
|
||||
}
|
||||
|
||||
private fun initialiseSerialization() {
|
||||
val context = KRYO_P2P_CONTEXT
|
||||
nodeSerializationEnv = SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoClientSerializationScheme())
|
||||
registerScheme(AMQPClientSerializationScheme())
|
||||
},
|
||||
context)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.demobench.model
|
||||
|
||||
import net.corda.nodeapi.NodeInfoFilesCopier
|
||||
import net.corda.nodeapi.internal.NodeInfoFilesCopier
|
||||
import rx.Scheduler
|
||||
import rx.schedulers.Schedulers
|
||||
import tornadofx.*
|
||||
|
@ -2,19 +2,26 @@ package net.corda.demobench.model
|
||||
|
||||
import javafx.beans.binding.IntegerExpression
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.copyToDirectory
|
||||
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.ServiceIdentityGenerator
|
||||
import tornadofx.*
|
||||
import java.io.IOException
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.logging.Level
|
||||
@ -35,6 +42,8 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
|
||||
private val command = jvm.commandFor(cordaPath).toTypedArray()
|
||||
|
||||
private val nodes = LinkedHashMap<String, NodeConfigWrapper>()
|
||||
private var notaryIdentity: Party? = null
|
||||
private var networkParametersCopier: NetworkParametersCopier? = null
|
||||
private val port = AtomicInteger(firstPort)
|
||||
|
||||
val activeNodes: List<NodeConfigWrapper>
|
||||
@ -58,6 +67,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
|
||||
fun IntegerExpression.toLocalAddress() = NetworkHostAndPort("localhost", value)
|
||||
|
||||
val location = nodeData.nearestCity.value
|
||||
val notary = nodeData.extraServices.filterIsInstance<NotaryService>().noneOrSingle()
|
||||
val nodeConfig = NodeConfig(
|
||||
myLegalName = CordaX500Name(
|
||||
organisation = nodeData.legalName.value.trim(),
|
||||
@ -67,7 +77,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
|
||||
p2pAddress = nodeData.p2pPort.toLocalAddress(),
|
||||
rpcAddress = nodeData.rpcPort.toLocalAddress(),
|
||||
webAddress = nodeData.webPort.toLocalAddress(),
|
||||
notary = nodeData.extraServices.filterIsInstance<NotaryService>().noneOrSingle(),
|
||||
notary = notary,
|
||||
h2port = nodeData.h2Port.value,
|
||||
issuableCurrencies = nodeData.extraServices.filterIsInstance<CurrencyIssuer>().map { it.currency.toString() }
|
||||
)
|
||||
@ -102,6 +112,8 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
|
||||
|
||||
fun runCorda(pty: R3Pty, config: NodeConfigWrapper): Boolean {
|
||||
try {
|
||||
// Notary can be removed and then added again, that's why we need to perform this check.
|
||||
require((config.nodeConfig.notary != null).xor(notaryIdentity != null)) { "There must be exactly one notary in the network" }
|
||||
config.nodeDir.createDirectories()
|
||||
|
||||
// Install any built-in plugins into the working directory.
|
||||
@ -115,6 +127,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
|
||||
val cordaEnv = System.getenv().toMutableMap().apply {
|
||||
jvm.setCapsuleCacheDir(this)
|
||||
}
|
||||
(networkParametersCopier ?: makeNetworkParametersCopier(config)).install(config.nodeDir)
|
||||
pty.run(command, cordaEnv, config.nodeDir.toString())
|
||||
log.info("Launched node: ${config.nodeConfig.myLegalName}")
|
||||
return true
|
||||
@ -124,6 +137,30 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeNetworkParametersCopier(config: NodeConfigWrapper): NetworkParametersCopier {
|
||||
val identity = getNotaryIdentity(config)
|
||||
val parametersCopier = NetworkParametersCopier(NetworkParameters(
|
||||
minimumPlatformVersion = 1,
|
||||
notaries = listOf(NotaryInfo(identity, config.nodeConfig.notary!!.validating)),
|
||||
modifiedTime = Instant.now(),
|
||||
eventHorizon = 10000.days,
|
||||
maxMessageSize = 40000,
|
||||
maxTransactionSize = 40000,
|
||||
epoch = 1
|
||||
))
|
||||
notaryIdentity = identity
|
||||
networkParametersCopier = parametersCopier
|
||||
return parametersCopier
|
||||
}
|
||||
|
||||
// Generate notary identity and save it into node's directory. This identity will be used in network parameters.
|
||||
private fun getNotaryIdentity(config: NodeConfigWrapper): Party {
|
||||
return ServiceIdentityGenerator.generateToDisk(
|
||||
dirs = listOf(config.nodeDir),
|
||||
serviceName = config.nodeConfig.myLegalName,
|
||||
serviceId = "identity")
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
baseDir = baseDirFor(System.currentTimeMillis())
|
||||
log.info("Changed base directory: $baseDir")
|
||||
@ -131,6 +168,8 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
|
||||
// Wipe out any knowledge of previous nodes.
|
||||
nodes.clear()
|
||||
nodeInfoFilesCopier.reset()
|
||||
notaryIdentity = null
|
||||
networkParametersCopier = null
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,7 +31,7 @@ val dummyNotarisationTest = LoadTest<NotariseCommand, Unit>(
|
||||
val issuerServices = MockServices(makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)), MEGA_CORP.name, DUMMY_CASH_ISSUER_KEY)
|
||||
val generateTx = Generator.pickOne(simpleNodes).flatMap { node ->
|
||||
Generator.int().map {
|
||||
val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[1], DUMMY_CASH_ISSUER) // TODO notary choice
|
||||
val issueBuilder = DummyContract.generateInitial(it, notary.info.legalIdentities[0], DUMMY_CASH_ISSUER) // TODO notary choice
|
||||
val issueTx = issuerServices.signInitialTransaction(issueBuilder)
|
||||
val asset = issueTx.tx.outRef<DummyContract.SingleOwnerState>(0)
|
||||
val moveBuilder = DummyContract.move(asset, DUMMY_CASH_ISSUER.party)
|
||||
|
@ -19,9 +19,9 @@ import net.corda.node.services.config.configureDevKeyAndTrustStores
|
||||
import net.corda.nodeapi.ArtemisTcpTransport
|
||||
import net.corda.nodeapi.ConnectionDirection
|
||||
import net.corda.nodeapi.VerifierApi
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.testing.driver.JmxPolicy
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
@ -75,7 +75,8 @@ fun <A> verifierDriver(
|
||||
waitForNodesToFinish = waitForNodesToFinish,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan,
|
||||
notarySpecs = notarySpecs,
|
||||
jmxPolicy = jmxPolicy
|
||||
jmxPolicy = jmxPolicy,
|
||||
compatibilityZone = null
|
||||
)
|
||||
),
|
||||
coerce = { it },
|
||||
@ -183,7 +184,6 @@ data class VerifierDriverDSL(private val driverDSL: DriverDSLImpl) : InternalDri
|
||||
val securityManager = object : ActiveMQSecurityManager {
|
||||
// We don't need auth, SSL is good enough
|
||||
override fun validateUser(user: String?, password: String?) = true
|
||||
|
||||
override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet<Role>?, checkType: CheckType?) = true
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user