Merge pull request #1374 from corda/anthony-os-merge-20180906

O/S Merge 20180906
This commit is contained in:
Anthony Keenan 2018-09-07 12:14:23 +01:00 committed by GitHub
commit 458bedd936
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 936 additions and 949 deletions

View File

@ -369,6 +369,7 @@ bintrayConfig {
'corda-rpc',
'corda-core',
'corda-core-deterministic',
'corda-djvm',
'corda',
'corda-finance',
'corda-node',
@ -477,6 +478,6 @@ if (file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BU
}
wrapper {
gradleVersion = "4.8.1"
gradleVersion = "4.10"
distributionType = Wrapper.DistributionType.ALL
}

View File

@ -12,6 +12,7 @@ import net.corda.core.utilities.days
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
import net.corda.nodeapi.internal.PLATFORM_VERSION
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
import java.time.Duration
@ -45,7 +46,7 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
* The default value is whatever version of Corda this RPC library was shipped as a part of. Therefore if you
* use the RPC library from Corda 4, it will by default only connect to a node of version 4 or above.
*/
open val minimumServerProtocolVersion: Int = 4,
open val minimumServerProtocolVersion: Int = PLATFORM_VERSION,
/**
* If set to true the client will track RPC call sites (default is false). If an error occurs subsequently

View File

@ -0,0 +1,21 @@
@file:JvmName("Enclavelet")
package net.corda.deterministic.common
import net.corda.core.serialization.deserialize
import net.corda.core.transactions.LedgerTransaction
/**
* We assume the signatures were already checked outside the sandbox: the purpose of this code
* is simply to check the sensitive, app-specific parts of a transaction.
*
* TODO: Transaction data is meant to be encrypted under an enclave-private key.
*/
@Throws(Exception::class)
fun verifyInEnclave(reqBytes: ByteArray) {
deserialize(reqBytes).verify()
}
private fun deserialize(reqBytes: ByteArray): LedgerTransaction {
return reqBytes.deserialize<TransactionVerificationRequest>()
.toLedgerTransaction()
}

View File

@ -1,11 +1,8 @@
@file:JvmName("Enclavelet")
package net.corda.deterministic.txverify
import net.corda.core.serialization.deserialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.deterministic.bytesOfResource
import net.corda.deterministic.common.LocalSerializationRule
import net.corda.deterministic.common.TransactionVerificationRequest
import net.corda.deterministic.common.verifyInEnclave
import net.corda.finance.contracts.asset.Cash.Commands.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.ClassRule
@ -30,23 +27,3 @@ class EnclaveletTest {
assertThat(e).hasMessageContaining("Required ${Move::class.java.canonicalName} command")
}
}
/**
* Returns either null to indicate success when the transactions are validated, or a string with the
* contents of the error. Invoked via JNI in response to an enclave RPC. The argument is a serialised
* [TransactionVerificationRequest].
*
* Note that it is assumed the signatures were already checked outside the sandbox: the purpose of this code
* is simply to check the sensitive, app specific parts of a transaction.
*
* TODO: Transaction data is meant to be encrypted under an enclave-private key.
*/
@Throws(Exception::class)
private fun verifyInEnclave(reqBytes: ByteArray) {
deserialize(reqBytes).verify()
}
private fun deserialize(reqBytes: ByteArray): LedgerTransaction {
return reqBytes.deserialize<TransactionVerificationRequest>()
.toLedgerTransaction()
}

View File

@ -76,7 +76,8 @@ sealed class StateMachineUpdate {
// DOCSTART 1
/**
* Data class containing information about the scheduled network parameters update. The info is emitted every time node
* receives network map with [ParametersUpdate] which wasn't seen before. For more information see: [CordaRPCOps.networkParametersFeed] and [CordaRPCOps.acceptNewNetworkParameters].
* receives network map with [ParametersUpdate] which wasn't seen before. For more information see: [CordaRPCOps.networkParametersFeed]
* and [CordaRPCOps.acceptNewNetworkParameters].
* @property hash new [NetworkParameters] hash
* @property parameters new [NetworkParameters] data structure
* @property description description of the update
@ -233,6 +234,9 @@ interface CordaRPCOps : RPCOps {
@RPCReturnsObservables
fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
/** Returns the network parameters the node is operating under. */
val networkParameters: NetworkParameters
/**
* Returns [DataFeed] object containing information on currently scheduled parameters update (null if none are currently scheduled)
* and observable with future update events. Any update that occurs before the deadline automatically cancels the current one.
@ -434,7 +438,7 @@ fun CordaRPCOps.pendingFlowsCount(): DataFeed<Int, Pair<Int, Int>> {
}
}
}.subscribe()
if (completedFlowsCount == 0) {
if (pendingFlowsCount == 0) {
updates.onCompleted()
}
return DataFeed(pendingFlowsCount, updates)

View File

@ -1,18 +1,24 @@
plugins {
id 'com.github.johnrengelman.shadow' version '2.0.4'
}
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
description 'Corda deterministic JVM sandbox'
ext {
// Shaded version of ASM to avoid conflict with root project.
asm_version = '6.1.1'
}
configurations {
testCompile.extendsFrom shadow
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
shadow "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
shadow "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
shadow "org.slf4j:slf4j-api:$slf4j_version"
// ASM: byte code manipulation library
compile "org.ow2.asm:asm:$asm_version"
@ -20,30 +26,29 @@ dependencies {
compile "org.ow2.asm:asm-commons:$asm_version"
// Classpath scanner
compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version"
shadow "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version"
// Test utilities
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:$assertj_version"
testCompile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
}
jar.enabled = false
shadowJar {
baseName = "djvm"
classifier = ""
dependencies {
exclude(dependency('com.jcabi:.*:.*'))
exclude(dependency('org.apache.*:.*:.*'))
exclude(dependency('org.jetbrains.*:.*:.*'))
exclude(dependency('org.slf4j:.*:.*'))
exclude(dependency('io.github.lukehutch:.*:.*'))
}
baseName 'corda-djvm'
classifier ''
relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm'
artifacts {
shadow(tasks.shadowJar.archivePath) {
builtBy shadowJar
}
}
}
assemble.dependsOn shadowJar
artifacts {
publish shadowJar
}
publish {
dependenciesFrom configurations.shadow
disableDefaultJar true
name shadowJar.baseName
}

View File

@ -15,12 +15,10 @@ configurations {
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
compile "info.picocli:picocli:$picocli_version"
compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version"
compile project(path: ":djvm", configuration: "shadow")
// Deterministic runtime - used in whitelist generation

View File

@ -1,8 +1,5 @@
package net.corda.djvm.tools.cli
import net.corda.djvm.tools.Utilities.createCodePath
import net.corda.djvm.tools.Utilities.getFileNames
import net.corda.djvm.tools.Utilities.jarPath
import picocli.CommandLine.Command
import picocli.CommandLine.Parameters
import java.nio.file.Path

View File

@ -7,9 +7,6 @@ import net.corda.djvm.execution.*
import net.corda.djvm.references.ClassModule
import net.corda.djvm.source.ClassSource
import net.corda.djvm.source.SourceClassLoader
import net.corda.djvm.tools.Utilities.find
import net.corda.djvm.tools.Utilities.onEmpty
import net.corda.djvm.tools.Utilities.userClassPath
import net.corda.djvm.utilities.Discovery
import djvm.org.objectweb.asm.ClassReader
import picocli.CommandLine.Option

View File

@ -1,7 +1,6 @@
package net.corda.djvm.tools.cli
import net.corda.djvm.source.ClassSource
import net.corda.djvm.tools.Utilities.createCodePath
import picocli.CommandLine.Command
import picocli.CommandLine.Parameters
import java.nio.file.Files

View File

@ -1,9 +1,5 @@
package net.corda.djvm.tools.cli
import net.corda.djvm.tools.Utilities.baseName
import net.corda.djvm.tools.Utilities.createCodePath
import net.corda.djvm.tools.Utilities.getFiles
import net.corda.djvm.tools.Utilities.openOptions
import picocli.CommandLine.*
import java.nio.file.Files
import java.nio.file.Path

View File

@ -1,6 +1,5 @@
package net.corda.djvm.tools.cli
import net.corda.djvm.tools.Utilities.workingDirectory
import picocli.CommandLine.Command
import java.nio.file.Files

View File

@ -0,0 +1,104 @@
@file:JvmName("Utilities")
package net.corda.djvm.tools.cli
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
import java.lang.reflect.Modifier
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
/**
* Get the expanded file name of each path in the provided array.
*/
fun Array<Path>?.getFiles(map: (Path) -> Path = { it }) = (this ?: emptyArray()).map {
val pathString = it.toString()
val path = map(it)
when {
'/' in pathString || '\\' in pathString ->
throw Exception("Please provide a pathless file name")
pathString.endsWith(".java", true) -> path
else -> Paths.get("$path.java")
}
}
/**
* Get the string representation of each expanded file name in the provided array.
*/
fun Array<Path>?.getFileNames(map: (Path) -> Path = { it }) = this.getFiles(map).map {
it.toString()
}.toTypedArray()
/**
* Execute inlined action if the collection is empty.
*/
inline fun <T> List<T>.onEmpty(action: () -> Unit): List<T> {
if (!this.any()) {
action()
}
return this
}
/**
* Execute inlined action if the array is empty.
*/
inline fun <reified T> Array<T>?.onEmpty(action: () -> Unit): Array<T> {
return (this ?: emptyArray()).toList().onEmpty(action).toTypedArray()
}
/**
* Derive the set of [StandardOpenOption]'s to use for a file operation.
*/
fun openOptions(force: Boolean) = if (force) {
arrayOf(StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)
} else {
arrayOf(StandardOpenOption.CREATE_NEW)
}
/**
* Get the path of where any generated code will be placed. Create the directory if it does not exist.
*/
fun createCodePath(): Path {
return Paths.get("tmp", "net", "corda", "djvm").let {
Files.createDirectories(it)
}
}
/**
* Return the base name of a file (i.e., its name without extension)
*/
val Path.baseName: String
get() = this.fileName.toString()
.replaceAfterLast('.', "")
.removeSuffix(".")
/**
* The path of the executing JAR.
*/
val jarPath: String = object {}.javaClass.protectionDomain.codeSource.location.toURI().path
/**
* The path of the current working directory.
*/
val workingDirectory: Path = Paths.get(System.getProperty("user.dir"))
/**
* The class path for the current execution context.
*/
val userClassPath: String = System.getProperty("java.class.path")
/**
* Get a reference of each concrete class that implements interface or class [T].
*/
inline fun <reified T> find(scanSpec: String = "net/corda/djvm"): List<Class<*>> {
val references = mutableListOf<Class<*>>()
FastClasspathScanner(scanSpec)
.matchClassesImplementing(T::class.java) { clazz ->
if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) {
references.add(clazz)
}
}
.scan()
return references
}

View File

@ -20,6 +20,7 @@ interface Emitter {
/**
* Indication of whether or not the emitter performs instrumentation for tracing inside the sandbox.
*/
@JvmDefault
val isTracer: Boolean
get() = false

View File

@ -12,5 +12,6 @@ interface MemberInformation {
val className: String
val memberName: String
val signature: String
@JvmDefault
val reference: String get() = "$className.$memberName:$signature"
}

View File

@ -1,5 +1,7 @@
package net.corda.djvm.rewiring
import org.objectweb.asm.Type
/**
* A class or interface running in a Java application, together with its raw byte code representation and all references
* made from within the type.
@ -16,7 +18,7 @@ class LoadedClass(
* The name of the loaded type.
*/
val name: String
get() = type.name.replace('.', '/')
get() = Type.getInternalName(type)
override fun toString(): String {
return "Class(type=$name, size=${byteCode.bytes.size}, isModified=${byteCode.isModified})"

View File

@ -4,6 +4,7 @@ import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import org.objectweb.asm.Type
/**
* Class writer for sandbox execution, with configurable a [classLoader] to ensure correct deduction of the used class
@ -52,7 +53,7 @@ open class SandboxClassWriter(
do {
clazz = clazz.superclass
} while (!clazz.isAssignableFrom(class2))
clazz.name.replace('.', '/')
Type.getInternalName(clazz)
}
}
}

View File

@ -80,7 +80,7 @@ open class SourceClassLoader(
when {
!Files.exists(it) -> throw FileNotFoundException("File not found; $it")
Files.isDirectory(it) -> {
listOf(it.toURL()) + Files.list(it).filter { isJarFile(it) }.map { it.toURL() }.toList()
listOf(it.toURL()) + Files.list(it).filter(::isJarFile).map { it.toURL() }.toList()
}
Files.isReadable(it) && isJarFile(it) -> listOf(it.toURL())
else -> throw IllegalArgumentException("Expected JAR or class file, but found $it")

View File

@ -1,114 +0,0 @@
package net.corda.djvm.tools
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
import java.lang.reflect.Modifier
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
/**
* Various utility functions.
*/
@Suppress("unused")
object Utilities {
/**
* Get the expanded file name of each path in the provided array.
*/
fun Array<Path>?.getFiles(map: (Path) -> Path = { it }) = (this ?: emptyArray()).map {
val pathString = it.toString()
val path = map(it)
when {
'/' in pathString || '\\' in pathString ->
throw Exception("Please provide a pathless file name")
pathString.endsWith(".java", true) -> path
else -> Paths.get("$path.java")
}
}
/**
* Get the string representation of each expanded file name in the provided array.
*/
fun Array<Path>?.getFileNames(map: (Path) -> Path = { it }) = this.getFiles(map).map {
it.toString()
}.toTypedArray()
/**
* Execute inlined action if the collection is empty.
*/
inline fun <T> List<T>.onEmpty(action: () -> Unit): List<T> {
if (!this.any()) {
action()
}
return this
}
/**
* Execute inlined action if the array is empty.
*/
inline fun <reified T> Array<T>?.onEmpty(action: () -> Unit): Array<T> {
return (this ?: emptyArray()).toList().onEmpty(action).toTypedArray()
}
/**
* Derive the set of [StandardOpenOption]'s to use for a file operation.
*/
fun openOptions(force: Boolean) = if (force) {
arrayOf(StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)
} else {
arrayOf(StandardOpenOption.CREATE_NEW)
}
/**
* Get the path of where any generated code will be placed. Create the directory if it does not exist.
*/
fun createCodePath(): Path {
val root = Paths.get("tmp")
.resolve("net")
.resolve("corda")
.resolve("djvm")
Files.createDirectories(root)
return root
}
/**
* Return the base name of a file (i.e., its name without extension)
*/
val Path.baseName: String
get() = this.fileName.toString()
.replaceAfterLast('.', "")
.removeSuffix(".")
/**
* The path of the executing JAR.
*/
val jarPath: String = Utilities::class.java.protectionDomain.codeSource.location.toURI().path
/**
* The path of the current working directory.
*/
val workingDirectory: Path = Paths.get(System.getProperty("user.dir"))
/**
* The class path for the current execution context.
*/
val userClassPath: String = System.getProperty("java.class.path")
/**
* Get a reference of each concrete class that implements interface or class [T].
*/
inline fun <reified T> find(scanSpec: String = "net/corda/djvm"): List<Class<*>> {
val references = mutableListOf<Class<*>>()
FastClasspathScanner(scanSpec)
.matchClassesImplementing(T::class.java) { clazz ->
if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) {
references.add(clazz)
}
}
.scan()
return references
}
}

View File

@ -7,6 +7,7 @@ import java.lang.reflect.Modifier
* Find and instantiate types that implement a certain interface.
*/
object Discovery {
const val FORBIDDEN_CLASS_MASK = (Modifier.STATIC or Modifier.ABSTRACT)
/**
* Get an instance of each concrete class that implements interface or class [T].
@ -15,7 +16,7 @@ object Discovery {
val instances = mutableListOf<T>()
FastClasspathScanner("net/corda/djvm")
.matchClassesImplementing(T::class.java) { clazz ->
if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) {
if (clazz.modifiers and FORBIDDEN_CLASS_MASK == 0) {
try {
instances.add(clazz.newInstance())
} catch (exception: Throwable) {

View File

@ -4,7 +4,7 @@ class StrictFloat : Callable {
override fun call() {
val d = java.lang.Double.MIN_VALUE
val x = d / 2 * 2
assert(x.toString() == "0.0")
require(x.toString() == "0.0")
}
}

View File

@ -18,9 +18,10 @@ import net.corda.djvm.utilities.Discovery
import net.corda.djvm.validation.RuleValidator
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.Type
import java.lang.reflect.InvocationTargetException
open class TestBase {
abstract class TestBase {
companion object {
@ -38,8 +39,7 @@ open class TestBase {
/**
* Get the full name of type [T].
*/
inline fun <reified T> nameOf(prefix: String = "") =
"$prefix${T::class.java.name.replace('.', '/')}"
inline fun <reified T> nameOf(prefix: String = "") = "$prefix${Type.getInternalName(T::class.java)}"
}
@ -115,7 +115,7 @@ open class TestBase {
var thrownException: Throwable? = null
Thread {
try {
val pinnedTestClasses = pinnedClasses.map { it.name.replace('.', '/') }.toSet()
val pinnedTestClasses = pinnedClasses.map(Type::getInternalName).toSet()
val analysisConfiguration = AnalysisConfiguration(
whitelist = whitelist,
additionalPinnedClasses = pinnedTestClasses,

View File

@ -63,7 +63,7 @@ class SandboxExecutorTest : TestBase() {
val obj = Object()
val hash1 = obj.hashCode()
val hash2 = obj.hashCode()
assert(hash1 == hash2)
require(hash1 == hash2)
return Object().hashCode()
}
}

View File

@ -4,6 +4,7 @@ import net.corda.djvm.annotations.NonDeterministic
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.annotations.NotNull
import org.junit.Test
import org.objectweb.asm.Type
class MemberModuleTest {
@ -132,7 +133,7 @@ class MemberModuleTest {
}
private val java.lang.Class<*>.descriptor: String
get() = "L${name.replace('.', '/')};"
get() = Type.getDescriptor(this)
private fun member(member: String) =
MemberReference("", member, "")

View File

@ -4,6 +4,7 @@ import foo.bar.sandbox.Callable
import net.corda.djvm.TestBase
import net.corda.djvm.assertions.AssertionExtensions.assertThat
import org.junit.Test
import org.objectweb.asm.Type
import java.util.*
class ReferenceExtractorTest : TestBase() {
@ -32,7 +33,7 @@ class ReferenceExtractorTest : TestBase() {
@Test
fun `can find field references`() = validate<B> { context ->
assertThat(context.references)
.hasMember(B::class.java.name.replace('.', '/'), "foo", "Ljava/lang/String;")
.hasMember(Type.getInternalName(B::class.java), "foo", "Ljava/lang/String;")
}
class B {
@ -47,7 +48,7 @@ class ReferenceExtractorTest : TestBase() {
@Test
fun `can find class references`() = validate<C> { context ->
assertThat(context.references)
.hasClass(A::class.java.name.replace('.', '/'))
.hasClass(Type.getInternalName(A::class.java))
}
class C {

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<ThresholdFilter level="info"/>
<Appenders>
<Console name="Console-Appender" target="SYSTEM_OUT">
<PatternLayout pattern="%date %highlight{%level %c{1}.%method - %msg%n}{INFO=white,WARN=red,FATAL=bright red}"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console-Appender"/>
</Root>
</Loggers>
</Configuration>

View File

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<ThresholdFilter level="info"/>
<Appenders>
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
those that are older than 60 days, but keep the most recent 10 GB -->
<RollingFile name="RollingFile-Appender"
fileName="djvm.log"
filePattern="djvm.%date{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%date{ISO8601}{UTC}Z [%-5level] %c - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<DefaultRolloverStrategy min="1" max="10">
<Delete basePath="" maxDepth="1">
<IfFileName glob="djvm*.log.gz"/>
<IfLastModified age="60d">
<IfAny>
<IfAccumulatedFileSize exceeds="10 GB"/>
</IfAny>
</IfLastModified>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="RollingFile-Appender"/>
</Root>
</Loggers>
</Configuration>

View File

@ -6,6 +6,8 @@ release, see :doc:`upgrade-notes`.
Unreleased
----------
* Getter added to ``CordaRPCOps`` for the node's network parameters.
* The RPC client library now checks at startup whether the server is of the client libraries major version or higher.
Therefore to connect to a Corda 4 node you must use version 4 or lower of the library. This behaviour can be overridden
by specifying a lower number in the ``CordaRPCClientConfiguration`` class.

View File

@ -26,21 +26,8 @@ Before reading this page, you should be familiar with the :doc:`key concepts of
Internal APIs and stability guarantees
--------------------------------------
.. warning:: For Corda 1.0 we do not currently provide a stable wire protocol or support for database upgrades.
Additionally, the JSON format produced by the client-jackson module may change in future.
Therefore, you should not expect to be able to migrate persisted data from 1.0 to future versions.
Additionally, it may be necessary to recompile applications against future versions of the API until we begin offering
ABI stability as well. We plan to do this soon after the release of Corda 1.0.
Finally, please note that the 1.0 release has not yet been security audited. You should not run it in situations
where security is required.
Corda artifacts can be required from Java 9 Jigsaw modules.
From within a ``module-info.java``, you can reference one of the modules e.g., ``requires net.corda.core;``.
.. warning:: while Corda artifacts can be required from ``module-info.java`` files, they are still not proper Jigsaw modules,
because they rely on the automatic module mechanism and declare no module descriptors themselves. We plan to integrate Jigsaw more thoroughly in the future.
Corda makes certain commitments about what parts of the API will preserve backwards compatibility as they change and
which will not. Over time, more of the API will fall under the stability guarantees.
Corda stable modules
--------------------

View File

@ -115,6 +115,9 @@ absolute path to the node's base directory.
note that the host is the included as the advertised entry in the network map. As a result the value listed
here must be externally accessible when running nodes across a cluster of machines. If the provided host is unreachable,
the node will try to auto-discover its public one.
:additionalP2PAddresses: An array of additional host:port values, which will be included in the advertised NodeInfo in the network map in addition to the ``p2pAddress``.
Nodes can use this configuration option to advertise HA endpoints and aliases to external parties. If not specified the default value is an empty list.
:flowTimeout: When a flow implementing the ``TimedFlow`` interface does not complete in time, it is restarted from the
initial checkpoint. Currently only used for notarisation requests: if a notary replica dies while processing a notarisation request,

View File

@ -0,0 +1,90 @@
# CorDapp Minimum and Target Platform Version
## Overview
We want to give CorDapps the ability to specify which versions of the platform they support. This will make it easier for CorDapp developers to support multiple platform versions, and enable CorDapp developers to tweak behaviour and opt in to changes that might be breaking (e.g. sandboxing). Corda developers gain the ability to introduce changes to the implementation of the API that would otherwise break existing CorDapps.
This document proposes that CorDapps will have metadata associated with them specifying a minimum platform version and a target platform Version. The minimum platform version of a CorDapp would indicate that a Corda node would have to be running at least this version of the Corda platform in order to be able to run this CorDapp. The target platform version of a CorDapp would indicate that it was tested for this version of the Corda platform.
## Background
> Introduce target version and min platform version as app attributes
>
> This is probably as simple as a couple of keys in a MANIFEST.MF file.
> We should document what it means, make sure API implementations can always access the target version of the calling CorDapp (i.e. by examining the flow, doing a stack walk or using Reflection.getCallerClass()) and do a simple test of an API that acts differently depending on the target version of the app.
> We should also implement checking at CorDapp load time that min platform version <= current platform version.
([from CORDA-470](https://r3-cev.atlassian.net/browse/CORDA-470))
### Definitions
* *Platform version (Corda)* An integer representing the API version of the Corda platform
> It starts at 1 and will increment by exactly 1 for each release which changes any of the publicly exposed APIs in the entire platform. This includes public APIs on the node itself, the RPC system, messaging, serialisation, etc. API backwards compatibility will always be maintained, with the use of deprecation to migrate away from old APIs. In rare situations APIs may have to be removed, for example due to security issues. There is no relationship between the Platform Version and the release version - a change in the major, minor or patch values may or may not increase the Platform Version.
([from the docs](https://docs.corda.net/head/versioning.html#versioning)).
* *Platform version (Node)* The value of the Corda platform version that a node is running and advertising to the network.
* *Minimum platform version (Network)* The minimum platform version that the nodes must run in order to be able to join the network. Set by the network zone operator. The minimum platform version is distributed with the network parameters as `minimumPlatformVersion`.
([see docs:](https://docs.corda.net/network-map.html#network-parameters))
* *Target platform version (CorDapp)* Introduced in this document. Indicates that a CorDapp was tested with this version of the Corda Platform and should be run at this API level if possible.
* *Minimum platform version (CorDapp)* Introduced in this document. Indicates the minimum version of the Corda platform that a Corda Node has to run in order to be able to run a CorDapp.
## Goals
Define the semantics of target platform version and minimum platform version attributes for CorDapps, and the minimum platform version for the Corda network. Describe how target and platform versions would be specified by CorDapp developers. Define how these values can be accessed by the node and the CorDapp itself.
## Non-goals
In the future it might make sense to integrate the minimum and target versions into a Corda gradle plugin. Such a plugin is out of scope of this document.
## Timeline
This is intended as a long-term solution. The first iteration of the implementation will be part of platform version 4 and contain the minimum and target platform version.
## Requirements
* The CorDapp's minimum and target platform version must be accessible to nodes at CorDapp load time.
* At CorDapp load time there should be a check that the node's platform version is greater or equal to the CorDapp's Minimum Platform version.
* API implementations must be able to access the target version of the calling CorDapp.
* The node's platform version must be accessible to CorDapps.
* The CorDapp's target platform version must be accessible to the node when running CorDapps.
## Design
### Testing
When a new platform version is released, CorDapp developers can increase their CorDapp's target version and re-test their app. If the tests are successful, they can then release their CorDapp with the increased target version. This way they would opt-in to potentially breaking changes that were introduced in that version. If they choose to keep their current target version, their CorDapp will continue to work.
### Implications for platform developers
When new features or changes are introduced that require all nodes on the network to understand them (e.g. changes in the wire transaction format), they must be version-gated on the network level. This means that the new behaviour should only take effect if the minimum platform version of the network is equal to or greater than the version in which these changes were introduced. Failing that, the old behaviour must be used instead.
Changes that risk breaking apps must be gated on targetVersion>=X where X is the version where the change was made, and the old behaviour must be preserved if that condition isn't met.
## Technical Design
The minimum- and target platform version will be written to the manifest of the CorDapp's JAR, in fields called `Min-Platform-Version` and `Target-Platform-Version`.
The node's CorDapp loader reads these values from the manifest when loading the CorDapp. If the CorDapp's minimum platform version is greater than the node's platform version, the node will not load the CorDapp and log a warning. The CorDapp loader sets the minimum and target version in `net.corda.core.cordapp.Cordapp`, which can be obtained via the `CorDappContext` from the service hub.
To make APIs caller-sensitive in cases where the service hub is not available a different approach has to be used. It would possible to do a stack walk, and parse the manifest of each class on the stack to determine if it belongs to a CorDapp, and if yes, what its target version is. Alternatively, the mapping of classes to `Cordapp`s obtained by the CorDapp loader could be stored in a global singleton. This singleton would expose a lambda returning the current CorDapp's version information (e.g. `() -> Cordapp.Info`).
Let's assume that we want to change `TimeWindow.Between` to make it inclusive, i.e. change `contains(instant: Instant) = instant >= fromTime && instant < untilTime` to `contains(instant: Instant) = instant >= fromTime && instant <= untilTime`. However, doing so will break existing CorDapps. We could then version-guard the change such that the new behaviour is only used if the target version of the CorDapp calling `contains` is equal to or greater than the platform version that contains this change. It would look similar to this:
```
fun contains(instant: Instant) {
if (CorDappVersionResolver.resolve().targetVersion > 42) {
return instant >= fromTime && instant <= untilTime
} else {
return instant >= fromTime && instant < untilTime
}
```
Version-gating API changes when the service hub is available would look similar to the above example, in that case the service hub's CorDapp provider would be used to determine if this code is being called from a CorDapp and to obtain its target version information.

View File

@ -47,22 +47,74 @@ Command-line options
~~~~~~~~~~~~~~~~~~~~
The node can optionally be started with the following command-line options:
* ``--base-directory``: The node working directory where all the files are kept (default: ``.``)
* ``--bootstrap-raft-cluster``: Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer
addresses), acting as a seed for other nodes to join the cluster
* ``--config-file``: The path to the config file (default: ``node.conf``)
* ``--help``
* ``--initial-registration``: Start initial node registration with Corda network to obtain certificate from the permissioning
server
* ``--just-generate-node-info``: Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then
quit
* ``--log-to-console``: If set, prints logging to the console as well as to a file
* ``--logging-level <[ERROR,WARN,INFO, DEBUG,TRACE]>``: Enable logging at this level and higher (default: INFO)
* ``--network-root-truststore``: Network root trust store obtained from network operator
* ``--network-root-truststore-password``: Network root trust store password obtained from network operator
* ``--no-local-shell``: Do not start the embedded shell locally
* ``--sshd``: Enables SSHD server for node administration
* ``--version``: Print the version and exit
* ``--base-directory``, ``-b``: The node working directory where all the files are kept (default: ``.``).
* ``--bootstrap-raft-cluster``: Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer
addresses), acting as a seed for other nodes to join the cluster.
* ``--clear-network-map-cache``, ``-c``: Clears local copy of network map, on node startup it will be restored from server or file system.
* ``--config-file``, ``-f``: The path to the config file. Defaults to ``node.conf``.
* ``--dev-mode``, ``-d``: Runs the node in developer mode. Unsafe in production. Defaults to true on MacOS and desktop versions of Windows. False otherwise.
* ``--help``, ``-h``: Displays the help message and exits.
* ``--initial-registration``: Start initial node registration with Corda network to obtain certificate from the permissioning
server.
* ``--install-shell-extensions``: Installs an alias and auto-completion for users of ``bash`` or ``zsh``. See below for more information.
* ``--just-generate-node-info``: Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then
quit.
* ``--just-generate-rpc-ssl-settings``: Generate the ssl keystore and truststore for a secure RPC connection.
* ``--log-to-console``, ``--verbose``, ``-v``: If set, prints logging to the console as well as to a file.
* ``--logging-level <[ERROR,WARN,INFO,DEBUG,TRACE]>``: Enable logging at this level and higher. Defaults to INFO.
* ``--network-root-truststore``, ``-t``: Network root trust store obtained from network operator.
* ``--network-root-truststore-password``, ``-p``: Network root trust store password obtained from network operator.
* ``--no-local-shell``, ``-n``: Do not start the embedded shell locally.
* ``--on-unknown-config-keys <[FAIL,WARN,INFO]>``: How to behave on unknown node configuration. Defaults to FAIL.
* ``--sshd``: Enables SSH server for node administration.
* ``--sshd-port``: Sets the port for the SSH server. If not supplied and SSH server is enabled, the port defaults to 2222.
* ``--version``, ``-V``: Prints the version and exits.
.. _installing-shell-extensions:
Installing shell extensions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Users of ``bash`` or ``zsh`` can install an alias and command line completion for Corda. Run:
.. code-block:: shell
java -jar corda.jar --install-shell-extensions
Then, either restart your shell, or for ``bash`` users run:
.. code-block:: shell
. ~/.bashrc
Or, for ``zsh`` run:
.. code-block:: shell
. ~/.zshrc
You will now be able to run a Corda node from anywhere by running the following:
.. code-block:: shell
corda --<option>
Upgrading shell extensions
~~~~~~~~~~~~~~~~~~~~~~~~~~
Once the shell extensions have been installed, you can upgrade them in one of two ways.
1) Overwrite the existing ``corda.jar`` with the newer version. The next time you run Corda, it will automatically update
the completion file. Either restart the shell or see :ref:`above<installing-shell-extensions>` for instructions
on making the changes take effect immediately.
2) If you wish to use a new ``corda.jar`` from a different directory, navigate to that directory and run:
.. code-block:: shell
java -jar corda.jar
Which will update the ``corda`` alias to point to the new location, and update command line completion functionality. Either
restart the shell or see :ref:`above<installing-shell-extensions>` for instructions on making the changes take effect immediately.
.. _enabling-remote-debugging:

View File

@ -49,7 +49,6 @@ import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.messaging.P2PMessagingClient
import net.corda.node.services.network.NetworkMapCacheImpl
import net.corda.node.services.network.NetworkMapUpdater
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.*
@ -120,8 +119,7 @@ class FlowWorkerServiceHub(override val configuration: NodeConfiguration, overri
identityService.database = database
}
private val persistentNetworkMapCache = PersistentNetworkMapCache(database, myInfo.legalIdentities[0].name)
override val networkMapCache = NetworkMapCacheImpl(persistentNetworkMapCache, identityService, database).tokenize()
override val networkMapCache = PersistentNetworkMapCache(database, identityService, myInfo.legalIdentities[0].name)
private val checkpointStorage = DBCheckpointStorage()
@Suppress("LeakingThis")
override val validatedTransactions: WritableTransactionStorage = DBTransactionStorage(configuration.transactionCacheSizeBytes, database).tokenize()
@ -370,10 +368,9 @@ class FlowWorkerServiceHub(override val configuration: NodeConfiguration, overri
database.startHikariPool(configuration.dataSourceProperties, configuration.database, schemas)
identityService.start(trustRoot, listOf(myInfo.legalIdentitiesAndCerts.first().certificate, nodeCa))
persistentNetworkMapCache.start(networkParameters.notaries)
database.transaction {
networkMapCache.start()
networkMapCache.start(networkParameters.notaries)
}
identityService.ourNames = myInfo.legalIdentities.map { it.name }.toSet()

View File

@ -20,6 +20,7 @@ import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.sign
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.*
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
@ -69,6 +70,8 @@ class CordaRpcWorkerOps(
private val flowReplyStateMachineRunIdMap = ConcurrentHashMap<Trace.InvocationId, SettableFuture<StateMachineRunId>>()
private val flowReplyResultMap = ConcurrentHashMap<Trace.InvocationId, OpenFuture<Any?>>()
override val networkParameters: NetworkParameters = services.networkParameters
fun start() {
session = artemisClient.start().session
producer = session.createProducer()

View File

@ -84,8 +84,7 @@ class RpcWorkerServiceHub(override val configuration: NodeConfiguration, overrid
identityService.database = database
}
private val persistentNetworkMapCache = PersistentNetworkMapCache(database, myInfo.legalIdentities[0].name)
override val networkMapCache = NetworkMapCacheImpl(persistentNetworkMapCache, identityService, database)
override val networkMapCache = PersistentNetworkMapCache(database, identityService, myInfo.legalIdentities[0].name)
@Suppress("LeakingThis")
override val validatedTransactions: WritableTransactionStorage = DBTransactionStorage(configuration.transactionCacheSizeBytes, database)
private val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
@ -218,13 +217,12 @@ class RpcWorkerServiceHub(override val configuration: NodeConfiguration, overrid
database.startHikariPool(configuration.dataSourceProperties, configuration.database, schemas)
identityService.start(trustRoot, listOf(myInfo.legalIdentitiesAndCerts.first().certificate, nodeCa))
persistentNetworkMapCache.start(networkParameters.notaries)
runOnStop += { rpcOps.shutdown() }
rpcOps.start()
database.transaction {
networkMapCache.start()
networkMapCache.start(networkParameters.notaries)
networkMapCache.addNode(myInfo)
}

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -6,7 +6,6 @@ import net.corda.core.messaging.MessageRecipientGroup
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.NetworkHostAndPort
import org.apache.activemq.artemis.api.core.Message
import org.apache.activemq.artemis.api.core.SimpleString
import java.security.PublicKey
@ -77,9 +76,7 @@ class ArtemisMessagingComponent {
val queueName: String
}
interface ArtemisPeerAddress : ArtemisAddress, SingleMessageRecipient {
val hostAndPort: NetworkHostAndPort
}
interface ArtemisPeerAddress : ArtemisAddress, SingleMessageRecipient
/**
* This is the class used to implement [SingleMessageRecipient], for now. Note that in future this class
@ -90,12 +87,11 @@ class ArtemisMessagingComponent {
* an advertised service's queue.
*
* @param queueName The name of the queue this address is associated with.
* @param hostAndPort The address of the node.
*/
@CordaSerializable
data class NodeAddress(override val queueName: String, override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress {
constructor(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort) :
this("$PEERS_PREFIX${peerIdentity.toStringShort()}", hostAndPort)
data class NodeAddress(override val queueName: String) : ArtemisPeerAddress {
constructor(peerIdentity: PublicKey) :
this("$PEERS_PREFIX${peerIdentity.toStringShort()}")
}
/**

View File

@ -1,4 +1,5 @@
package net.corda.nodeapi.internal
// TODO: Add to Corda node.conf to allow customisation
const val NODE_INFO_DIRECTORY = "additional-node-infos"
const val NODE_INFO_DIRECTORY = "additional-node-infos"
const val PLATFORM_VERSION = 4

View File

@ -4,16 +4,13 @@ import io.netty.channel.EventLoopGroup
import io.netty.channel.nio.NioEventLoopGroup
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.VisibleForTesting
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.ArtemisMessagingClient
import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_USER
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress
import net.corda.nodeapi.internal.ArtemisSessionProvider
import net.corda.nodeapi.internal.bridging.AMQPBridgeManager.AMQPBridge.Companion.getBridgeName
import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
@ -41,7 +38,7 @@ import kotlin.concurrent.withLock
class AMQPBridgeManager(config: MutualSslConfiguration, socksProxyConfig: SocksProxyConfig? = null, maxMessageSize: Int, private val artemisMessageClientFactory: () -> ArtemisSessionProvider) : BridgeManager {
private val lock = ReentrantLock()
private val bridgeNameToBridgeMap = mutableMapOf<String, AMQPBridge>()
private val queueNamesToBridgesMap = mutableMapOf<String, MutableList<AMQPBridge>>()
private class AMQPConfigurationImpl private constructor(override val keyStore: CertificateStore,
override val trustStore: CertificateStore,
@ -68,14 +65,13 @@ class AMQPBridgeManager(config: MutualSslConfiguration, socksProxyConfig: SocksP
* If the delivery fails the session is rolled back to prevent loss of the message. This may cause duplicate delivery,
* however Artemis and the remote Corda instanced will deduplicate these messages.
*/
private class AMQPBridge(private val queueName: String,
private val target: NetworkHostAndPort,
private class AMQPBridge(val queueName: String,
val targets: List<NetworkHostAndPort>,
private val legalNames: Set<CordaX500Name>,
private val amqpConfig: AMQPConfiguration,
sharedEventGroup: EventLoopGroup,
private val artemis: ArtemisSessionProvider) {
companion object {
fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort"
private val log = contextLogger()
}
@ -83,8 +79,7 @@ class AMQPBridgeManager(config: MutualSslConfiguration, socksProxyConfig: SocksP
val oldMDC = MDC.getCopyOfContextMap()
try {
MDC.put("queueName", queueName)
MDC.put("target", target.toString())
MDC.put("bridgeName", bridgeName)
MDC.put("targets", targets.joinToString(separator = ";") { it.toString() })
MDC.put("legalNames", legalNames.joinToString(separator = ";") { it.toString() })
MDC.put("maxMessageSize", amqpConfig.maxMessageSize.toString())
block()
@ -103,8 +98,7 @@ class AMQPBridgeManager(config: MutualSslConfiguration, socksProxyConfig: SocksP
private fun logWarnWithMDC(msg: String) = withMDC { log.warn(msg) }
val amqpClient = AMQPClient(listOf(target), legalNames, amqpConfig, sharedThreadPool = sharedEventGroup)
val bridgeName: String get() = getBridgeName(queueName, target)
val amqpClient = AMQPClient(targets, legalNames, amqpConfig, sharedThreadPool = sharedEventGroup)
private val lock = ReentrantLock() // lock to serialise session level access
private var session: ClientSession? = null
private var consumer: ClientConsumer? = null
@ -212,39 +206,37 @@ class AMQPBridgeManager(config: MutualSslConfiguration, socksProxyConfig: SocksP
}
}
private fun gatherAddresses(node: NodeInfo): List<ArtemisMessagingComponent.NodeAddress> {
return node.legalIdentitiesAndCerts.map { ArtemisMessagingComponent.NodeAddress(it.party.owningKey, node.addresses[0]) }
}
override fun deployBridge(queueName: String, target: NetworkHostAndPort, legalNames: Set<CordaX500Name>) {
if (bridgeExists(getBridgeName(queueName, target))) {
return
}
val newBridge = AMQPBridge(queueName, target, legalNames, amqpConfig, sharedEventLoopGroup!!, artemis!!)
lock.withLock {
bridgeNameToBridgeMap[newBridge.bridgeName] = newBridge
override fun deployBridge(queueName: String, targets: List<NetworkHostAndPort>, legalNames: Set<CordaX500Name>) {
val newBridge = lock.withLock {
val bridges = queueNamesToBridgesMap.getOrPut(queueName) { mutableListOf() }
for (target in targets) {
if (bridges.any { it.targets.contains(target) }) {
return
}
}
val newBridge = AMQPBridge(queueName, targets, legalNames, amqpConfig, sharedEventLoopGroup!!, artemis!!)
bridges += newBridge
newBridge
}
newBridge.start()
}
override fun destroyBridges(node: NodeInfo) {
override fun destroyBridge(queueName: String, targets: List<NetworkHostAndPort>) {
lock.withLock {
gatherAddresses(node).forEach {
val bridge = bridgeNameToBridgeMap.remove(getBridgeName(it.queueName, it.hostAndPort))
bridge?.stop()
val bridges = queueNamesToBridgesMap[queueName] ?: mutableListOf()
for (target in targets) {
val bridge = bridges.firstOrNull { it.targets.contains(target) }
if (bridge != null) {
bridges -= bridge
if (bridges.isEmpty()) {
queueNamesToBridgesMap.remove(queueName)
}
bridge.stop()
}
}
}
}
override fun destroyBridge(queueName: String, hostAndPort: NetworkHostAndPort) {
lock.withLock {
val bridge = bridgeNameToBridgeMap.remove(getBridgeName(queueName, hostAndPort))
bridge?.stop()
}
}
override fun bridgeExists(bridgeName: String): Boolean = lock.withLock { bridgeNameToBridgeMap.containsKey(bridgeName) }
override fun start() {
sharedEventLoopGroup = NioEventLoopGroup(NUM_BRIDGE_THREADS)
val artemis = artemisMessageClientFactory()
@ -256,13 +248,13 @@ class AMQPBridgeManager(config: MutualSslConfiguration, socksProxyConfig: SocksP
override fun close() {
lock.withLock {
for (bridge in bridgeNameToBridgeMap.values) {
for (bridge in queueNamesToBridgesMap.values.flatten()) {
bridge.stop()
}
sharedEventLoopGroup?.shutdownGracefully()
sharedEventLoopGroup?.terminationFuture()?.sync()
sharedEventLoopGroup = null
bridgeNameToBridgeMap.clear()
queueNamesToBridgesMap.clear()
artemis?.stop()
}
}

View File

@ -158,7 +158,7 @@ class BridgeControlListener(val config: MutualSslConfiguration,
return
}
for (outQueue in controlMessage.sendQueues) {
bridgeManager.deployBridge(outQueue.queueName, outQueue.targets.first(), outQueue.legalNames.toSet())
bridgeManager.deployBridge(outQueue.queueName, outQueue.targets, outQueue.legalNames.toSet())
}
val wasActive = active
validInboundQueues.addAll(controlMessage.inboxQueues)
@ -174,14 +174,14 @@ class BridgeControlListener(val config: MutualSslConfiguration,
log.error("Invalid queue names in control message $controlMessage")
return
}
bridgeManager.deployBridge(controlMessage.bridgeInfo.queueName, controlMessage.bridgeInfo.targets.first(), controlMessage.bridgeInfo.legalNames.toSet())
bridgeManager.deployBridge(controlMessage.bridgeInfo.queueName, controlMessage.bridgeInfo.targets, controlMessage.bridgeInfo.legalNames.toSet())
}
is BridgeControl.Delete -> {
if (!controlMessage.bridgeInfo.queueName.startsWith(PEERS_PREFIX)) {
log.error("Invalid queue names in control message $controlMessage")
return
}
bridgeManager.destroyBridge(controlMessage.bridgeInfo.queueName, controlMessage.bridgeInfo.targets.first())
bridgeManager.destroyBridge(controlMessage.bridgeInfo.queueName, controlMessage.bridgeInfo.targets)
}
}
}

View File

@ -2,7 +2,6 @@ package net.corda.nodeapi.internal.bridging
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.VisibleForTesting
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.NetworkHostAndPort
/**
@ -10,13 +9,9 @@ import net.corda.core.utilities.NetworkHostAndPort
*/
@VisibleForTesting
interface BridgeManager : AutoCloseable {
fun deployBridge(queueName: String, target: NetworkHostAndPort, legalNames: Set<CordaX500Name>)
fun deployBridge(queueName: String, targets: List<NetworkHostAndPort>, legalNames: Set<CordaX500Name>)
fun destroyBridges(node: NodeInfo)
fun destroyBridge(queueName: String, hostAndPort: NetworkHostAndPort)
fun bridgeExists(bridgeName: String): Boolean
fun destroyBridge(queueName: String, targets: List<NetworkHostAndPort>)
fun start()

View File

@ -71,6 +71,7 @@ dependencies {
compile project(":confidential-identities")
compile project(':client:rpc')
compile project(':tools:shell')
compile project(':tools:cliutils')
compile "net.corda.plugins:cordform-common:$gradle_plugins_version"
@ -107,9 +108,6 @@ dependencies {
exclude group: 'org.apache.qpid', module: 'proton-j'
}
// JAnsi: for drawing things to the terminal in nicely coloured ways.
compile "org.fusesource.jansi:jansi:$jansi_version"
// Manifests: for reading stuff from the manifest file
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"

View File

@ -9,7 +9,7 @@ apply plugin: 'com.jfrog.artifactory'
description 'Corda Enterprise'
configurations {
runtimeArtifacts.extendsFrom runtime
runtimeArtifacts.extendsFrom runtimeClasspath
capsuleRuntime
}
@ -31,18 +31,20 @@ ext {
sourceCompatibility = 1.6
targetCompatibility = 1.6
task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) {
archiveName "corda-enterprise-${corda_release_version}.jar"
jar.enabled = false
task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').tasks.jar) {
applicationClass 'net.corda.node.Corda'
archiveName "corda-enterprise-${corda_release_version}.jar"
applicationSource = files(
project(':node').configurations.runtime,
project(':node').jar,
project(':node').sourceSets.main.java.outputDir.toString() + '/CordaCaplet.class',
project(':node').sourceSets.main.java.outputDir.toString() + '/CordaCaplet$1.class',
"$rootDir/config/dev/log4j2.xml",
"$rootDir/node/build/resources/main/reference.conf"
project(':node').configurations.runtimeClasspath,
project(':node').tasks.jar,
project(':node').sourceSets.main.java.outputDir.toString() + '/CordaCaplet.class',
project(':node').sourceSets.main.java.outputDir.toString() + '/CordaCaplet$1.class',
project(':node').buildDir.toString() + '/resources/main/reference.conf',
"$rootDir/config/dev/log4j2.xml",
'NOTICE' // Copy CDDL notice
)
from 'NOTICE' // Copy CDDL notice
from configurations.capsuleRuntime.files.collect { zipTree(it) }
capsuleManifest {
@ -65,12 +67,12 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) {
}
}
build.dependsOn buildCordaJAR
assemble.dependsOn buildCordaJAR
artifacts {
runtimeArtifacts buildCordaJAR
publish buildCordaJAR {
classifier ""
classifier ''
}
}

View File

@ -270,7 +270,7 @@ class AMQPBridgeTest {
if (sourceQueueName != null) {
// Local queue for outgoing messages
artemis.session.createQueue(sourceQueueName, RoutingType.ANYCAST, sourceQueueName, true)
bridgeManager.deployBridge(sourceQueueName, amqpAddress, setOf(BOB.name))
bridgeManager.deployBridge(sourceQueueName, listOf(amqpAddress), setOf(BOB.name))
}
return Triple(artemisServer, artemisClient, bridgeManager)
}

View File

@ -10,7 +10,9 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds
import net.corda.node.internal.configureDatabase
import net.corda.node.services.config.*
import net.corda.node.services.network.NetworkMapCacheImpl
import net.corda.node.services.config.FlowTimeoutConfiguration
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
@ -66,7 +68,7 @@ class ArtemisMessagingTest {
private var messagingClient: P2PMessagingClient? = null
private var messagingServer: ArtemisMessagingServer? = null
private lateinit var networkMapCache: NetworkMapCacheImpl
private lateinit var networkMapCache: PersistentNetworkMapCache
@Before
fun setUp() {
@ -91,8 +93,7 @@ class ArtemisMessagingTest {
}
LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeInternalTestDataSourceProperties(configSupplier = { ConfigFactory.empty() }), DatabaseConfig(runMigration = true), { null }, { null })
val persistentNetworkMapCache = PersistentNetworkMapCache(database, ALICE_NAME).apply { start(emptyList()) }
networkMapCache = NetworkMapCacheImpl(persistentNetworkMapCache, rigorousMock(), database).apply { start() }
networkMapCache = PersistentNetworkMapCache(database, rigorousMock(), ALICE_NAME).apply { start(emptyList()) }
}
@After

View File

@ -3,10 +3,11 @@ package net.corda.node.services.network
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.configureDatabase
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.*
import net.corda.testing.internal.IntegrationTest
@ -34,16 +35,17 @@ class PersistentNetworkMapCacheTest : IntegrationTest() {
val testSerialization = SerializationEnvironmentRule()
private var portCounter = 1000
//Enterprise only - objects created in the setup method, below initialized with dummy values to avoid need for nullable type declaration
private var database = CordaPersistence(DatabaseConfig(), emptySet())
private var charlieNetMapCache = PersistentNetworkMapCache(database, CHARLIE.name)
private var charlieNetMapCache = PersistentNetworkMapCache(database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate), CHARLIE.name)
@Before()
fun setup() {
//Enterprise only - for test in database mode ensure the remote database is setup before creating CordaPersistence
super.setUp()
database = configureDatabase(makeTestDataSourceProperties(CHARLIE_NAME.toDatabaseSchemaName()), makeTestDatabaseProperties(CHARLIE_NAME.toDatabaseSchemaName()), { null }, { null })
charlieNetMapCache = PersistentNetworkMapCache(database, CHARLIE.name)
charlieNetMapCache = PersistentNetworkMapCache(database, InMemoryIdentityService(trustRoot = DEV_ROOT_CA.certificate), CHARLIE.name)
}
@After

View File

@ -0,0 +1,70 @@
package net.corda.services.messaging
import com.typesafe.config.ConfigValueFactory
import junit.framework.TestCase.assertEquals
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.withoutIssuer
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_BANK_B_NAME
import net.corda.testing.core.expect
import net.corda.testing.core.expectEvents
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import org.junit.Test
import java.util.*
class AdditionP2PAddressModeTest {
private val portAllocation = PortAllocation.Incremental(27182)
@Test
fun `runs nodes with one configured to use additionalP2PAddresses`() {
val testUser = User("test", "test", setOf(all()))
driver(DriverParameters(startNodesInProcess = true, inMemoryDB = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
val mainAddress = portAllocation.nextHostAndPort().toString()
val altAddress = portAllocation.nextHostAndPort().toString()
val haConfig = mutableMapOf<String, Any?>()
haConfig["detectPublicIp"] = false
haConfig["p2pAddress"] = mainAddress //advertise this as primary
haConfig["messagingServerAddress"] = altAddress // but actually host on the alternate address
haConfig["messagingServerExternal"] = false
haConfig["additionalP2PAddresses"] = ConfigValueFactory.fromIterable(listOf(altAddress)) // advertise this secondary address
val (nodeA, nodeB) = listOf(
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(testUser), customOverrides = haConfig),
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(testUser), customOverrides = mapOf("p2pAddress" to portAllocation.nextHostAndPort().toString()))
).map { it.getOrThrow() }
val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map {
val client = CordaRPCClient(it.rpcAddress)
client.start(testUser.username, testUser.password).proxy
}
val nodeBVaultUpdates = nodeBRpc.vaultTrack(Cash.State::class.java).updates
val issueRef = OpaqueBytes.of(0.toByte())
nodeARpc.startFlowDynamic(
CashIssueAndPaymentFlow::class.java,
DOLLARS(1234),
issueRef,
nodeB.nodeInfo.legalIdentities.get(0),
true,
defaultNotaryIdentity
).returnValue.getOrThrow()
nodeBVaultUpdates.expectEvents {
expect { update ->
println("Bob got vault update of $update")
val amount: Amount<Issued<Currency>> = update.produced.first().state.data.amount
assertEquals(1234.DOLLARS, amount.withoutIssuer())
}
}
}
}
}

View File

@ -26,16 +26,8 @@ public class CordaCaplet extends Capsule {
private Config parseConfigFile(List<String> args) {
String baseDirOption = getOption(args, "--base-directory");
// Ensure consistent behaviour with NodeArgsParser.kt, see CORDA-1598.
if (null == baseDirOption || baseDirOption.isEmpty()) {
baseDirOption = getOption(args, "-base-directory");
}
this.baseDir = Paths.get((baseDirOption == null) ? "." : baseDirOption).toAbsolutePath().normalize().toString();
String config = getOption(args, "--config-file");
// Same as for baseDirOption.
if (null == config || config.isEmpty()) {
config = getOption(args, "-config-file");
}
File configFile = (config == null) ? new File(baseDir, "node.conf") : new File(config);
try {
ConfigParseOptions parseOptions = ConfigParseOptions.defaults().setAllowMissing(false);

View File

@ -3,14 +3,12 @@
package net.corda.node
import net.corda.core.crypto.CordaSecurityProvider
import net.corda.core.crypto.Crypto
import kotlin.system.exitProcess
import net.corda.cliutils.start
import net.corda.node.internal.EnterpriseNode
fun main(args: Array<String>) {
// Pass the arguments to the Node factory. In the Enterprise edition, this line is modified to point to a subclass.
// It will exit the process in case of startup failure and is not intended to be used by embedders. If you want
// to embed Node in your own container, instantiate it directly and set up the configuration objects yourself.
exitProcess(if (EnterpriseNode.Startup(args).run()) 0 else 1)
EnterpriseNode.Startup().start(args)
}

View File

@ -1,149 +0,0 @@
package net.corda.node
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import joptsimple.OptionSet
import joptsimple.util.EnumConverter
import joptsimple.util.PathConverter
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.utilities.Try
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.node.utilities.AbstractArgsParser
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import org.slf4j.event.Level
import java.nio.file.Path
import java.nio.file.Paths
// NOTE: Do not use any logger in this class as args parsing is done before the logger is setup.
class NodeArgsParser : AbstractArgsParser<CmdLineOptions>() {
// The intent of allowing a command line configurable directory and config path is to allow deployment flexibility.
// Other general configuration should live inside the config file unless we regularly need temporary overrides on the command line
private val baseDirectoryArg = optionParser
.accepts("base-directory", "The node working directory where all the files are kept")
.withRequiredArg()
.withValuesConvertedBy(PathConverter())
.defaultsTo(Paths.get("."))
private val configFileArg = optionParser
.accepts("config-file", "The path to the config file")
.withRequiredArg()
.defaultsTo("node.conf")
private val loggerLevel = optionParser
.accepts("logging-level", "Enable logging at this level and higher")
.withRequiredArg()
.withValuesConvertedBy(object : EnumConverter<Level>(Level::class.java) {})
.defaultsTo(Level.INFO)
private val logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.")
private val sshdServerArg = optionParser.accepts("sshd", "Enables SSHD server for node administration.")
private val noLocalShellArg = optionParser.accepts("no-local-shell", "Do not start the embedded shell locally.")
private val isRegistrationArg = optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.")
private val networkRootTrustStorePathArg = optionParser.accepts("network-root-truststore", "Network root trust store obtained from network operator.")
.withRequiredArg()
.withValuesConvertedBy(PathConverter())
.defaultsTo((Paths.get("certificates") / "network-root-truststore.jks"))
private val networkRootTrustStorePasswordArg = optionParser.accepts("network-root-truststore-password", "Network root trust store password obtained from network operator.")
.withRequiredArg()
private val unknownConfigKeysPolicy = optionParser.accepts("on-unknown-config-keys", "How to behave on unknown node configuration.")
.withRequiredArg()
.withValuesConvertedBy(object : EnumConverter<UnknownConfigKeysPolicy>(UnknownConfigKeysPolicy::class.java) {})
.defaultsTo(UnknownConfigKeysPolicy.FAIL)
private val devModeArg = optionParser.accepts("dev-mode", "Run the node in developer mode. Unsafe for production.")
private val isVersionArg = optionParser.accepts("version", "Print the version and exit")
private val justGenerateNodeInfoArg = optionParser.accepts("just-generate-node-info",
"Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then quit")
private val justGenerateRpcSslCertsArg = optionParser.accepts("just-generate-rpc-ssl-settings",
"Generate the ssl keystore and truststore for a secure RPC connection.")
private val bootstrapRaftClusterArg = optionParser.accepts("bootstrap-raft-cluster", "Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer addresses), acting as a seed for other nodes to join the cluster.")
private val clearNetworkMapCache = optionParser.accepts("clear-network-map-cache", "Clears local copy of network map, on node startup it will be restored from server or file system.")
override fun doParse(optionSet: OptionSet): CmdLineOptions {
require(optionSet.nonOptionArguments().isEmpty()) { "Unrecognized argument(s): ${optionSet.nonOptionArguments().joinToString(separator = ", ")}"}
require(!optionSet.has(baseDirectoryArg) || !optionSet.has(configFileArg)) {
"${baseDirectoryArg.options()[0]} and ${configFileArg.options()[0]} cannot be specified together"
}
// Workaround for javapackager polluting cwd: restore it from system property set by launcher.
val baseDirectory = System.getProperty("corda.launcher.cwd")?.let { Paths.get(it) }
?: optionSet.valueOf(baseDirectoryArg)
.normalize()
.toAbsolutePath()
val configFilePath = Paths.get(optionSet.valueOf(configFileArg))
val configFile = if (configFilePath.isAbsolute) configFilePath else baseDirectory / configFilePath.toString()
val loggingLevel = optionSet.valueOf(loggerLevel)
val logToConsole = optionSet.has(logToConsoleArg)
val isRegistration = optionSet.has(isRegistrationArg)
val isVersion = optionSet.has(isVersionArg)
val noLocalShell = optionSet.has(noLocalShellArg)
val sshdServer = optionSet.has(sshdServerArg)
val justGenerateNodeInfo = optionSet.has(justGenerateNodeInfoArg)
val justGenerateRpcSslCerts = optionSet.has(justGenerateRpcSslCertsArg)
val bootstrapRaftCluster = optionSet.has(bootstrapRaftClusterArg)
val networkRootTrustStorePath = optionSet.valueOf(networkRootTrustStorePathArg)
val networkRootTrustStorePassword = optionSet.valueOf(networkRootTrustStorePasswordArg)
val unknownConfigKeysPolicy = optionSet.valueOf(unknownConfigKeysPolicy)
val devMode = optionSet.has(devModeArg)
val clearNetworkMapCache = optionSet.has(clearNetworkMapCache)
val registrationConfig = if (isRegistration) {
requireNotNull(networkRootTrustStorePassword) { "Network root trust store password must be provided in registration mode using --network-root-truststore-password." }
require(networkRootTrustStorePath.exists()) { "Network root trust store path: '$networkRootTrustStorePath' doesn't exist" }
NodeRegistrationOption(networkRootTrustStorePath, networkRootTrustStorePassword)
} else {
null
}
return CmdLineOptions(baseDirectory,
configFile,
loggingLevel,
logToConsole,
registrationConfig,
isVersion,
noLocalShell,
sshdServer,
justGenerateNodeInfo,
justGenerateRpcSslCerts,
bootstrapRaftCluster,
unknownConfigKeysPolicy,
devMode,
clearNetworkMapCache)
}
}
data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String)
data class CmdLineOptions(val baseDirectory: Path,
val configFile: Path,
val loggingLevel: Level,
val logToConsole: Boolean,
val nodeRegistrationOption: NodeRegistrationOption?,
val isVersion: Boolean,
val noLocalShell: Boolean,
val sshdServer: Boolean,
val justGenerateNodeInfo: Boolean,
val justGenerateRpcSslCerts: Boolean,
val bootstrapRaftCluster: Boolean,
val unknownConfigKeysPolicy: UnknownConfigKeysPolicy,
val devMode: Boolean,
val clearNetworkMapCache: Boolean) {
fun loadConfig(): Pair<Config, Try<NodeConfiguration>> {
val rawConfig = ConfigHelper.loadConfig(
baseDirectory,
configFile,
configOverrides = ConfigFactory.parseMap(mapOf("noLocalShell" to this.noLocalShell) +
if (devMode) mapOf("devMode" to this.devMode) else emptyMap<String, Any>())
)
return rawConfig to Try.on {
rawConfig.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
if (nodeRegistrationOption != null) {
require(!config.devMode) { "registration cannot occur in devMode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
}
}
}
}
}
}

View File

@ -0,0 +1,135 @@
package net.corda.node
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.utilities.Try
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import picocli.CommandLine.Option
import java.nio.file.Path
import java.nio.file.Paths
class NodeCmdLineOptions {
@Option(
names = ["-b", "--base-directory"],
description = ["The node working directory where all the files are kept."]
)
var baseDirectory: Path = Paths.get(".")
@Option(
names = ["-f", "--config-file"],
description = ["The path to the config file. By default this is node.conf in the base directory."]
)
var configFileArgument: Path? = null
val configFile : Path
get() = configFileArgument ?: (baseDirectory / "node.conf")
@Option(
names = ["--sshd"],
description = ["If set, enables SSH server for node administration."]
)
var sshdServer: Boolean = false
@Option(
names = ["--sshd-port"],
description = ["The port to start the SSH server on, if enabled."]
)
var sshdServerPort: Int = 2222
@Option(
names = ["-n", "--no-local-shell"],
description = ["Do not start the embedded shell locally."]
)
var noLocalShell: Boolean = false
@Option(
names = ["--initial-registration"],
description = ["Start initial node registration with Corda network to obtain certificate from the permissioning server."]
)
var isRegistration: Boolean = false
@Option(
names = ["-t", "--network-root-truststore"],
description = ["Network root trust store obtained from network operator."]
)
var networkRootTrustStorePath = Paths.get("certificates") / "network-root-truststore.jks"
@Option(
names = ["-p", "--network-root-truststore-password"],
description = ["Network root trust store password obtained from network operator."]
)
var networkRootTrustStorePassword: String? = null
@Option(
names = ["--on-unknown-config-keys"],
description = ["How to behave on unknown node configuration. \${COMPLETION-CANDIDATES}"]
)
var unknownConfigKeysPolicy: UnknownConfigKeysPolicy = UnknownConfigKeysPolicy.FAIL
@Option(
names = ["-d", "--dev-mode"],
description = ["Run the node in developer mode. Unsafe for production."]
)
var devMode: Boolean? = null
@Option(
names = ["--just-generate-node-info"],
description = ["Perform the node start-up task necessary to generate its node info, save it to disk, then quit"]
)
var justGenerateNodeInfo: Boolean = false
@Option(
names = ["--just-generate-rpc-ssl-settings"],
description = ["Generate the SSL key and trust stores for a secure RPC connection."]
)
var justGenerateRpcSslCerts: Boolean = false
@Option(
names = ["--bootstrap-raft-cluster"],
description = ["Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer addresses), acting as a seed for other nodes to join the cluster."]
)
var bootstrapRaftCluster: Boolean = false
@Option(
names = ["-c", "--clear-network-map-cache"],
description = ["Clears local copy of network map, on node startup it will be restored from server or file system."]
)
var clearNetworkMapCache: Boolean = false
val nodeRegistrationOption : NodeRegistrationOption? by lazy {
if (isRegistration) {
requireNotNull(networkRootTrustStorePassword) { "Network root trust store password must be provided in registration mode using --network-root-truststore-password." }
require(networkRootTrustStorePath.exists()) { "Network root trust store path: '$networkRootTrustStorePath' doesn't exist" }
NodeRegistrationOption(networkRootTrustStorePath, networkRootTrustStorePassword!!)
} else {
null
}
}
fun loadConfig(): Pair<Config, Try<NodeConfiguration>> {
val rawConfig = ConfigHelper.loadConfig(
baseDirectory,
configFile,
configOverrides = ConfigFactory.parseMap(mapOf("noLocalShell" to this.noLocalShell) +
if (sshdServer) mapOf("sshd" to mapOf("port" to sshdServerPort.toString())) else emptyMap<String, Any>() +
if (devMode != null) mapOf("devMode" to this.devMode) else emptyMap())
)
return rawConfig to Try.on {
rawConfig.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
if (nodeRegistrationOption != null) {
require(!config.devMode) { "Registration cannot occur in devMode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
}
}
}
}
}
}
data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String)

View File

@ -55,7 +55,10 @@ import net.corda.node.services.keys.KeyManagementServiceInternal
import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.DeduplicationHandler
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.*
import net.corda.node.services.network.NetworkMapClient
import net.corda.node.services.network.NetworkMapUpdater
import net.corda.node.services.network.NodeInfoWatcher
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.*
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.*
@ -146,8 +149,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// TODO Break cyclic dependency
identityService.database = database
}
private val persistentNetworkMapCache = PersistentNetworkMapCache(database, configuration.myLegalName)
val networkMapCache = NetworkMapCacheImpl(persistentNetworkMapCache, identityService, database).tokenize()
val networkMapCache = PersistentNetworkMapCache(database, identityService, configuration.myLegalName).tokenize()
val checkpointStorage = DBCheckpointStorage()
@Suppress("LeakingThis")
val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize()
@ -262,7 +264,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
return database.use {
it.transaction {
persistentNetworkMapCache.start(notaries = emptyList())
val (_, nodeInfoAndSigned) = updateNodeInfo(identity, identityKeyPair, publish = false)
nodeInfoAndSigned.nodeInfo
}
@ -274,8 +275,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
log.info("Starting clearing of network map cache entries...")
startDatabase()
database.use {
persistentNetworkMapCache.start(notaries = emptyList())
persistentNetworkMapCache.clearNetworkMapCache()
networkMapCache.clearNetworkMapCache()
}
}
@ -331,8 +331,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
val (keyPairs, nodeInfoAndSigned, myNotaryIdentity) = database.transaction {
persistentNetworkMapCache.start(netParams.notaries)
networkMapCache.start()
networkMapCache.start(netParams.notaries)
updateNodeInfo(identity, identityKeyPair, publish = true)
}

View File

@ -19,6 +19,7 @@ import net.corda.core.internal.RPC_UPLOADER
import net.corda.core.internal.STRUCTURAL_STEP_PREFIX
import net.corda.core.internal.sign
import net.corda.core.messaging.*
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
@ -62,6 +63,8 @@ internal class CordaRPCOpsImpl(
return snapshot
}
override val networkParameters: NetworkParameters get() = services.networkParameters
override fun networkParametersFeed(): DataFeed<ParametersUpdateInfo?, ParametersUpdateInfo> {
return services.networkMapUpdater.trackParametersUpdate()
}

View File

@ -48,7 +48,7 @@ open class EnterpriseNode(configuration: NodeConfiguration,
}
}
class Startup(args: Array<String>) : NodeStartup(args) {
class Startup : NodeStartup() {
override fun preNetworkRegistration(conf: NodeConfiguration) {
super.preNetworkRegistration(conf)
conf.relay?.let { connectToRelay(it, conf.p2pAddress.port) }

View File

@ -42,11 +42,7 @@ import net.corda.node.services.Permissions
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.SecurityConfiguration
import net.corda.node.services.config.shouldInitCrashShell
import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.JmxReporterType
import net.corda.node.services.config.*
import net.corda.node.services.messaging.*
import net.corda.node.services.rpc.ArtemisRpcBroker
import net.corda.node.utilities.AddressUtils
@ -59,8 +55,8 @@ import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.bridging.BridgeControlListener
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.serialization.internal.*
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.serialization.internal.*
import org.h2.jdbc.JdbcSQLException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@ -72,10 +68,10 @@ import java.net.InetAddress
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Clock
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import javax.management.ObjectName
import kotlin.system.exitProcess
import java.util.concurrent.TimeUnit
class NodeWithInfo(val node: Node, val info: NodeInfo) {
val services: StartedNodeServices = object : StartedNodeServices, ServiceHubInternal by node.services, FlowStarter by node.flowStarter {}
@ -282,7 +278,7 @@ open class Node(configuration: NodeConfiguration,
network.start(
myIdentity = nodeInfo.legalIdentities[0].owningKey,
serviceIdentity = if (nodeInfo.legalIdentities.size == 1) null else nodeInfo.legalIdentities[1].owningKey,
advertisedAddress = nodeInfo.addresses.single(),
advertisedAddress = nodeInfo.addresses[0],
maxMessageSize = networkParameters.maxMessageSize,
legalName = nodeInfo.legalIdentities[0].name.toString()
)
@ -299,13 +295,12 @@ open class Node(configuration: NodeConfiguration,
ArtemisRpcBroker.withoutSsl(configuration.p2pSslOptions, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell())
}
}
rpcBroker!!.closeOnStop()
rpcBroker!!.addresses
}
}
}
override fun myAddresses(): List<NetworkHostAndPort> = listOf(getAdvertisedAddress())
override fun myAddresses(): List<NetworkHostAndPort> = listOf(getAdvertisedAddress()) + configuration.additionalP2PAddresses
private fun getAdvertisedAddress(): NetworkHostAndPort {
return with(configuration) {

View File

@ -1,13 +1,12 @@
package net.corda.node.internal
import com.jcabi.manifests.Manifests
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigRenderOptions
import io.netty.channel.unix.Errors
import joptsimple.OptionParser
import joptsimple.util.PathConverter
import net.corda.cliutils.CordaCliWrapper
import net.corda.cliutils.CordaVersionProvider
import net.corda.cliutils.ExitCodes
import net.corda.core.cordapp.Cordapp
import net.corda.core.crypto.Crypto
import net.corda.core.internal.*
@ -24,20 +23,21 @@ import net.corda.node.services.config.shouldStartSSHDaemon
import net.corda.node.services.transactions.bftSMaRtSerialFilter
import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.node.utilities.registration.NodeRegistrationException
import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.node.utilities.saveToKeyStore
import net.corda.node.utilities.saveToTrustStore
import net.corda.nodeapi.internal.PLATFORM_VERSION
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException
import net.corda.nodeapi.internal.persistence.DatabaseMigrationException
import net.corda.nodeapi.internal.persistence.oracleJdbcDriverSerialFilter
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
import net.corda.nodeapi.internal.persistence.DatabaseMigrationException
import net.corda.nodeapi.internal.persistence.oracleJdbcDriverSerialFilter
import net.corda.tools.shell.InteractiveShell
import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole
import org.slf4j.bridge.SLF4JBridgeHandler
import picocli.CommandLine.Mixin
import sun.misc.VMSupport
import java.io.Console
import java.io.File
@ -53,7 +53,7 @@ import java.util.*
import kotlin.system.exitProcess
/** This class is responsible for starting a Node from command line arguments. */
open class NodeStartup(val args: Array<String>) {
open class NodeStartup: CordaCliWrapper("corda", "Runs a Corda Node") {
companion object {
private val logger by lazy { loggerFor<Node>() } // I guess this is lazy to allow for logging init, but why Node?
const val LOGS_DIRECTORY_NAME = "logs"
@ -61,31 +61,33 @@ open class NodeStartup(val args: Array<String>) {
private const val INITIAL_REGISTRATION_MARKER = ".initialregistration"
}
@Mixin
var cmdLineOptions = NodeCmdLineOptions()
/**
* @return true if the node startup was successful. This value is intended to be the exit code of the process.
* @return exit code based on the success of the node startup. This value is intended to be the exit code of the process.
*/
open fun run(): Boolean {
override fun runProgram(): Int {
val startTime = System.currentTimeMillis()
if (!canNormalizeEmptyPath()) {
println("You are using a version of Java that is not supported (${System.getProperty("java.version")}). Please upgrade to the latest version.")
println("Corda will now exit...")
return false
return ExitCodes.FAILURE
}
val registrationMode = checkRegistrationMode()
val cmdlineOptions: CmdLineOptions = if (registrationMode && !args.contains("--initial-registration")) {
if (registrationMode && !cmdLineOptions.isRegistration) {
println("Node was started before with `--initial-registration`, but the registration was not completed.\nResuming registration.")
// Pretend that the node was started with `--initial-registration` to help prevent user error.
NodeArgsParser().parseOrExit(*args.plus("--initial-registration"))
} else {
NodeArgsParser().parseOrExit(*args)
cmdLineOptions.isRegistration = true
}
// We do the single node check before we initialise logging so that in case of a double-node start it
// doesn't mess with the running node's logs.
enforceSingleNodeIsRunning(cmdlineOptions.baseDirectory)
enforceSingleNodeIsRunning(cmdLineOptions.baseDirectory)
initLogging(cmdlineOptions)
initLogging()
// Register all cryptography [Provider]s.
// Required to install our [SecureRandom] before e.g., UUID asks for one.
// This needs to go after initLogging(netty clashes with our logging).
@ -93,40 +95,34 @@ open class NodeStartup(val args: Array<String>) {
val versionInfo = getVersionInfo()
if (cmdlineOptions.isVersion) {
println("${versionInfo.vendor} ${versionInfo.releaseVersion}")
println("Revision ${versionInfo.revision}")
println("Platform Version ${versionInfo.platformVersion}")
return true
}
drawBanner(versionInfo)
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
val configuration = (attempt { loadConfiguration(cmdlineOptions) }.doOnException(handleConfigurationLoadingError(cmdlineOptions.configFile)) as? Try.Success)?.let(Try.Success<NodeConfiguration>::value) ?: return false
val configuration = (attempt { loadConfiguration() }.doOnException(handleConfigurationLoadingError(cmdLineOptions.configFile)) as? Try.Success)?.let(Try.Success<NodeConfiguration>::value) ?: return ExitCodes.FAILURE
val errors = configuration.validate()
if (errors.isNotEmpty()) {
logger.error("Invalid node configuration. Errors were:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}")
return false
return ExitCodes.FAILURE
}
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success ?: return false
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success ?: return ExitCodes.FAILURE
attempt { preNetworkRegistration(configuration) }.doOnException(handleRegistrationError) as? Try.Success ?: return false
attempt { preNetworkRegistration(configuration) }.doOnException(handleRegistrationError) as? Try.Success ?: return ExitCodes.FAILURE
cmdlineOptions.nodeRegistrationOption?.let {
cmdLineOptions.nodeRegistrationOption?.let {
// Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig]
attempt { registerWithNetwork(configuration, versionInfo, cmdlineOptions.nodeRegistrationOption) }.doOnException(handleRegistrationError) as? Try.Success ?: return false
attempt { registerWithNetwork(configuration, versionInfo, it) }.doOnException(handleRegistrationError) as? Try.Success ?: return ExitCodes.FAILURE
// At this point the node registration was successful. We can delete the marker file.
deleteNodeRegistrationMarker(cmdlineOptions.baseDirectory)
return true
deleteNodeRegistrationMarker(cmdLineOptions.baseDirectory)
return ExitCodes.SUCCESS
}
logStartupInfo(versionInfo, cmdlineOptions, configuration)
logStartupInfo(versionInfo, configuration)
return attempt { startNode(configuration, versionInfo, startTime, cmdlineOptions) }.doOnSuccess { logger.info("Node exiting successfully") }.doOnException(handleStartError).isSuccess
attempt { startNode(configuration, versionInfo, startTime) }.doOnSuccess { logger.info("Node exiting successfully") }.doOnException(handleStartError) as? Try.Success ?: return ExitCodes.FAILURE
return ExitCodes.SUCCESS
}
private fun <RESULT> attempt(action: () -> RESULT): Try<RESULT> = Try.on(action)
@ -142,13 +138,11 @@ open class NodeStartup(val args: Array<String>) {
private fun Exception.isOpenJdkKnownIssue() = message?.startsWith("Unknown named curve:") == true
private fun Exception.errorCode(): String {
val hash = staticLocationBasedHash()
return Integer.toOctalString(hash)
}
private fun Throwable.staticLocationBasedHash(visited: Set<Throwable> = setOf(this)): Int {
val cause = this.cause
return when {
cause != null && !visited.contains(cause) -> Objects.hash(this::class.java.name, stackTrace.customHashCode(), cause.staticLocationBasedHash(visited + cause))
@ -203,14 +197,13 @@ open class NodeStartup(val args: Array<String>) {
""".trimIndent()
}
private fun loadConfiguration(cmdlineOptions: CmdLineOptions): NodeConfiguration {
val (rawConfig, configurationResult) = loadConfigFile(cmdlineOptions)
if (cmdlineOptions.devMode) {
private fun loadConfiguration(): NodeConfiguration {
val (rawConfig, configurationResult) = loadConfigFile()
if (cmdLineOptions.devMode == true) {
println("Config:\n${rawConfig.root().render(ConfigRenderOptions.defaults())}")
}
val configuration = configurationResult.getOrThrow()
return if (cmdlineOptions.bootstrapRaftCluster) {
return if (cmdLineOptions.bootstrapRaftCluster) {
println("Bootstrapping raft cluster (starting up as seed node).")
// Ignore the configured clusterAddresses to make the node bootstrap a cluster instead of joining.
(configuration as NodeConfigurationImpl).copy(notary = configuration.notary?.copy(raft = configuration.notary?.raft?.copy(clusterAddresses = emptyList())))
@ -220,27 +213,11 @@ open class NodeStartup(val args: Array<String>) {
}
private fun checkRegistrationMode(): Boolean {
// Parse the command line args just to get the base directory. The base directory is needed to determine
// if the node registration marker file exists, _before_ we call NodeArgsParser.parse().
// If it does exist, we call NodeArgsParser with `--initial-registration` added to the argument list. This way
// we make sure that the initial registration is completed, even if the node was restarted before the first
// attempt to register succeeded and the node administrator forgets to specify `--initial-registration` upon
// restart.
val optionParser = OptionParser()
optionParser.allowsUnrecognizedOptions()
val baseDirectoryArg = optionParser
.accepts("base-directory", "The node working directory where all the files are kept")
.withRequiredArg()
.withValuesConvertedBy(PathConverter())
.defaultsTo(Paths.get("."))
val isRegistrationArg =
optionParser.accepts("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.")
val optionSet = optionParser.parse(*args)
val baseDirectory = optionSet.valueOf(baseDirectoryArg).normalize().toAbsolutePath()
val baseDirectory = cmdLineOptions.baseDirectory.normalize().toAbsolutePath()
// If the node was started with `--initial-registration`, create marker file.
// We do this here to ensure the marker is created even if parsing the args with NodeArgsParser fails.
val marker = File((baseDirectory / INITIAL_REGISTRATION_MARKER).toUri())
if (!optionSet.has(isRegistrationArg) && !marker.exists()) {
if (!cmdLineOptions.isRegistration && !marker.exists()) {
return false
}
try {
@ -266,20 +243,19 @@ open class NodeStartup(val args: Array<String>) {
protected open fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo)
protected open fun startNode(conf: NodeConfiguration, versionInfo: VersionInfo, startTime: Long, cmdlineOptions: CmdLineOptions) {
cmdlineOptions.baseDirectory.createDirectories()
protected open fun startNode(conf: NodeConfiguration, versionInfo: VersionInfo, startTime: Long) {
cmdLineOptions.baseDirectory.createDirectories()
val node = createNode(conf, versionInfo)
if (cmdlineOptions.clearNetworkMapCache) {
if (cmdLineOptions.clearNetworkMapCache) {
node.clearNetworkMapCache()
return
}
if (cmdlineOptions.justGenerateNodeInfo) {
if (cmdLineOptions.justGenerateNodeInfo) {
// Perform the minimum required start-up logic to be able to write a nodeInfo to disk
node.generateAndSaveNodeInfo()
return
}
if (cmdlineOptions.justGenerateRpcSslCerts) {
if (cmdLineOptions.justGenerateRpcSslCerts) {
val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(conf.myLegalName.x500Principal)
val keyStorePath = conf.baseDirectory / "certificates" / "rpcsslkeystore.jks"
@ -380,7 +356,7 @@ open class NodeStartup(val args: Array<String>) {
node.run()
}
protected open fun logStartupInfo(versionInfo: VersionInfo, cmdlineOptions: CmdLineOptions, conf: NodeConfiguration) {
protected open fun logStartupInfo(versionInfo: VersionInfo, conf: NodeConfiguration) {
logger.info("Vendor: ${versionInfo.vendor}")
logger.info("Release: ${versionInfo.releaseVersion}")
logger.info("Platform Version: ${versionInfo.platformVersion}")
@ -389,12 +365,11 @@ open class NodeStartup(val args: Array<String>) {
logger.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this
logger.info("Main class: ${NodeConfiguration::class.java.location.toURI().path}")
logger.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
logger.info("Application Args: ${args.joinToString(" ")}")
logger.info("bootclasspath: ${info.bootClassPath}")
logger.info("classpath: ${info.classPath}")
logger.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
logger.info("Machine: ${lookupMachineNameAndMaybeWarn()}")
logger.info("Working Directory: ${cmdlineOptions.baseDirectory}")
logger.info("Working Directory: ${cmdLineOptions.baseDirectory}")
val agentProperties = VMSupport.getAgentProperties()
if (agentProperties.containsKey("sun.jdwp.listenerAddress")) {
logger.info("Debug port: ${agentProperties.getProperty("sun.jdwp.listenerAddress")}")
@ -422,7 +397,7 @@ open class NodeStartup(val args: Array<String>) {
println("Corda node will now terminate.")
}
protected open fun loadConfigFile(cmdlineOptions: CmdLineOptions): Pair<Config, Try<NodeConfiguration>> = cmdlineOptions.loadConfig()
protected open fun loadConfigFile(): Pair<Config, Try<NodeConfiguration>> = cmdLineOptions.loadConfig()
protected open fun banJavaSerialisation(conf: NodeConfiguration) {
val isOracleDbDriver = conf.dataSourceProperties.getProperty("dataSource.url", "").startsWith("jdbc:oracle:")
@ -441,14 +416,11 @@ open class NodeStartup(val args: Array<String>) {
}
protected open fun getVersionInfo(): VersionInfo {
// Manifest properties are only available if running from the corda jar
fun manifestValue(name: String): String? = if (Manifests.exists(name)) Manifests.read(name) else null
return VersionInfo(
manifestValue("Corda-Platform-Version")?.toInt() ?: 1,
manifestValue("Corda-Release-Version") ?: "Unknown",
manifestValue("Corda-Revision") ?: "Unknown",
manifestValue("Corda-Vendor") ?: "Unknown"
PLATFORM_VERSION,
CordaVersionProvider.releaseVersion,
CordaVersionProvider.revision,
CordaVersionProvider.vendor
)
}
@ -496,14 +468,14 @@ open class NodeStartup(val args: Array<String>) {
}
}
protected open fun initLogging(cmdlineOptions: CmdLineOptions) {
val loggingLevel = cmdlineOptions.loggingLevel.name.toLowerCase(Locale.ENGLISH)
override fun initLogging() {
val loggingLevel = loggingLevel.name().toLowerCase(Locale.ENGLISH)
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
if (cmdlineOptions.logToConsole) {
if (verbose) {
System.setProperty("consoleLogLevel", loggingLevel)
Node.renderBasicInfoToConsole = false
}
System.setProperty("log-path", (cmdlineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString())
System.setProperty("log-path", (cmdLineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString())
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
SLF4JBridgeHandler.install()
}
@ -544,9 +516,6 @@ open class NodeStartup(val args: Array<String>) {
}
open fun drawBanner(versionInfo: VersionInfo) {
// This line makes sure ANSI escapes work on Windows, where they aren't supported out of the box.
AnsiConsole.systemInstall()
Emoji.renderIfSupported {
val messages = arrayListOf(
"The only distributed ledger that pays\nhomage to Pac Man in its logo.",

View File

@ -25,9 +25,9 @@ import net.corda.node.services.persistence.AttachmentStorageInternal
import net.corda.node.services.statemachine.ExternalEvent
import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.nodeapi.internal.persistence.CordaPersistence
import java.security.PublicKey
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal
interface NetworkMapCacheBaseInternal : NetworkMapCacheBase {
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBase {
val allNodeHashes: List<SecureHash>
fun getNodeByHash(nodeHash: SecureHash): NodeInfo?

View File

@ -5,6 +5,7 @@ import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigFactory.systemEnvironment
import com.typesafe.config.ConfigFactory.systemProperties
import com.typesafe.config.ConfigParseOptions
import net.corda.cliutils.CordaSystemUtils
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
@ -120,18 +121,7 @@ fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500N
}
}
}
/** Parse a value to be database schema name friendly and removes the last part if it matches a port ("_" followed by at least 5 digits) */
fun parseToDbSchemaFriendlyName(value: String) =
value.replace(" ", "").replace("-", "_").replace(Regex("_\\d{5,}$"),"")
/** This is generally covered by commons-lang. */
object CordaSystemUtils {
const val OS_NAME = "os.name"
const val MAC_PREFIX = "Mac"
const val WIN_PREFIX = "Windows"
fun isOsMac() = getOsName().startsWith(MAC_PREFIX)
fun isOsWindows() = getOsName().startsWith(WIN_PREFIX)
fun getOsName() = System.getProperty(OS_NAME)
}

View File

@ -52,6 +52,7 @@ interface NodeConfiguration {
val notary: NotaryConfig?
val additionalNodeInfoPollingFrequencyMsec: Long
val p2pAddress: NetworkHostAndPort
val additionalP2PAddresses: List<NetworkHostAndPort>
val rpcOptions: NodeRpcOptions
val messagingServerAddress: NetworkHostAndPort?
val messagingServerExternal: Boolean
@ -243,6 +244,7 @@ data class NodeConfigurationImpl(
override val verifierType: VerifierType,
override val flowTimeout: FlowTimeoutConfiguration,
override val p2pAddress: NetworkHostAndPort,
override val additionalP2PAddresses: List<NetworkHostAndPort> = emptyList(),
private val rpcAddress: NetworkHostAndPort? = null,
private val rpcSettings: NodeRpcSettings,
override val relay: RelayConfiguration?,

View File

@ -122,7 +122,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
private lateinit var advertisedAddress: NetworkHostAndPort
private var maxMessageSize: Int = -1
override val myAddress: SingleMessageRecipient get() = NodeAddress(myIdentity, advertisedAddress)
override val myAddress: SingleMessageRecipient get() = NodeAddress(myIdentity)
override val ourSenderUUID = UUID.randomUUID().toString()
private val state = ThreadBox(InnerState())
@ -269,7 +269,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
fun gatherAddresses(node: NodeInfo): Sequence<BridgeEntry> {
return state.locked {
node.legalIdentitiesAndCerts.map {
val messagingAddress = NodeAddress(it.party.owningKey, node.addresses.first())
val messagingAddress = NodeAddress(it.party.owningKey)
BridgeEntry(messagingAddress.queueName, node.addresses, node.legalIdentities.map { it.name })
}.filter { producerSession!!.queueQuery(SimpleString(it.queueName)).isExists }.asSequence()
}
@ -278,14 +278,14 @@ class P2PMessagingClient(val config: NodeConfiguration,
fun deployBridges(node: NodeInfo) {
gatherAddresses(node)
.forEach {
sendBridgeControl(BridgeControl.Create(myIdentity.toStringShort(), it))
sendBridgeControl(BridgeControl.Create(config.myLegalName.toString(), it))
}
}
fun destroyBridges(node: NodeInfo) {
gatherAddresses(node)
.forEach {
sendBridgeControl(BridgeControl.Delete(myIdentity.toStringShort(), it))
sendBridgeControl(BridgeControl.Delete(config.myLegalName.toString(), it))
}
}
@ -325,7 +325,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
delayStartQueues += queue.toString()
}
}
val startupMessage = BridgeControl.NodeToBridgeSnapshot(myIdentity.toStringShort(), inboxes, requiredBridges)
val startupMessage = BridgeControl.NodeToBridgeSnapshot(config.myLegalName.toString(), inboxes, requiredBridges)
sendBridgeControl(startupMessage)
}
@ -346,12 +346,9 @@ class P2PMessagingClient(val config: NodeConfiguration,
return
}
eventsSubscription = p2pConsumer!!.messages
.doOnError { error -> throw error }
.doOnNext { message -> deliver(message) }
// this `run()` method is semantically meant to block until the message consumption runs, hence the latch here
.doOnCompleted(latch::countDown)
.doOnError { error -> throw error }
.subscribe()
.subscribe({ message -> deliver(message) }, { error -> throw error })
p2pConsumer!!
}
consumer.start()
@ -537,7 +534,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
val peers = networkMap.getNodesByOwningKeyIndex(keyHash)
for (node in peers) {
val bridge = BridgeEntry(queueName, node.addresses, node.legalIdentities.map { it.name })
val createBridgeMessage = BridgeControl.Create(myIdentity.toStringShort(), bridge)
val createBridgeMessage = BridgeControl.Create(config.myLegalName.toString(), bridge)
sendBridgeControl(createBridgeMessage)
}
}
@ -582,7 +579,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients {
return when (partyInfo) {
is PartyInfo.SingleNode -> NodeAddress(partyInfo.party.owningKey, partyInfo.addresses.single())
is PartyInfo.SingleNode -> NodeAddress(partyInfo.party.owningKey)
is PartyInfo.DistributedNode -> ServiceAddress(partyInfo.party.owningKey)
}
}

View File

@ -1,51 +0,0 @@
package net.corda.node.services.network
import net.corda.core.identity.AbstractParty
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger
import net.corda.node.services.api.NetworkMapCacheBaseInternal
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.nodeapi.internal.persistence.CordaPersistence
class NetworkMapCacheImpl(
private val networkMapCacheBase: NetworkMapCacheBaseInternal,
private val identityService: IdentityService,
private val database: CordaPersistence
) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal, SingletonSerializeAsToken() {
companion object {
private val logger = contextLogger()
}
fun start() {
for (nodeInfo in networkMapCacheBase.allNodes) {
for (identity in nodeInfo.legalIdentitiesAndCerts) {
identityService.verifyAndRegisterIdentity(identity)
}
}
networkMapCacheBase.changed.subscribe { mapChange ->
// TODO how should we handle network map removal
if (mapChange is NetworkMapCache.MapChange.Added) {
mapChange.node.legalIdentitiesAndCerts.forEach {
try {
identityService.verifyAndRegisterIdentity(it)
} catch (ignore: Exception) {
// Log a warning to indicate node info is not added to the network map cache.
logger.warn("Node info for :'${it.name}' is not added to the network map due to verification error.")
}
}
}
}
}
override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? {
return database.transaction {
val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party)
wellKnownParty?.let {
getNodesByLegalIdentityKey(it.owningKey).firstOrNull()
}
}
}
}

View File

@ -3,6 +3,7 @@ package net.corda.node.services.network
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
@ -11,15 +12,17 @@ import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.node.services.PartyInfo
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.Try
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.api.NetworkMapCacheBaseInternal
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.utilities.NonInvalidatingCache
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit
@ -34,7 +37,8 @@ import javax.annotation.concurrent.ThreadSafe
/** Database-based network map cache. */
@ThreadSafe
open class PersistentNetworkMapCache(private val database: CordaPersistence,
private val myLegalName: CordaX500Name) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
private val identityService: IdentityService,
private val myLegalName: CordaX500Name) : NetworkMapCacheInternal, SingletonSerializeAsToken() {
companion object {
private val logger = contextLogger()
}
@ -78,6 +82,15 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
}
}
override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? {
return database.transaction {
val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party)
wellKnownParty?.let {
getNodesByLegalIdentityKey(it.owningKey).firstOrNull()
}
}
}
override fun getNodeByHash(nodeHash: SecureHash): NodeInfo? {
return database.transaction {
val builder = session.criteriaBuilder
@ -160,6 +173,7 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
val previousNode = getNodesByLegalIdentityKey(node.legalIdentities.first().owningKey).firstOrNull()
if (previousNode == null) {
logger.info("No previous node found")
if (!verifyAndRegisterIdentities(node)) return
database.transaction {
updateInfoDB(node, session)
changePublisher.onNext(MapChange.Added(node))
@ -169,6 +183,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
return
} else if (previousNode != node) {
logger.info("Previous node was found as: $previousNode")
// TODO We should be adding any new identities as well
if (!verifyIdentities(node)) return
database.transaction {
updateInfoDB(node, session)
changePublisher.onNext(MapChange.Modified(node, previousNode))
@ -183,6 +199,27 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence,
logger.debug { "Done adding node with info: $node" }
}
private fun verifyIdentities(node: NodeInfo): Boolean {
val failures = node.legalIdentitiesAndCerts.mapNotNull { Try.on { it.verify(identityService.trustAnchor) } as? Try.Failure }
if (failures.isNotEmpty()) {
logger.warn("$node has ${failures.size} invalid identities:")
failures.forEach { logger.warn("", it) }
}
return failures.isEmpty()
}
private fun verifyAndRegisterIdentities(node: NodeInfo): Boolean {
// First verify all the node's identities are valid before registering any of them
return if (verifyIdentities(node)) {
for (identity in node.legalIdentitiesAndCerts) {
identityService.verifyAndRegisterIdentity(identity)
}
true
} else {
false
}
}
override fun removeNode(node: NodeInfo) {
logger.info("Removing node with info: $node")
synchronized(_changed) {

View File

@ -3,6 +3,7 @@ keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"
crlCheckSoftFail = true
lazyBridgeStart = true
additionalP2PAddresses = []
dataSourceProperties = {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0;LOCK_TIMEOUT=10000"

View File

@ -1,189 +0,0 @@
package net.corda.node
import joptsimple.OptionException
import net.corda.core.internal.delete
import net.corda.core.internal.div
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import net.corda.nodeapi.internal.crypto.X509KeyStore
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.BeforeClass
import org.junit.Test
import org.slf4j.event.Level
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class NodeArgsParserTest {
private val parser = NodeArgsParser()
companion object {
private lateinit var workingDirectory: Path
private lateinit var buildDirectory: Path
@BeforeClass
@JvmStatic
fun initDirectories() {
workingDirectory = Paths.get(".").normalize().toAbsolutePath()
buildDirectory = workingDirectory.resolve("build")
}
}
@Test
fun `no command line arguments`() {
assertThat(parser.parse()).isEqualTo(CmdLineOptions(
baseDirectory = workingDirectory,
configFile = workingDirectory / "node.conf",
logToConsole = false,
loggingLevel = Level.INFO,
nodeRegistrationOption = null,
isVersion = false,
noLocalShell = false,
sshdServer = false,
justGenerateNodeInfo = false,
justGenerateRpcSslCerts = false,
bootstrapRaftCluster = false,
unknownConfigKeysPolicy = UnknownConfigKeysPolicy.FAIL,
devMode = false,
clearNetworkMapCache = false))
}
@Test
fun `base-directory with relative path`() {
val expectedBaseDir = Paths.get("tmp").normalize().toAbsolutePath()
val cmdLineOptions = parser.parse("--base-directory", "tmp")
assertThat(cmdLineOptions.baseDirectory).isEqualTo(expectedBaseDir)
assertThat(cmdLineOptions.configFile).isEqualTo(expectedBaseDir / "node.conf")
}
@Test
fun `base-directory with absolute path`() {
val baseDirectory = Paths.get("tmp").normalize().toAbsolutePath()
val cmdLineOptions = parser.parse("--base-directory", baseDirectory.toString())
assertThat(cmdLineOptions.baseDirectory).isEqualTo(baseDirectory)
assertThat(cmdLineOptions.configFile).isEqualTo(baseDirectory / "node.conf")
}
@Test
fun `config-file with relative path`() {
val cmdLineOptions = parser.parse("--config-file", "different.conf")
assertThat(cmdLineOptions.baseDirectory).isEqualTo(workingDirectory)
assertThat(cmdLineOptions.configFile).isEqualTo(workingDirectory / "different.conf")
}
@Test
fun `config-file with absolute path`() {
val configFile = Paths.get("tmp", "a.conf").normalize().toAbsolutePath()
val cmdLineOptions = parser.parse("--config-file", configFile.toString())
assertThat(cmdLineOptions.baseDirectory).isEqualTo(workingDirectory)
assertThat(cmdLineOptions.configFile).isEqualTo(configFile)
}
@Test
fun `base-directory without argument`() {
assertThatExceptionOfType(OptionException::class.java).isThrownBy {
parser.parse("--base-directory")
}.withMessageContaining("base-directory")
}
@Test
fun `config-file without argument`() {
assertThatExceptionOfType(OptionException::class.java).isThrownBy {
parser.parse("--config-file")
}.withMessageContaining("config-file")
}
@Test
fun `log-to-console`() {
val cmdLineOptions = parser.parse("--log-to-console")
assertThat(cmdLineOptions.logToConsole).isTrue()
}
@Test
fun `logging-level`() {
for (level in Level.values()) {
val cmdLineOptions = parser.parse("--logging-level", level.name)
assertThat(cmdLineOptions.loggingLevel).isEqualTo(level)
}
}
@Test
fun `logging-level without argument`() {
assertThatExceptionOfType(OptionException::class.java).isThrownBy {
parser.parse("--logging-level")
}.withMessageContaining("logging-level")
}
@Test
fun `logging-level with invalid argument`() {
assertThatExceptionOfType(OptionException::class.java).isThrownBy {
parser.parse("--logging-level", "not-a-level")
}.withMessageContaining("logging-level")
}
@Test
fun `initial-registration`() {
// Create this temporary file in the "build" directory so that "clean" can delete it.
val truststorePath = buildDirectory / "truststore" / "file.jks"
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
parser.parse("--initial-registration", "--network-root-truststore", "$truststorePath", "--network-root-truststore-password", "password-test")
}.withMessageContaining("Network root trust store path").withMessageContaining("doesn't exist")
X509KeyStore.fromFile(truststorePath, "dummy_password", createNew = true)
try {
val cmdLineOptions = parser.parse("--initial-registration", "--network-root-truststore", "$truststorePath", "--network-root-truststore-password", "password-test")
assertNotNull(cmdLineOptions.nodeRegistrationOption)
assertEquals(truststorePath.toAbsolutePath(), cmdLineOptions.nodeRegistrationOption?.networkRootTrustStorePath)
assertEquals("password-test", cmdLineOptions.nodeRegistrationOption?.networkRootTrustStorePassword)
} finally {
truststorePath.delete()
}
}
@Test
fun version() {
val cmdLineOptions = parser.parse("--version")
assertThat(cmdLineOptions.isVersion).isTrue()
}
@Test
fun `generate node infos`() {
val cmdLineOptions = parser.parse("--just-generate-node-info")
assertThat(cmdLineOptions.justGenerateNodeInfo).isTrue()
}
@Test
fun `clear network map cache`() {
val cmdLineOptions = parser.parse("--clear-network-map-cache")
assertThat(cmdLineOptions.clearNetworkMapCache).isTrue()
}
@Test
fun `bootstrap raft cluster`() {
val cmdLineOptions = parser.parse("--bootstrap-raft-cluster")
assertThat(cmdLineOptions.bootstrapRaftCluster).isTrue()
}
@Test
fun `on-unknown-config-keys options`() {
UnknownConfigKeysPolicy.values().forEach { onUnknownConfigKeyPolicy ->
val cmdLineOptions = parser.parse("--on-unknown-config-keys", onUnknownConfigKeyPolicy.name)
assertThat(cmdLineOptions.unknownConfigKeysPolicy).isEqualTo(onUnknownConfigKeyPolicy)
}
}
@Test
fun `invalid argument`() {
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
parser.parse("foo")
}.withMessageContaining("Unrecognized argument(s): foo")
}
@Test
fun `invalid arguments`() {
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
parser.parse("foo", "bar")
}.withMessageContaining("Unrecognized argument(s): foo, bar")
}
}

View File

@ -0,0 +1,44 @@
package net.corda.node
import net.corda.core.internal.div
import net.corda.node.internal.NodeStartup
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import org.apache.logging.log4j.Level
import org.assertj.core.api.Assertions.assertThat
import org.junit.BeforeClass
import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths
class NodeCmdLineOptionsTest {
private val parser = NodeStartup()
companion object {
private lateinit var workingDirectory: Path
private lateinit var buildDirectory: Path
@BeforeClass
@JvmStatic
fun initDirectories() {
workingDirectory = Paths.get(".").normalize().toAbsolutePath()
buildDirectory = workingDirectory.resolve("build")
}
}
@Test
fun `no command line arguments`() {
assertThat(parser.cmdLineOptions.baseDirectory.normalize().toAbsolutePath()).isEqualTo(workingDirectory)
assertThat(parser.cmdLineOptions.configFile.normalize().toAbsolutePath()).isEqualTo(workingDirectory / "node.conf")
assertThat(parser.verbose).isEqualTo(false)
assertThat(parser.loggingLevel).isEqualTo(Level.INFO)
assertThat(parser.cmdLineOptions.nodeRegistrationOption).isEqualTo(null)
assertThat(parser.cmdLineOptions.noLocalShell).isEqualTo(false)
assertThat(parser.cmdLineOptions.sshdServer).isEqualTo(false)
assertThat(parser.cmdLineOptions.justGenerateNodeInfo).isEqualTo(false)
assertThat(parser.cmdLineOptions.justGenerateRpcSslCerts).isEqualTo(false)
assertThat(parser.cmdLineOptions.bootstrapRaftCluster).isEqualTo(false)
assertThat(parser.cmdLineOptions.unknownConfigKeysPolicy).isEqualTo(UnknownConfigKeysPolicy.FAIL)
assertThat(parser.cmdLineOptions.devMode).isEqualTo(null)
assertThat(parser.cmdLineOptions.clearNetworkMapCache).isEqualTo(false)
}
}

View File

@ -25,6 +25,7 @@ import net.corda.node.services.Permissions
import net.corda.node.services.config.*
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.nodeapi.internal.PLATFORM_VERSION
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.addShutdownHook
@ -220,7 +221,8 @@ class DriverDSLImpl(
): CordaFuture<NodeHandle> {
val p2pAddress = portAllocation.nextHostAndPort()
// TODO: Derive name from the full picked name, don't just wrap the common name
val name = providedName ?: CordaX500Name("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB")
val name = providedName
?: CordaX500Name("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB")
val registrationFuture = if (compatibilityZone?.rootCert != null) {
// We don't need the network map to be available to be able to register the node
@ -295,7 +297,7 @@ class DriverDSLImpl(
"devMode" to false)
)).checkAndOverrideForInMemoryDB()
val versionInfo = VersionInfo(1, "1", "1", "1")
val versionInfo = VersionInfo(PLATFORM_VERSION, "1", "1", "1")
config.corda.certificatesDirectory.createDirectories()
// Create network root truststore.
val rootTruststorePath = config.corda.certificatesDirectory / "network-root-truststore.jks"
@ -632,7 +634,8 @@ class DriverDSLImpl(
}
}
}
val p2pReadyFuture = addressMustBeBoundFuture(executorService, config.corda.p2pAddress, process)
val effectiveP2PAddress = config.corda.messagingServerAddress ?: config.corda.p2pAddress
val p2pReadyFuture = addressMustBeBoundFuture(executorService, effectiveP2PAddress, process)
return p2pReadyFuture.flatMap {
val processDeathFuture = poll(executorService, "process death while waiting for RPC (${config.corda.myLegalName})") {
if (process.isAlive) null else process
@ -642,7 +645,7 @@ class DriverDSLImpl(
val networkMapFuture = executorService.fork { visibilityHandle.listen(rpc) }.flatMap { it }
firstOf(processDeathFuture, networkMapFuture) {
if (it == processDeathFuture) {
throw ListenProcessDeathException(config.corda.p2pAddress, process)
throw ListenProcessDeathException(effectiveP2PAddress, process)
}
// Will interrupt polling for process death as this is no longer relevant since the process been
// successfully started and reflected itself in the NetworkMap.
@ -717,6 +720,7 @@ class DriverDSLImpl(
executorService: ScheduledExecutorService,
config: NodeConfig
): CordaFuture<Pair<NodeWithInfo, Thread>> {
val effectiveP2PAddress = config.corda.messagingServerAddress ?: config.corda.p2pAddress
return executorService.fork {
log.info("Starting in-process Node ${config.corda.myLegalName.organisation}")
if (!(ManagementFactory.getRuntimeMXBean().inputArguments.any { it.contains("quasar") })) {
@ -733,7 +737,7 @@ class DriverDSLImpl(
}
nodeWithInfo to nodeThread
}.flatMap { nodeAndThread ->
addressMustBeBoundFuture(executorService, config.corda.p2pAddress).map { nodeAndThread }
addressMustBeBoundFuture(executorService, effectiveP2PAddress).map { nodeAndThread }
}
}
@ -917,7 +921,7 @@ class NetworkVisibilityController {
val (snapshot, updates) = rpc.networkMapFeed()
visibleNodeCount = snapshot.size
checkIfAllVisible()
subscription = updates.subscribe {
subscription = updates.subscribe({
when (it) {
is NetworkMapCache.MapChange.Added -> {
visibleNodeCount++
@ -931,7 +935,9 @@ class NetworkVisibilityController {
// Nothing to do here but better being exhaustive.
}
}
}
}, { _ ->
// Nothing to do on errors here.
})
return future
}

View File

@ -14,6 +14,7 @@ import net.corda.core.internal.openHttpConnection
import net.corda.core.internal.responseAs
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.*
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
@ -23,6 +24,7 @@ import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.testing.common.internal.testNetworkParameters
import java.io.InputStream
import java.net.URL
import java.security.PublicKey
@ -74,6 +76,8 @@ class CordaRPCProxyClient(private val targetHostAndPort: NetworkHostAndPort) : C
return doGet(targetHostAndPort, "registered-flows")
}
override val networkParameters: NetworkParameters get() = testNetworkParameters()
override fun stateMachinesSnapshot(): List<StateMachineInfo> {
TODO("not implemented")
}

View File

@ -8,7 +8,7 @@ import java.nio.file.Path
import java.nio.file.Paths
fun main(args: Array<String>) {
NetworkBootstrapperRunner().start(*args)
NetworkBootstrapperRunner().start(args)
}
class NetworkBootstrapperRunner : CordaCliWrapper("bootstrapper", "Bootstrap a local test Corda network using a set of node configuration files and CorDapp JARs") {
@ -24,7 +24,8 @@ class NetworkBootstrapperRunner : CordaCliWrapper("bootstrapper", "Bootstrap a l
@Option(names = ["--no-copy"], description = ["""Don't copy the CorDapp JARs into the nodes' "cordapps" directories."""])
private var noCopy: Boolean = false
override fun runProgram() {
override fun runProgram(): Int {
NetworkBootstrapper().bootstrap(dir.toAbsolutePath().normalize(), copyCordapps = !noCopy)
return 0 //exit code
}
}

View File

@ -10,5 +10,8 @@ dependencies {
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
// JAnsi: for drawing things to the terminal in nicely coloured ways.
compile "org.fusesource.jansi:jansi:$jansi_version"
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}

View File

@ -1,25 +0,0 @@
package net.corda.cliutils
import net.corda.core.internal.exists
import net.corda.core.internal.isReadable
import picocli.CommandLine
import java.nio.file.Path
/**
* When a config file is required as part of setup, use this class to check that it exists and is formatted correctly. Add it as
* `@CommandLine.Mixin
* lateinit var configParser: ConfigFilePathArgsParser`
* in your command class and then call `validate()`
*/
@CommandLine.Command(description = ["Parse configuration file. Checks if given configuration file exists"])
class ConfigFilePathArgsParser : Validated {
@CommandLine.Option(names = ["--config-file", "-f"], required = true, paramLabel = "FILE", description = ["The path to the config file"])
lateinit var configFile: Path
override fun validator(): List<String> {
val res = mutableListOf<String>()
if (!configFile.exists()) res += "Config file ${configFile.toAbsolutePath().normalize()} does not exist!"
if (!configFile.isReadable) res += "Config file ${configFile.toAbsolutePath().normalize()} is not readable"
return res
}
}

View File

@ -2,12 +2,15 @@ package net.corda.cliutils
import net.corda.core.internal.rootMessage
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.loggerFor
import org.apache.logging.log4j.Level
import org.fusesource.jansi.AnsiConsole
import picocli.CommandLine
import picocli.CommandLine.*
import kotlin.system.exitProcess
import java.util.*
import java.util.concurrent.Callable
/**
* When we have errors in command line flags that are not handled by picocli (e.g. non existing files), an error is thrown
@ -34,19 +37,46 @@ interface Validated {
logger.error(RED + "Exceptions when parsing command line arguments:")
logger.error(errors.joinToString("\n") + RESET)
CommandLine(this).usage(System.err)
exitProcess(1)
exitProcess(ExitCodes.FAILURE)
}
}
}
fun CordaCliWrapper.start(vararg args: String) {
/** This is generally covered by commons-lang. */
object CordaSystemUtils {
const val OS_NAME = "os.name"
const val MAC_PREFIX = "Mac"
const val WIN_PREFIX = "Windows"
fun isOsMac() = getOsName().startsWith(MAC_PREFIX)
fun isOsWindows() = getOsName().startsWith(WIN_PREFIX)
fun getOsName() = System.getProperty(OS_NAME)
}
fun CordaCliWrapper.start(args: Array<String>) {
// This line makes sure ANSI escapes work on Windows, where they aren't supported out of the box.
AnsiConsole.systemInstall()
val cmd = CommandLine(this)
this.args = args
cmd.commandSpec.name(alias)
cmd.commandSpec.usageMessage().description(description)
try {
cmd.parseWithHandlers(RunLast().useOut(System.out).useAnsi(Help.Ansi.AUTO),
DefaultExceptionHandler<List<Any>>().useErr(System.err).useAnsi(Help.Ansi.AUTO),
val defaultAnsiMode = if (CordaSystemUtils.isOsWindows()) { Help.Ansi.ON } else { Help.Ansi.AUTO }
val results = cmd.parseWithHandlers(RunLast().useOut(System.out).useAnsi(defaultAnsiMode),
DefaultExceptionHandler<List<Any>>().useErr(System.err).useAnsi(defaultAnsiMode),
*args)
// If an error code has been returned, use this and exit
results?.firstOrNull()?.let {
if (it is Int) {
exitProcess(it)
} else {
exitProcess(ExitCodes.FAILURE)
}
}
// If no results returned, picocli ran something without invoking the main program, e.g. --help or --version, so exit successfully
exitProcess(ExitCodes.SUCCESS)
} catch (e: ExecutionException) {
val throwable = e.cause ?: e
if (this.verbose) {
@ -54,7 +84,7 @@ fun CordaCliWrapper.start(vararg args: String) {
} else {
System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}")
}
exitProcess(1)
exitProcess(ExitCodes.FAILURE)
}
}
@ -72,8 +102,16 @@ fun CordaCliWrapper.start(vararg args: String) {
parameterListHeading = "%n@|bold,underline Parameters|@:%n%n",
optionListHeading = "%n@|bold,underline Options|@:%n%n",
commandListHeading = "%n@|bold,underline Commands|@:%n%n")
abstract class CordaCliWrapper(val alias: String, val description: String) : Runnable {
@Option(names = ["-v", "--verbose"], description = ["If set, prints logging to the console as well as to a file."])
abstract class CordaCliWrapper(val alias: String, val description: String) : Callable<Int> {
companion object {
private val logger by lazy { loggerFor<CordaCliWrapper>() }
}
// Raw args are provided for use in logging - this is a lateinit var rather than a constructor parameter as the class
// needs to be parameterless for autocomplete to work.
lateinit var args: Array<String>
@Option(names = ["-v", "--verbose", "--log-to-console"], description = ["If set, prints logging to the console as well as to a file."])
var verbose: Boolean = false
@Option(names = ["--logging-level"],
@ -88,7 +126,7 @@ abstract class CordaCliWrapper(val alias: String, val description: String) : Run
// This needs to be called before loggers (See: NodeStartup.kt:51 logger called by lazy, initLogging happens before).
// Node's logging is more rich. In corda configurations two properties, defaultLoggingLevel and consoleLogLevel, are usually used.
private fun initLogging() {
open fun initLogging() {
val loggingLevel = loggingLevel.name().toLowerCase(Locale.ENGLISH)
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
if (verbose) {
@ -96,13 +134,15 @@ abstract class CordaCliWrapper(val alias: String, val description: String) : Run
}
}
// Override this function with the actual method to be run once all the arguments have been parsed
abstract fun runProgram()
// Override this function with the actual method to be run once all the arguments have been parsed. The return number
// is the exit code to be returned
abstract fun runProgram(): Int
final override fun run() {
installShellExtensionsParser.installOrUpdateShellExtensions(alias, this.javaClass.name)
override fun call(): Int {
initLogging()
runProgram()
logger.info("Application Args: ${args.joinToString(" ")}")
installShellExtensionsParser.installOrUpdateShellExtensions(alias, this.javaClass.name)
return runProgram()
}
}

View File

@ -9,13 +9,17 @@ import picocli.CommandLine
*/
class CordaVersionProvider : CommandLine.IVersionProvider {
companion object {
val releaseVersion: String by lazy { Manifests.read("Corda-Release-Version") }
val revision: String by lazy { Manifests.read("Corda-Revision") }
private fun manifestValue(name: String): String? = if (Manifests.exists(name)) Manifests.read(name) else null
val releaseVersion: String by lazy { manifestValue("Corda-Release-Version") ?: "Unknown" }
val revision: String by lazy { manifestValue("Corda-Revision") ?: "Unknown" }
val vendor: String by lazy { manifestValue("Corda-Vendor") ?: "Unknown" }
val platformVersion: Int by lazy { manifestValue("Corda-Platform-Version")?.toInt() ?: 1 }
}
override fun getVersion(): Array<String> {
return if (Manifests.exists("Corda-Release-Version") && Manifests.exists("Corda-Revision")) {
arrayOf("Version: $releaseVersion", "Revision: $revision")
arrayOf("Version: $releaseVersion", "Revision: $revision", "Platform Version: $platformVersion", "Vendor: $vendor")
} else {
arrayOf("No version data is available in the MANIFEST file.")
}

View File

@ -0,0 +1,8 @@
package net.corda.cliutils
open class ExitCodes {
companion object {
const val SUCCESS: Int = 0
const val FAILURE: Int = 1
}
}

View File

@ -53,7 +53,14 @@ private class ShellExtensionsGenerator(val alias: String, val className: String)
}
private val userHome: Path by lazy { Paths.get(System.getProperty("user.home")) }
private val jarLocation: Path by lazy { this.javaClass.location.toPath() }
private val jarLocation: Path by lazy {
val capsuleJarProperty = System.getProperty("capsule.jar")
if (capsuleJarProperty != null) {
Paths.get(capsuleJarProperty)
} else {
this.javaClass.location.toPath()
}
}
// If on Windows, Path.toString() returns a path with \ instead of /, but for bash Windows users we want to convert those back to /'s
private fun Path.toStringWithDeWindowsfication(): String = this.toAbsolutePath().toString().replace("\\", "/")
@ -114,7 +121,6 @@ private class ShellExtensionsGenerator(val alias: String, val className: String)
}
}
@CommandLine.Command(description = [""])
class InstallShellExtensionsParser {
@CommandLine.Option(names = ["--install-shell-extensions"], description = ["Install alias and autocompletion for bash and zsh"])
var installShellExtensions: Boolean = false

View File

@ -10,6 +10,7 @@ import java.util.logging.*;
* to be added to the JVM's command line.
*/
public class LoggingConfig {
private static final String LOGGING_CONFIG = "logging.properties";
public LoggingConfig() throws IOException {
try (InputStream input = getLoggingProperties()) {
@ -20,10 +21,11 @@ public class LoggingConfig {
private static InputStream getLoggingProperties() throws IOException {
ClassLoader classLoader = LoggingConfig.class.getClassLoader();
InputStream input = classLoader.getResourceAsStream("logging.properties");
InputStream input = classLoader.getResourceAsStream(LOGGING_CONFIG);
if (input == null) {
Path javaHome = Paths.get(System.getProperty("java.home"));
input = Files.newInputStream(javaHome.resolve("lib").resolve("logging.properties"));
// Use the default JUL logging configuration properties instead.
Path logging = Paths.get(System.getProperty("java.home"), "lib", LOGGING_CONFIG);
input = Files.newInputStream(logging, StandardOpenOption.READ);
}
return input;
}

View File

@ -8,7 +8,7 @@ apply plugin: 'com.jfrog.artifactory'
description 'Node Explorer'
configurations {
runtimeArtifacts.extendsFrom runtime
runtimeArtifacts.extendsFrom runtimeClasspath
}
// Force the Caplet to target Java 6. This ensures that running 'java -jar explorer.jar' on any Java 6 VM upwards
@ -19,12 +19,12 @@ configurations {
sourceCompatibility = 1.6
targetCompatibility = 1.6
task buildExplorerJAR(type: FatCapsule, dependsOn: project(':tools:explorer').compileJava) {
task buildExplorerJAR(type: FatCapsule, dependsOn: project(':tools:explorer').tasks.jar) {
applicationClass 'net.corda.explorer.Main'
archiveName "node-explorer-${corda_release_version}.jar"
applicationSource = files(
project(':tools:explorer').configurations.runtime,
project(':tools:explorer').jar,
project(':tools:explorer').configurations.runtimeClasspath,
project(':tools:explorer').tasks.jar,
project(':tools:explorer').sourceSets.main.java.outputDir.toString() + '/ExplorerCaplet.class'
)
classifier 'fat'
@ -54,6 +54,7 @@ artifacts {
jar {
classifier "ignore"
enabled = false
}
publish {

View File

@ -1,6 +1,6 @@
/**
* This build.gradle exists to publish our capsule (executable fat jar) to maven. It cannot be placed in the
* node project because the bintray plugin cannot publish two modules from one project.
* webserver project because the bintray plugin cannot publish two modules from one project.
*/
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'us.kirchmeier.capsule'
@ -26,18 +26,20 @@ dependencies {
sourceCompatibility = 1.6
targetCompatibility = 1.6
task buildWebserverJar(type: FatCapsule, dependsOn: project(':node').compileJava) {
jar.enabled = false
task buildWebserverJar(type: FatCapsule, dependsOn: project(':node').tasks.jar) {
applicationClass 'net.corda.webserver.WebServer'
archiveName "corda-webserver-${corda_release_version}.jar"
applicationSource = files(
project(':webserver').configurations.runtime,
project(':webserver').jar,
project(':node').sourceSets.main.java.outputDir.toString() + '/CordaCaplet.class',
project(':node').sourceSets.main.java.outputDir.toString() + '/CordaCaplet$1.class',
"$rootDir/config/dev/log4j2.xml",
"$rootDir/node/build/resources/main/reference.conf"
project(':webserver').configurations.runtimeClasspath,
project(':webserver').tasks.jar,
project(':node').sourceSets.main.java.outputDir.toString() + '/CordaCaplet.class',
project(':node').sourceSets.main.java.outputDir.toString() + '/CordaCaplet$1.class',
project(':node').buildDir.toString() + '/resources/main/reference.conf',
"$rootDir/config/dev/log4j2.xml",
project(':node:capsule').projectDir.toString() + '/NOTICE' // Copy CDDL notice
)
from 'NOTICE' // Copy CDDL notice
from configurations.capsuleRuntime.files.collect { zipTree(it) }
capsuleManifest {
@ -57,10 +59,12 @@ task buildWebserverJar(type: FatCapsule, dependsOn: project(':node').compileJava
}
}
assemble.dependsOn buildWebserverJar
artifacts {
runtimeArtifacts buildWebserverJar
publish buildWebserverJar {
classifier ""
classifier ''
}
}