mirror of
https://github.com/corda/corda.git
synced 2024-12-30 01:39:04 +00:00
Cleanup: move node startup code into a class that can be sub-classed to customise behaviour.
This commit is contained in:
parent
0cb41c55a5
commit
28b850ec85
@ -8,6 +8,7 @@ import net.corda.core.getOrThrow
|
|||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.utilities.ALICE
|
import net.corda.core.utilities.ALICE
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
|
import net.corda.node.internal.NodeStartup
|
||||||
import net.corda.node.services.startFlowPermission
|
import net.corda.node.services.startFlowPermission
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
@ -43,7 +44,7 @@ class BootTests {
|
|||||||
startNode(ALICE.name).getOrThrow()
|
startNode(ALICE.name).getOrThrow()
|
||||||
}
|
}
|
||||||
// We count the number of nodes that wrote into the logfile by counting "Logs can be found in"
|
// We count the number of nodes that wrote into the logfile by counting "Logs can be found in"
|
||||||
val numberOfNodesThatLogged = Files.lines(logFile.toPath()).filter { it.contains(LOGS_CAN_BE_FOUND_IN_STRING) }.count()
|
val numberOfNodesThatLogged = Files.lines(logFile.toPath()).filter { NodeStartup.LOGS_CAN_BE_FOUND_IN_STRING in it }.count()
|
||||||
assertEquals(1, numberOfNodesThatLogged)
|
assertEquals(1, numberOfNodesThatLogged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,281 +2,11 @@
|
|||||||
|
|
||||||
package net.corda.node
|
package net.corda.node
|
||||||
|
|
||||||
import com.jcabi.manifests.Manifests
|
import net.corda.node.internal.NodeStartup
|
||||||
import com.typesafe.config.ConfigException
|
|
||||||
import joptsimple.OptionException
|
|
||||||
import net.corda.core.*
|
|
||||||
import net.corda.core.crypto.commonName
|
|
||||||
import net.corda.core.crypto.orgName
|
|
||||||
import net.corda.core.node.VersionInfo
|
|
||||||
import net.corda.core.utilities.Emoji
|
|
||||||
import net.corda.node.internal.Node
|
|
||||||
import net.corda.node.internal.enforceSingleNodeIsRunning
|
|
||||||
import net.corda.node.services.config.FullNodeConfiguration
|
|
||||||
import net.corda.node.services.transactions.bftSMaRtSerialFilter
|
|
||||||
import net.corda.node.shell.InteractiveShell
|
|
||||||
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
|
|
||||||
import net.corda.node.utilities.registration.NetworkRegistrationHelper
|
|
||||||
import org.fusesource.jansi.Ansi
|
|
||||||
import org.fusesource.jansi.AnsiConsole
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.slf4j.bridge.SLF4JBridgeHandler
|
|
||||||
import java.lang.management.ManagementFactory
|
|
||||||
import java.net.InetAddress
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
private var renderBasicInfoToConsole = true
|
|
||||||
|
|
||||||
/** Used for useful info that we always want to show, even when not logging to the console */
|
|
||||||
fun printBasicNodeInfo(description: String, info: String? = null) {
|
|
||||||
val msg = if (info == null) description else "${description.padEnd(40)}: $info"
|
|
||||||
val loggerName = if (renderBasicInfoToConsole) "BasicInfo" else "Main"
|
|
||||||
LoggerFactory.getLogger(loggerName).info(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
val LOGS_DIRECTORY_NAME = "logs"
|
|
||||||
val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
|
|
||||||
private val log by lazy { LoggerFactory.getLogger("Main") }
|
|
||||||
|
|
||||||
private fun initLogging(cmdlineOptions: CmdLineOptions) {
|
|
||||||
val loggingLevel = cmdlineOptions.loggingLevel.name.toLowerCase(Locale.ENGLISH)
|
|
||||||
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
|
|
||||||
if (cmdlineOptions.logToConsole) {
|
|
||||||
System.setProperty("consoleLogLevel", loggingLevel)
|
|
||||||
renderBasicInfoToConsole = false
|
|
||||||
}
|
|
||||||
System.setProperty("log-path", (cmdlineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString())
|
|
||||||
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
|
|
||||||
SLF4JBridgeHandler.install()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val startTime = System.currentTimeMillis()
|
// Pass the arguments to the Node factory. In the Enterprise edition, this line is modified to point to a subclass.
|
||||||
assertCanNormalizeEmptyPath()
|
// 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.
|
||||||
val argsParser = ArgsParser()
|
NodeStartup(args).run()
|
||||||
|
|
||||||
val cmdlineOptions = try {
|
|
||||||
argsParser.parse(*args)
|
|
||||||
} catch (ex: OptionException) {
|
|
||||||
println("Invalid command line arguments: ${ex.message}")
|
|
||||||
argsParser.printHelp(System.out)
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
initLogging(cmdlineOptions)
|
|
||||||
// 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
|
|
||||||
|
|
||||||
val versionInfo = VersionInfo(
|
|
||||||
manifestValue("Corda-Platform-Version")?.toInt() ?: 1,
|
|
||||||
manifestValue("Corda-Release-Version") ?: "Unknown",
|
|
||||||
manifestValue("Corda-Revision") ?: "Unknown",
|
|
||||||
manifestValue("Corda-Vendor") ?: "Unknown"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (cmdlineOptions.isVersion) {
|
|
||||||
println("${versionInfo.vendor} ${versionInfo.releaseVersion}")
|
|
||||||
println("Revision ${versionInfo.revision}")
|
|
||||||
println("Platform Version ${versionInfo.platformVersion}")
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maybe render command line help.
|
|
||||||
if (cmdlineOptions.help) {
|
|
||||||
argsParser.printHelp(System.out)
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
drawBanner(versionInfo)
|
|
||||||
|
|
||||||
printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
|
|
||||||
|
|
||||||
val conf = try {
|
|
||||||
cmdlineOptions.loadConfig()
|
|
||||||
} catch (e: ConfigException) {
|
|
||||||
println("Unable to load the configuration file: ${e.rootCause.message}")
|
|
||||||
exitProcess(2)
|
|
||||||
}
|
|
||||||
// TODO: Replace with "::funcName" notation when Dokka is upgraded to not crash when analysing the notation
|
|
||||||
val filterFunc = if (conf.bftReplicaId != null) { it: Class<*> -> bftSMaRtSerialFilter(it) } else { it: Class<*> -> defaultSerialFilter(it) }
|
|
||||||
SerialFilter.install(filterFunc)
|
|
||||||
if (cmdlineOptions.isRegistration) {
|
|
||||||
println()
|
|
||||||
println("******************************************************************")
|
|
||||||
println("* *")
|
|
||||||
println("* Registering as a new participant with Corda network *")
|
|
||||||
println("* *")
|
|
||||||
println("******************************************************************")
|
|
||||||
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(conf.certificateSigningService)).buildKeystore()
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Vendor: ${versionInfo.vendor}")
|
|
||||||
log.info("Release: ${versionInfo.releaseVersion}")
|
|
||||||
log.info("Platform Version: ${versionInfo.platformVersion}")
|
|
||||||
log.info("Revision: ${versionInfo.revision}")
|
|
||||||
val info = ManagementFactory.getRuntimeMXBean()
|
|
||||||
log.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this
|
|
||||||
log.info("Main class: ${FullNodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}")
|
|
||||||
log.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}")
|
|
||||||
log.info("Application Args: ${args.joinToString(" ")}")
|
|
||||||
log.info("bootclasspath: ${info.bootClassPath}")
|
|
||||||
log.info("classpath: ${info.classPath}")
|
|
||||||
log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
|
|
||||||
log.info("Machine: ${lookupMachineNameAndMaybeWarn()}")
|
|
||||||
log.info("Working Directory: ${cmdlineOptions.baseDirectory}")
|
|
||||||
val agentProperties = sun.misc.VMSupport.getAgentProperties()
|
|
||||||
if (agentProperties.containsKey("sun.jdwp.listenerAddress")) {
|
|
||||||
log.info("Debug port: ${agentProperties.getProperty("sun.jdwp.listenerAddress")}")
|
|
||||||
}
|
|
||||||
log.info("Starting as node on ${conf.p2pAddress}")
|
|
||||||
|
|
||||||
try {
|
|
||||||
cmdlineOptions.baseDirectory.createDirectories()
|
|
||||||
|
|
||||||
val node = conf.createNode(versionInfo)
|
|
||||||
node.start()
|
|
||||||
printPluginsAndServices(node)
|
|
||||||
|
|
||||||
node.networkMapRegistrationFuture.success {
|
|
||||||
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
|
|
||||||
// TODO: Replace this with a standard function to get an unambiguous rendering of the X.500 name.
|
|
||||||
val name = node.info.legalIdentity.name.orgName ?: node.info.legalIdentity.name.commonName
|
|
||||||
printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec")
|
|
||||||
|
|
||||||
// Don't start the shell if there's no console attached.
|
|
||||||
val runShell = !cmdlineOptions.noLocalShell && System.console() != null
|
|
||||||
node.startupComplete then {
|
|
||||||
try {
|
|
||||||
InteractiveShell.startShell(cmdlineOptions.baseDirectory, runShell, cmdlineOptions.sshdServer, node)
|
|
||||||
} catch(e: Throwable) {
|
|
||||||
log.error("Shell failed to start", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} failure {
|
|
||||||
log.error("Error during network map registration", it)
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
node.run()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (e.message?.startsWith("Unknown named curve:") ?: false) {
|
|
||||||
log.error("Exception during node startup - ${e.message}. " +
|
|
||||||
"This is a known OpenJDK issue on some Linux distributions, please use OpenJDK from zulu.org or Oracle JDK.")
|
|
||||||
} else
|
|
||||||
log.error("Exception during node startup", e)
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun lookupMachineNameAndMaybeWarn(): String {
|
|
||||||
val start = System.currentTimeMillis()
|
|
||||||
val hostName: String = InetAddress.getLocalHost().hostName
|
|
||||||
val elapsed = System.currentTimeMillis() - start
|
|
||||||
if (elapsed > 1000 && hostName.endsWith(".local")) {
|
|
||||||
// User is probably on macOS and experiencing this problem: http://stackoverflow.com/questions/10064581/how-can-i-eliminate-slow-resolving-loading-of-localhost-virtualhost-a-2-3-secon
|
|
||||||
//
|
|
||||||
// Also see https://bugs.openjdk.java.net/browse/JDK-8143378
|
|
||||||
val messages = listOf(
|
|
||||||
"Your computer took over a second to resolve localhost due an incorrect configuration. Corda will work but start very slowly until this is fixed. ",
|
|
||||||
"Please see https://docs.corda.net/troubleshooting.html#slow-localhost-resolution for information on how to fix this. ",
|
|
||||||
"It will only take a few seconds for you to resolve."
|
|
||||||
)
|
|
||||||
log.warn(messages.joinToString(""))
|
|
||||||
Emoji.renderIfSupported {
|
|
||||||
print(Ansi.ansi().fgBrightRed())
|
|
||||||
messages.forEach {
|
|
||||||
println("${Emoji.sleepingFace}$it")
|
|
||||||
}
|
|
||||||
print(Ansi.ansi().reset())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hostName
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun assertCanNormalizeEmptyPath() {
|
|
||||||
// Check we're not running a version of Java with a known bug: https://github.com/corda/corda/issues/83
|
|
||||||
try {
|
|
||||||
Paths.get("").normalize()
|
|
||||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
|
||||||
failStartUp("You are using a version of Java that is not supported (${System.getProperty("java.version")}). Please upgrade to the latest version.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun failStartUp(message: String): Nothing {
|
|
||||||
println(message)
|
|
||||||
println("Corda will now exit...")
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun printPluginsAndServices(node: Node) {
|
|
||||||
node.configuration.extraAdvertisedServiceIds.let {
|
|
||||||
if (it.isNotEmpty()) printBasicNodeInfo("Providing network services", it.joinToString())
|
|
||||||
}
|
|
||||||
val plugins = node.pluginRegistries
|
|
||||||
.map { it.javaClass.name }
|
|
||||||
.filterNot { it.startsWith("net.corda.node.") || it.startsWith("net.corda.core.") || it.startsWith("net.corda.nodeapi.") }
|
|
||||||
.map { it.substringBefore('$') }
|
|
||||||
if (plugins.isNotEmpty())
|
|
||||||
printBasicNodeInfo("Loaded plugins", plugins.joinToString())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun messageOfTheDay(): Pair<String, String> {
|
|
||||||
val messages = arrayListOf(
|
|
||||||
"The only distributed ledger that pays\nhomage to Pac Man in its logo.",
|
|
||||||
"You know, I was a banker\nonce ... but I lost interest. ${Emoji.bagOfCash}",
|
|
||||||
"It's not who you know, it's who you know\nknows what you know you know.",
|
|
||||||
"It runs on the JVM because QuickBasic\nis apparently not 'professional' enough.",
|
|
||||||
"\"It's OK computer, I go to sleep after\ntwenty minutes of inactivity too!\"",
|
|
||||||
"It's kind of like a block chain but\ncords sounded healthier than chains.",
|
|
||||||
"Computer science and finance together.\nYou should see our crazy Christmas parties!",
|
|
||||||
"I met my bank manager yesterday and asked\nto check my balance ... he pushed me over!",
|
|
||||||
"A banker with nobody around may find\nthemselves .... a-loan! <applause>",
|
|
||||||
"Whenever I go near my bank I get\nwithdrawal symptoms ${Emoji.coolGuy}",
|
|
||||||
"There was an earthquake in California,\na local bank went into de-fault.",
|
|
||||||
"I asked for insurance if the nearby\nvolcano erupted. They said I'd be covered.",
|
|
||||||
"I had an account with a bank in the\nNorth Pole, but they froze all my assets ${Emoji.santaClaus}",
|
|
||||||
"Check your contracts carefully. The fine print\nis usually a clause for suspicion ${Emoji.santaClaus}",
|
|
||||||
"Some bankers are generous ...\nto a vault! ${Emoji.bagOfCash} ${Emoji.coolGuy}",
|
|
||||||
"What you can buy for a dollar these\ndays is absolute non-cents! ${Emoji.bagOfCash}",
|
|
||||||
"Old bankers never die, they\njust... pass the buck",
|
|
||||||
"I won $3M on the lottery so I donated a quarter\nof it to charity. Now I have $2,999,999.75.",
|
|
||||||
"There are two rules for financial success:\n1) Don't tell everything you know.",
|
|
||||||
"Top tip: never say \"oops\", instead\nalways say \"Ah, Interesting!\"",
|
|
||||||
"Computers are useless. They can only\ngive you answers. -- Picasso"
|
|
||||||
)
|
|
||||||
if (Emoji.hasEmojiTerminal)
|
|
||||||
messages += "Kind of like a regular database but\nwith emojis, colours and ascii art. ${Emoji.coolGuy}"
|
|
||||||
val (a, b) = messages.randomOrNull()!!.split('\n')
|
|
||||||
return Pair(a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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 (msg1, msg2) = messageOfTheDay()
|
|
||||||
|
|
||||||
println(Ansi.ansi().fgBrightRed().a("""
|
|
||||||
______ __
|
|
||||||
/ ____/ _________/ /___ _
|
|
||||||
/ / __ / ___/ __ / __ `/ """).fgBrightBlue().a(msg1).newline().fgBrightRed().a(
|
|
||||||
"/ /___ /_/ / / / /_/ / /_/ / ").fgBrightBlue().a(msg2).newline().fgBrightRed().a(
|
|
||||||
"""\____/ /_/ \__,_/\__,_/""").reset().newline().newline().fgBrightDefault().bold().
|
|
||||||
a("--- ${versionInfo.vendor} ${versionInfo.releaseVersion} (${versionInfo.revision.take(7)}) -----------------------------------------------").
|
|
||||||
newline().
|
|
||||||
newline().
|
|
||||||
a("${Emoji.books}New! ").reset().a("Training now available worldwide, see https://corda.net/corda-training/").
|
|
||||||
newline().
|
|
||||||
reset())
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.node
|
package net.corda.node
|
||||||
|
|
||||||
|
import net.corda.node.internal.Node
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Proxy
|
import java.lang.reflect.Proxy
|
||||||
@ -24,7 +25,7 @@ internal object SerialFilter {
|
|||||||
// JDK 8u121 is the earliest JDK8 JVM that supports this functionality.
|
// JDK 8u121 is the earliest JDK8 JVM that supports this functionality.
|
||||||
filterInterface = getFilterInterface("java.io")
|
filterInterface = getFilterInterface("java.io")
|
||||||
?: getFilterInterface("sun.misc")
|
?: getFilterInterface("sun.misc")
|
||||||
?: failStartUp("Corda forbids Java deserialisation. Please upgrade to at least JDK 8u121.")
|
?: Node.failStartUp("Corda forbids Java deserialisation. Please upgrade to at least JDK 8u121.")
|
||||||
serialClassGetter = Class.forName("${filterInterface.name}\$FilterInfo").getMethod("serialClass")
|
serialClassGetter = Class.forName("${filterInterface.name}\$FilterInfo").getMethod("serialClass")
|
||||||
val statusEnum = Class.forName("${filterInterface.name}\$Status")
|
val statusEnum = Class.forName("${filterInterface.name}\$Status")
|
||||||
undecided = statusEnum.getField("UNDECIDED").get(null)
|
undecided = statusEnum.getField("UNDECIDED").get(null)
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package net.corda.node.internal
|
|
||||||
|
|
||||||
import net.corda.nodeapi.internal.addShutdownHook
|
|
||||||
import net.corda.core.div
|
|
||||||
import java.io.RandomAccessFile
|
|
||||||
import java.lang.management.ManagementFactory
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function enforces that only a single node is running using the given [baseDirectory] by using a file lock.
|
|
||||||
*/
|
|
||||||
fun enforceSingleNodeIsRunning(baseDirectory: Path) {
|
|
||||||
// Write out our process ID (which may or may not resemble a UNIX process id - to us it's just a string) to a
|
|
||||||
// file that we'll do our best to delete on exit. But if we don't, it'll be overwritten next time. If it already
|
|
||||||
// exists, we try to take the file lock first before replacing it and if that fails it means we're being started
|
|
||||||
// twice with the same directory: that's a user error and we should bail out.
|
|
||||||
val pidFile = (baseDirectory / "process-id").toFile()
|
|
||||||
pidFile.createNewFile()
|
|
||||||
pidFile.deleteOnExit()
|
|
||||||
val pidFileRw = RandomAccessFile(pidFile, "rw")
|
|
||||||
val pidFileLock = pidFileRw.channel.tryLock()
|
|
||||||
if (pidFileLock == null) {
|
|
||||||
println("It appears there is already a node running with the specified data directory $baseDirectory")
|
|
||||||
println("Shut that other node down and try again. It may have process ID ${pidFile.readText()}")
|
|
||||||
System.exit(1)
|
|
||||||
}
|
|
||||||
// Avoid the lock being garbage collected. We don't really need to release it as the OS will do so for us
|
|
||||||
// when our process shuts down, but we try in stop() anyway just to be nice.
|
|
||||||
addShutdownHook {
|
|
||||||
pidFileLock.release()
|
|
||||||
}
|
|
||||||
val ourProcessID: String = ManagementFactory.getRuntimeMXBean().name.split("@")[0]
|
|
||||||
pidFileRw.setLength(0)
|
|
||||||
pidFileRw.write(ourProcessID.toByteArray())
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ import com.google.common.net.HostAndPort
|
|||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import net.corda.nodeapi.internal.ShutdownHook
|
|
||||||
import net.corda.core.flatMap
|
import net.corda.core.flatMap
|
||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
import net.corda.core.minutes
|
import net.corda.core.minutes
|
||||||
@ -18,7 +17,6 @@ import net.corda.core.seconds
|
|||||||
import net.corda.core.success
|
import net.corda.core.success
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.core.utilities.trace
|
import net.corda.core.utilities.trace
|
||||||
import net.corda.node.printBasicNodeInfo
|
|
||||||
import net.corda.node.serialization.NodeClock
|
import net.corda.node.serialization.NodeClock
|
||||||
import net.corda.node.services.RPCUserService
|
import net.corda.node.services.RPCUserService
|
||||||
import net.corda.node.services.RPCUserServiceImpl
|
import net.corda.node.services.RPCUserServiceImpl
|
||||||
@ -39,16 +37,19 @@ import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER
|
|||||||
import net.corda.nodeapi.ArtemisMessagingComponent.NetworkMapAddress
|
import net.corda.nodeapi.ArtemisMessagingComponent.NetworkMapAddress
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.nodeapi.ConnectionDirection
|
||||||
|
import net.corda.nodeapi.internal.ShutdownHook
|
||||||
import net.corda.nodeapi.internal.addShutdownHook
|
import net.corda.nodeapi.internal.addShutdownHook
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
||||||
import org.apache.activemq.artemis.api.core.client.ClientMessage
|
import org.apache.activemq.artemis.api.core.client.ClientMessage
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.management.ObjectName
|
import javax.management.ObjectName
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Node manages a standalone server that takes part in the P2P network. It creates the services found in [ServiceHub],
|
* A Node manages a standalone server that takes part in the P2P network. It creates the services found in [ServiceHub],
|
||||||
@ -59,12 +60,26 @@ import javax.management.ObjectName
|
|||||||
* but nodes are not required to advertise services they run (hence subset).
|
* but nodes are not required to advertise services they run (hence subset).
|
||||||
* @param clock The clock used within the node and by all flows etc.
|
* @param clock The clock used within the node and by all flows etc.
|
||||||
*/
|
*/
|
||||||
class Node(override val configuration: FullNodeConfiguration,
|
open class Node(override val configuration: FullNodeConfiguration,
|
||||||
advertisedServices: Set<ServiceInfo>,
|
advertisedServices: Set<ServiceInfo>,
|
||||||
val versionInfo: VersionInfo,
|
val versionInfo: VersionInfo,
|
||||||
clock: Clock = NodeClock()) : AbstractNode(configuration, advertisedServices, clock) {
|
clock: Clock = NodeClock()) : AbstractNode(configuration, advertisedServices, clock) {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<Node>()
|
private val logger = loggerFor<Node>()
|
||||||
|
var renderBasicInfoToConsole = true
|
||||||
|
|
||||||
|
/** Used for useful info that we always want to show, even when not logging to the console */
|
||||||
|
fun printBasicNodeInfo(description: String, info: String? = null) {
|
||||||
|
val msg = if (info == null) description else "${description.padEnd(40)}: $info"
|
||||||
|
val loggerName = if (renderBasicInfoToConsole) "BasicInfo" else "Main"
|
||||||
|
LoggerFactory.getLogger(loggerName).info(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun failStartUp(message: String): Nothing {
|
||||||
|
println(message)
|
||||||
|
println("Corda will now exit...")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val log: Logger get() = logger
|
override val log: Logger get() = logger
|
||||||
|
330
node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt
Normal file
330
node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
package net.corda.node.internal
|
||||||
|
|
||||||
|
import com.jcabi.manifests.Manifests
|
||||||
|
import com.typesafe.config.ConfigException
|
||||||
|
import joptsimple.OptionException
|
||||||
|
import net.corda.core.*
|
||||||
|
import net.corda.core.crypto.commonName
|
||||||
|
import net.corda.core.crypto.orgName
|
||||||
|
import net.corda.core.node.VersionInfo
|
||||||
|
import net.corda.core.node.services.ServiceInfo
|
||||||
|
import net.corda.core.utilities.Emoji
|
||||||
|
import net.corda.core.utilities.loggerFor
|
||||||
|
import net.corda.node.ArgsParser
|
||||||
|
import net.corda.node.CmdLineOptions
|
||||||
|
import net.corda.node.SerialFilter
|
||||||
|
import net.corda.node.defaultSerialFilter
|
||||||
|
import net.corda.node.serialization.NodeClock
|
||||||
|
import net.corda.node.services.config.FullNodeConfiguration
|
||||||
|
import net.corda.node.services.transactions.bftSMaRtSerialFilter
|
||||||
|
import net.corda.node.shell.InteractiveShell
|
||||||
|
import net.corda.node.utilities.TestClock
|
||||||
|
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
|
||||||
|
import net.corda.node.utilities.registration.NetworkRegistrationHelper
|
||||||
|
import net.corda.nodeapi.internal.addShutdownHook
|
||||||
|
import org.fusesource.jansi.Ansi
|
||||||
|
import org.fusesource.jansi.AnsiConsole
|
||||||
|
import org.slf4j.bridge.SLF4JBridgeHandler
|
||||||
|
import sun.misc.VMSupport
|
||||||
|
import java.io.RandomAccessFile
|
||||||
|
import java.lang.management.ManagementFactory
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
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>) {
|
||||||
|
companion object {
|
||||||
|
private val logger by lazy { loggerFor<Node>() }
|
||||||
|
val LOGS_DIRECTORY_NAME = "logs"
|
||||||
|
val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun run() {
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
assertCanNormalizeEmptyPath()
|
||||||
|
val (argsParser, cmdlineOptions) = parseArguments()
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
initLogging(cmdlineOptions)
|
||||||
|
|
||||||
|
val versionInfo = getVersionInfo()
|
||||||
|
|
||||||
|
if (cmdlineOptions.isVersion) {
|
||||||
|
println("${versionInfo.vendor} ${versionInfo.releaseVersion}")
|
||||||
|
println("Revision ${versionInfo.revision}")
|
||||||
|
println("Platform Version ${versionInfo.platformVersion}")
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe render command line help.
|
||||||
|
if (cmdlineOptions.help) {
|
||||||
|
argsParser.printHelp(System.out)
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawBanner(versionInfo)
|
||||||
|
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
|
||||||
|
val conf = loadConfigFile(cmdlineOptions)
|
||||||
|
banJavaSerialisation(conf)
|
||||||
|
preNetworkRegistration()
|
||||||
|
maybeRegisterWithNetworkAndExit(cmdlineOptions, conf)
|
||||||
|
logStartupInfo(versionInfo, cmdlineOptions, conf)
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmdlineOptions.baseDirectory.createDirectories()
|
||||||
|
startNode(conf, versionInfo, startTime, cmdlineOptions)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (e.message?.startsWith("Unknown named curve:") ?: false) {
|
||||||
|
logger.error("Exception during node startup - ${e.message}. " +
|
||||||
|
"This is a known OpenJDK issue on some Linux distributions, please use OpenJDK from zulu.org or Oracle JDK.")
|
||||||
|
} else
|
||||||
|
logger.error("Exception during node startup", e)
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
open protected fun preNetworkRegistration() = Unit
|
||||||
|
|
||||||
|
open protected fun createNode(conf: FullNodeConfiguration, versionInfo: VersionInfo, services: Set<ServiceInfo>): Node {
|
||||||
|
return Node(conf, services, versionInfo, if (conf.useTestClock) TestClock() else NodeClock())
|
||||||
|
}
|
||||||
|
|
||||||
|
open protected fun startNode(conf: FullNodeConfiguration, versionInfo: VersionInfo, startTime: Long, cmdlineOptions: CmdLineOptions) {
|
||||||
|
val advertisedServices = conf.calculateServices()
|
||||||
|
val node = createNode(conf, versionInfo, advertisedServices)
|
||||||
|
node.start()
|
||||||
|
printPluginsAndServices(node)
|
||||||
|
|
||||||
|
node.networkMapRegistrationFuture.success {
|
||||||
|
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
|
||||||
|
// TODO: Replace this with a standard function to get an unambiguous rendering of the X.500 name.
|
||||||
|
val name = node.info.legalIdentity.name.orgName ?: node.info.legalIdentity.name.commonName
|
||||||
|
Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec")
|
||||||
|
|
||||||
|
// Don't start the shell if there's no console attached.
|
||||||
|
val runShell = !cmdlineOptions.noLocalShell && System.console() != null
|
||||||
|
node.startupComplete then {
|
||||||
|
try {
|
||||||
|
InteractiveShell.startShell(cmdlineOptions.baseDirectory, runShell, cmdlineOptions.sshdServer, node)
|
||||||
|
} catch(e: Throwable) {
|
||||||
|
logger.error("Shell failed to start", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} failure {
|
||||||
|
logger.error("Error during network map registration", it)
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
node.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
open protected fun logStartupInfo(versionInfo: VersionInfo, cmdlineOptions: CmdLineOptions, conf: FullNodeConfiguration) {
|
||||||
|
logger.info("Vendor: ${versionInfo.vendor}")
|
||||||
|
logger.info("Release: ${versionInfo.releaseVersion}")
|
||||||
|
logger.info("Platform Version: ${versionInfo.platformVersion}")
|
||||||
|
logger.info("Revision: ${versionInfo.revision}")
|
||||||
|
val info = ManagementFactory.getRuntimeMXBean()
|
||||||
|
logger.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this
|
||||||
|
logger.info("Main class: ${FullNodeConfiguration::class.java.protectionDomain.codeSource.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}")
|
||||||
|
val agentProperties = VMSupport.getAgentProperties()
|
||||||
|
if (agentProperties.containsKey("sun.jdwp.listenerAddress")) {
|
||||||
|
logger.info("Debug port: ${agentProperties.getProperty("sun.jdwp.listenerAddress")}")
|
||||||
|
}
|
||||||
|
logger.info("Starting as node on ${conf.p2pAddress}")
|
||||||
|
}
|
||||||
|
|
||||||
|
open protected fun maybeRegisterWithNetworkAndExit(cmdlineOptions: CmdLineOptions, conf: FullNodeConfiguration) {
|
||||||
|
if (!cmdlineOptions.isRegistration) return
|
||||||
|
println()
|
||||||
|
println("******************************************************************")
|
||||||
|
println("* *")
|
||||||
|
println("* Registering as a new participant with Corda network *")
|
||||||
|
println("* *")
|
||||||
|
println("******************************************************************")
|
||||||
|
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(conf.certificateSigningService)).buildKeystore()
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): FullNodeConfiguration {
|
||||||
|
val conf = try {
|
||||||
|
cmdlineOptions.loadConfig()
|
||||||
|
} catch (e: ConfigException) {
|
||||||
|
println("Unable to load the configuration file: ${e.rootCause.message}")
|
||||||
|
exitProcess(2)
|
||||||
|
}
|
||||||
|
return conf
|
||||||
|
}
|
||||||
|
|
||||||
|
open protected fun banJavaSerialisation(conf: FullNodeConfiguration) {
|
||||||
|
SerialFilter.install(if (conf.bftReplicaId != null) ::bftSMaRtSerialFilter else ::defaultSerialFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
open protected 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
|
||||||
|
|
||||||
|
val versionInfo = VersionInfo(
|
||||||
|
manifestValue("Corda-Platform-Version")?.toInt() ?: 1,
|
||||||
|
manifestValue("Corda-Release-Version") ?: "Unknown",
|
||||||
|
manifestValue("Corda-Revision") ?: "Unknown",
|
||||||
|
manifestValue("Corda-Vendor") ?: "Unknown"
|
||||||
|
)
|
||||||
|
return versionInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enforceSingleNodeIsRunning(baseDirectory: Path) {
|
||||||
|
// Write out our process ID (which may or may not resemble a UNIX process id - to us it's just a string) to a
|
||||||
|
// file that we'll do our best to delete on exit. But if we don't, it'll be overwritten next time. If it already
|
||||||
|
// exists, we try to take the file lock first before replacing it and if that fails it means we're being started
|
||||||
|
// twice with the same directory: that's a user error and we should bail out.
|
||||||
|
val pidFile = (baseDirectory / "process-id").toFile()
|
||||||
|
pidFile.createNewFile()
|
||||||
|
pidFile.deleteOnExit()
|
||||||
|
val pidFileRw = RandomAccessFile(pidFile, "rw")
|
||||||
|
val pidFileLock = pidFileRw.channel.tryLock()
|
||||||
|
if (pidFileLock == null) {
|
||||||
|
println("It appears there is already a node running with the specified data directory $baseDirectory")
|
||||||
|
println("Shut that other node down and try again. It may have process ID ${pidFile.readText()}")
|
||||||
|
System.exit(1)
|
||||||
|
}
|
||||||
|
// Avoid the lock being garbage collected. We don't really need to release it as the OS will do so for us
|
||||||
|
// when our process shuts down, but we try in stop() anyway just to be nice.
|
||||||
|
addShutdownHook {
|
||||||
|
pidFileLock.release()
|
||||||
|
}
|
||||||
|
val ourProcessID: String = ManagementFactory.getRuntimeMXBean().name.split("@")[0]
|
||||||
|
pidFileRw.setLength(0)
|
||||||
|
pidFileRw.write(ourProcessID.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseArguments(): Pair<ArgsParser, CmdLineOptions> {
|
||||||
|
val argsParser = ArgsParser()
|
||||||
|
val cmdlineOptions = try {
|
||||||
|
argsParser.parse(*args)
|
||||||
|
} catch (ex: OptionException) {
|
||||||
|
println("Invalid command line arguments: ${ex.message}")
|
||||||
|
argsParser.printHelp(System.out)
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
return Pair(argsParser, cmdlineOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
open protected fun initLogging(cmdlineOptions: CmdLineOptions) {
|
||||||
|
val loggingLevel = cmdlineOptions.loggingLevel.name.toLowerCase(Locale.ENGLISH)
|
||||||
|
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
|
||||||
|
if (cmdlineOptions.logToConsole) {
|
||||||
|
System.setProperty("consoleLogLevel", loggingLevel)
|
||||||
|
Node.renderBasicInfoToConsole = false
|
||||||
|
}
|
||||||
|
System.setProperty("log-path", (cmdlineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString())
|
||||||
|
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
|
||||||
|
SLF4JBridgeHandler.install()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun lookupMachineNameAndMaybeWarn(): String {
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
val hostName: String = InetAddress.getLocalHost().hostName
|
||||||
|
val elapsed = System.currentTimeMillis() - start
|
||||||
|
if (elapsed > 1000 && hostName.endsWith(".local")) {
|
||||||
|
// User is probably on macOS and experiencing this problem: http://stackoverflow.com/questions/10064581/how-can-i-eliminate-slow-resolving-loading-of-localhost-virtualhost-a-2-3-secon
|
||||||
|
//
|
||||||
|
// Also see https://bugs.openjdk.java.net/browse/JDK-8143378
|
||||||
|
val messages = listOf(
|
||||||
|
"Your computer took over a second to resolve localhost due an incorrect configuration. Corda will work but start very slowly until this is fixed. ",
|
||||||
|
"Please see https://docs.corda.net/troubleshooting.html#slow-localhost-resolution for information on how to fix this. ",
|
||||||
|
"It will only take a few seconds for you to resolve."
|
||||||
|
)
|
||||||
|
logger.warn(messages.joinToString(""))
|
||||||
|
Emoji.renderIfSupported {
|
||||||
|
print(Ansi.ansi().fgBrightRed())
|
||||||
|
messages.forEach {
|
||||||
|
println("${Emoji.sleepingFace}$it")
|
||||||
|
}
|
||||||
|
print(Ansi.ansi().reset())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hostName
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertCanNormalizeEmptyPath() {
|
||||||
|
// Check we're not running a version of Java with a known bug: https://github.com/corda/corda/issues/83
|
||||||
|
try {
|
||||||
|
Paths.get("").normalize()
|
||||||
|
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||||
|
Node.failStartUp("You are using a version of Java that is not supported (${System.getProperty("java.version")}). Please upgrade to the latest version.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun printPluginsAndServices(node: Node) {
|
||||||
|
node.configuration.extraAdvertisedServiceIds.let {
|
||||||
|
if (it.isNotEmpty()) Node.printBasicNodeInfo("Providing network services", it.joinToString())
|
||||||
|
}
|
||||||
|
val plugins = node.pluginRegistries
|
||||||
|
.map { it.javaClass.name }
|
||||||
|
.filterNot { it.startsWith("net.corda.node.") || it.startsWith("net.corda.core.") || it.startsWith("net.corda.nodeapi.") }
|
||||||
|
.map { it.substringBefore('$') }
|
||||||
|
if (plugins.isNotEmpty())
|
||||||
|
Node.printBasicNodeInfo("Loaded plugins", plugins.joinToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
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.",
|
||||||
|
"You know, I was a banker\nonce ... but I lost interest. ${Emoji.bagOfCash}",
|
||||||
|
"It's not who you know, it's who you know\nknows what you know you know.",
|
||||||
|
"It runs on the JVM because QuickBasic\nis apparently not 'professional' enough.",
|
||||||
|
"\"It's OK computer, I go to sleep after\ntwenty minutes of inactivity too!\"",
|
||||||
|
"It's kind of like a block chain but\ncords sounded healthier than chains.",
|
||||||
|
"Computer science and finance together.\nYou should see our crazy Christmas parties!",
|
||||||
|
"I met my bank manager yesterday and asked\nto check my balance ... he pushed me over!",
|
||||||
|
"A banker with nobody around may find\nthemselves .... a-loan! <applause>",
|
||||||
|
"Whenever I go near my bank I get\nwithdrawal symptoms ${Emoji.coolGuy}",
|
||||||
|
"There was an earthquake in California,\na local bank went into de-fault.",
|
||||||
|
"I asked for insurance if the nearby\nvolcano erupted. They said I'd be covered.",
|
||||||
|
"I had an account with a bank in the\nNorth Pole, but they froze all my assets ${Emoji.santaClaus}",
|
||||||
|
"Check your contracts carefully. The fine print\nis usually a clause for suspicion ${Emoji.santaClaus}",
|
||||||
|
"Some bankers are generous ...\nto a vault! ${Emoji.bagOfCash} ${Emoji.coolGuy}",
|
||||||
|
"What you can buy for a dollar these\ndays is absolute non-cents! ${Emoji.bagOfCash}",
|
||||||
|
"Old bankers never die, they\njust... pass the buck",
|
||||||
|
"I won $3M on the lottery so I donated a quarter\nof it to charity. Now I have $2,999,999.75.",
|
||||||
|
"There are two rules for financial success:\n1) Don't tell everything you know.",
|
||||||
|
"Top tip: never say \"oops\", instead\nalways say \"Ah, Interesting!\"",
|
||||||
|
"Computers are useless. They can only\ngive you answers. -- Picasso"
|
||||||
|
)
|
||||||
|
if (Emoji.hasEmojiTerminal)
|
||||||
|
messages += "Kind of like a regular database but\nwith emojis, colours and ascii art. ${Emoji.coolGuy}"
|
||||||
|
val (msg1, msg2) = messages.randomOrNull()!!.split('\n')
|
||||||
|
|
||||||
|
println(Ansi.ansi().fgBrightRed().a("""
|
||||||
|
______ __
|
||||||
|
/ ____/ _________/ /___ _
|
||||||
|
/ / __ / ___/ __ / __ `/ """).fgBrightBlue().a(msg1).newline().fgBrightRed().a(
|
||||||
|
"/ /___ /_/ / / / /_/ / /_/ / ").fgBrightBlue().a(msg2).newline().fgBrightRed().a(
|
||||||
|
"""\____/ /_/ \__,_/\__,_/""").reset().newline().newline().fgBrightDefault().bold().
|
||||||
|
a("--- ${versionInfo.vendor} ${versionInfo.releaseVersion} (${versionInfo.revision.take(7)}) -----------------------------------------------").
|
||||||
|
newline().
|
||||||
|
newline().
|
||||||
|
a("${Emoji.books}New! ").reset().a("Training now available worldwide, see https://corda.net/corda-training/").
|
||||||
|
newline().
|
||||||
|
reset())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,10 @@
|
|||||||
package net.corda.node.services.config
|
package net.corda.node.services.config
|
||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import net.corda.core.node.VersionInfo
|
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.node.internal.NetworkMapInfo
|
import net.corda.node.internal.NetworkMapInfo
|
||||||
import net.corda.node.internal.Node
|
|
||||||
import net.corda.node.serialization.NodeClock
|
|
||||||
import net.corda.node.services.messaging.CertificateChainCheckPolicy
|
import net.corda.node.services.messaging.CertificateChainCheckPolicy
|
||||||
import net.corda.node.services.network.NetworkMapService
|
import net.corda.node.services.network.NetworkMapService
|
||||||
import net.corda.node.utilities.TestClock
|
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.nodeapi.config.NodeSSLConfiguration
|
import net.corda.nodeapi.config.NodeSSLConfiguration
|
||||||
import net.corda.nodeapi.config.OldConfig
|
import net.corda.nodeapi.config.OldConfig
|
||||||
@ -78,14 +74,13 @@ data class FullNodeConfiguration(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createNode(versionInfo: VersionInfo): Node {
|
fun calculateServices(): Set<ServiceInfo> {
|
||||||
val advertisedServices = extraAdvertisedServiceIds
|
val advertisedServices = extraAdvertisedServiceIds
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.map { ServiceInfo.parse(it) }
|
.map { ServiceInfo.parse(it) }
|
||||||
.toMutableSet()
|
.toMutableSet()
|
||||||
if (networkMapService == null) advertisedServices += ServiceInfo(NetworkMapService.type)
|
if (networkMapService == null) advertisedServices += ServiceInfo(NetworkMapService.type)
|
||||||
|
return advertisedServices
|
||||||
return Node(this, advertisedServices, versionInfo, if (useTestClock) TestClock() else NodeClock())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import net.corda.core.node.services.NetworkMapCache
|
|||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.node.printBasicNodeInfo
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.services.RPCUserService
|
import net.corda.node.services.RPCUserService
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE
|
import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE
|
||||||
@ -47,7 +47,6 @@ import org.apache.activemq.artemis.utils.ConfigurationHelper
|
|||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import sun.security.x509.X509CertImpl
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
@ -156,9 +155,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
|||||||
registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } }
|
registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } }
|
||||||
}
|
}
|
||||||
activeMQServer.start()
|
activeMQServer.start()
|
||||||
printBasicNodeInfo("Listening on port", p2pPort.toString())
|
Node.printBasicNodeInfo("Listening on port", p2pPort.toString())
|
||||||
if (rpcPort != null) {
|
if (rpcPort != null) {
|
||||||
printBasicNodeInfo("RPC service listening on port", rpcPort.toString())
|
Node.printBasicNodeInfo("RPC service listening on port", rpcPort.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,14 +14,12 @@ import net.corda.core.flows.FlowInitiator
|
|||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.FlowStateMachine
|
import net.corda.core.flows.FlowStateMachine
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.StateMachineInfo
|
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
import net.corda.core.messaging.StateMachineUpdate
|
||||||
import net.corda.core.utilities.Emoji
|
import net.corda.core.utilities.Emoji
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.jackson.JacksonSupport
|
import net.corda.jackson.JacksonSupport
|
||||||
import net.corda.jackson.StringToMethodCallParser
|
import net.corda.jackson.StringToMethodCallParser
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.printBasicNodeInfo
|
|
||||||
import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT
|
import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT
|
||||||
import net.corda.node.services.messaging.RpcContext
|
import net.corda.node.services.messaging.RpcContext
|
||||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||||
@ -93,7 +91,7 @@ object InteractiveShell {
|
|||||||
// to that local copy, as CRaSH is no longer well maintained by the upstream and the SSH plugin
|
// to that local copy, as CRaSH is no longer well maintained by the upstream and the SSH plugin
|
||||||
// that it comes with is based on a very old version of Apache SSHD which can't handle connections
|
// that it comes with is based on a very old version of Apache SSHD which can't handle connections
|
||||||
// from newer SSH clients. It also means hooking things up to the authentication system.
|
// from newer SSH clients. It also means hooking things up to the authentication system.
|
||||||
printBasicNodeInfo("SSH server access is not fully implemented, sorry.")
|
Node.printBasicNodeInfo("SSH server access is not fully implemented, sorry.")
|
||||||
runSSH = false
|
runSSH = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import net.corda.core.readLines
|
|||||||
import net.corda.core.utilities.DUMMY_BANK_A
|
import net.corda.core.utilities.DUMMY_BANK_A
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
import net.corda.core.utilities.DUMMY_REGULATOR
|
import net.corda.core.utilities.DUMMY_REGULATOR
|
||||||
import net.corda.node.LOGS_DIRECTORY_NAME
|
import net.corda.node.internal.NodeStartup
|
||||||
import net.corda.node.services.api.RegulatorService
|
import net.corda.node.services.api.RegulatorService
|
||||||
import net.corda.node.services.transactions.SimpleNotaryService
|
import net.corda.node.services.transactions.SimpleNotaryService
|
||||||
import net.corda.nodeapi.ArtemisMessagingComponent
|
import net.corda.nodeapi.ArtemisMessagingComponent
|
||||||
@ -74,7 +74,7 @@ class DriverTests {
|
|||||||
assertThat(logConfigFile).isRegularFile()
|
assertThat(logConfigFile).isRegularFile()
|
||||||
driver(isDebug = true, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString())) {
|
driver(isDebug = true, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString())) {
|
||||||
val baseDirectory = startNode(DUMMY_BANK_A.name).getOrThrow().configuration.baseDirectory
|
val baseDirectory = startNode(DUMMY_BANK_A.name).getOrThrow().configuration.baseDirectory
|
||||||
val logFile = (baseDirectory / LOGS_DIRECTORY_NAME).list { it.sorted().findFirst().get() }
|
val logFile = (baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME).list { it.sorted().findFirst().get() }
|
||||||
val debugLinesPresent = logFile.readLines { lines -> lines.anyMatch { line -> line.startsWith("[DEBUG]") } }
|
val debugLinesPresent = logFile.readLines { lines -> lines.anyMatch { line -> line.startsWith("[DEBUG]") } }
|
||||||
assertThat(debugLinesPresent).isTrue()
|
assertThat(debugLinesPresent).isTrue()
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import net.corda.core.node.NodeInfo
|
|||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.core.node.services.ServiceType
|
import net.corda.core.node.services.ServiceType
|
||||||
import net.corda.core.utilities.*
|
import net.corda.core.utilities.*
|
||||||
import net.corda.node.LOGS_DIRECTORY_NAME
|
import net.corda.node.internal.NodeStartup
|
||||||
import net.corda.node.services.config.*
|
import net.corda.node.services.config.*
|
||||||
import net.corda.node.services.network.NetworkMapService
|
import net.corda.node.services.network.NetworkMapService
|
||||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||||
@ -698,7 +698,7 @@ class DriverDSL(
|
|||||||
),
|
),
|
||||||
jdwpPort = debugPort,
|
jdwpPort = debugPort,
|
||||||
extraJvmArguments = extraJvmArguments,
|
extraJvmArguments = extraJvmArguments,
|
||||||
errorLogPath = nodeConf.baseDirectory / LOGS_DIRECTORY_NAME / "error.log",
|
errorLogPath = nodeConf.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME / "error.log",
|
||||||
workingDirectory = nodeConf.baseDirectory
|
workingDirectory = nodeConf.baseDirectory
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import net.corda.core.utilities.DUMMY_CA
|
|||||||
import net.corda.core.utilities.DUMMY_MAP
|
import net.corda.core.utilities.DUMMY_MAP
|
||||||
import net.corda.core.utilities.WHITESPACE
|
import net.corda.core.utilities.WHITESPACE
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
|
import net.corda.node.serialization.NodeClock
|
||||||
import net.corda.node.services.config.ConfigHelper
|
import net.corda.node.services.config.ConfigHelper
|
||||||
import net.corda.node.services.config.FullNodeConfiguration
|
import net.corda.node.services.config.FullNodeConfiguration
|
||||||
import net.corda.node.services.config.configOf
|
import net.corda.node.services.config.configOf
|
||||||
@ -158,7 +159,9 @@ abstract class NodeBasedTest {
|
|||||||
) + configOverrides
|
) + configOverrides
|
||||||
)
|
)
|
||||||
|
|
||||||
val node = config.parseAs<FullNodeConfiguration>().createNode(MOCK_VERSION_INFO.copy(platformVersion = platformVersion))
|
val parsedConfig = config.parseAs<FullNodeConfiguration>()
|
||||||
|
val node = Node(parsedConfig, parsedConfig.calculateServices(), MOCK_VERSION_INFO.copy(platformVersion = platformVersion),
|
||||||
|
if (parsedConfig.useTestClock) TestClock() else NodeClock())
|
||||||
node.start()
|
node.start()
|
||||||
nodes += node
|
nodes += node
|
||||||
thread(name = legalName.commonName) {
|
thread(name = legalName.commonName) {
|
||||||
|
Loading…
Reference in New Issue
Block a user