mirror of
https://github.com/corda/corda.git
synced 2025-04-20 17:11:26 +00:00
Merge pull request #1374 from corda/anthony-os-merge-20180906
O/S Merge 20180906
This commit is contained in:
commit
458bedd936
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
104
djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt
Normal file
104
djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt
Normal 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
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -12,5 +12,6 @@ interface MemberInformation {
|
||||
val className: String
|
||||
val memberName: String
|
||||
val signature: String
|
||||
@JvmDefault
|
||||
val reference: String get() = "$className.$memberName:$signature"
|
||||
}
|
||||
|
@ -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})"
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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, "")
|
||||
|
@ -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 {
|
||||
|
17
djvm/src/test/resources/log4j2-test.xml
Normal file
17
djvm/src/test/resources/log4j2-test.xml
Normal 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>
|
@ -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>
|
@ -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.
|
||||
|
@ -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
|
||||
--------------------
|
||||
|
@ -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,
|
||||
|
90
docs/source/design/targetversion/design.md
Normal file
90
docs/source/design/targetversion/design.md
Normal 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.
|
@ -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:
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
@ -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()}")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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 ''
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
135
node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt
Normal file
135
node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt
Normal 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)
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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) }
|
||||
|
@ -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) {
|
||||
|
@ -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.",
|
||||
|
@ -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?
|
||||
|
@ -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)
|
||||
}
|
@ -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?,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.")
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package net.corda.cliutils
|
||||
|
||||
open class ExitCodes {
|
||||
companion object {
|
||||
const val SUCCESS: Int = 0
|
||||
const val FAILURE: Int = 1
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 ''
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user