Merge branch 'feature-network-parameters' into shams-merge-feature-network-parameters

This commit is contained in:
Shams Asari 2017-12-11 21:10:34 +00:00
commit 6d6393d984
84 changed files with 1439 additions and 464 deletions

View File

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

View File

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

View File

@ -48,8 +48,7 @@ public class StandaloneCordaRPCJavaClientTest {
port.getAndIncrement(),
port.getAndIncrement(),
true,
Collections.singletonList(rpcUser),
null
Collections.singletonList(rpcUser)
);
@Before

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
package net.corda.cordform;
import org.bouncycastle.asn1.x500.X500Name;
import java.nio.file.Path;
public interface CordformContext {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package net.corda.nodeapi
package net.corda.nodeapi.internal
import net.corda.cordform.CordformNode
import net.corda.core.internal.ThreadBox

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,6 +27,7 @@ class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: Ne
}
fun stop() = synchronized(this) {
rpcServer?.close()
artemis.stop()
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ apply plugin: 'com.jfrog.artifactory'
dependencies {
compile project(':core')
compile project(':node-api')
}
jar {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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