mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +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.utilities.ALICE
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.node.internal.NodeStartup
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.nodeapi.User
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@ -43,7 +44,7 @@ class BootTests {
|
||||
startNode(ALICE.name).getOrThrow()
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
@ -2,281 +2,11 @@
|
||||
|
||||
package net.corda.node
|
||||
|
||||
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.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()
|
||||
}
|
||||
import net.corda.node.internal.NodeStartup
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val startTime = System.currentTimeMillis()
|
||||
assertCanNormalizeEmptyPath()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
NodeStartup(args).run()
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.corda.node
|
||||
|
||||
import net.corda.node.internal.Node
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Proxy
|
||||
@ -24,7 +25,7 @@ internal object SerialFilter {
|
||||
// JDK 8u121 is the earliest JDK8 JVM that supports this functionality.
|
||||
filterInterface = getFilterInterface("java.io")
|
||||
?: 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")
|
||||
val statusEnum = Class.forName("${filterInterface.name}\$Status")
|
||||
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.ListenableFuture
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import net.corda.nodeapi.internal.ShutdownHook
|
||||
import net.corda.core.flatMap
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.minutes
|
||||
@ -18,7 +17,6 @@ import net.corda.core.seconds
|
||||
import net.corda.core.success
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.node.printBasicNodeInfo
|
||||
import net.corda.node.serialization.NodeClock
|
||||
import net.corda.node.services.RPCUserService
|
||||
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.ArtemisTcpTransport
|
||||
import net.corda.nodeapi.ConnectionDirection
|
||||
import net.corda.nodeapi.internal.ShutdownHook
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
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.ClientMessage
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.time.Clock
|
||||
import java.util.*
|
||||
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],
|
||||
@ -59,12 +60,26 @@ import javax.management.ObjectName
|
||||
* 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.
|
||||
*/
|
||||
class Node(override val configuration: FullNodeConfiguration,
|
||||
advertisedServices: Set<ServiceInfo>,
|
||||
val versionInfo: VersionInfo,
|
||||
clock: Clock = NodeClock()) : AbstractNode(configuration, advertisedServices, clock) {
|
||||
open class Node(override val configuration: FullNodeConfiguration,
|
||||
advertisedServices: Set<ServiceInfo>,
|
||||
val versionInfo: VersionInfo,
|
||||
clock: Clock = NodeClock()) : AbstractNode(configuration, advertisedServices, clock) {
|
||||
companion object {
|
||||
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
|
||||
|
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
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import net.corda.core.node.VersionInfo
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
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.network.NetworkMapService
|
||||
import net.corda.node.utilities.TestClock
|
||||
import net.corda.nodeapi.User
|
||||
import net.corda.nodeapi.config.NodeSSLConfiguration
|
||||
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
|
||||
.filter(String::isNotBlank)
|
||||
.map { ServiceInfo.parse(it) }
|
||||
.toMutableSet()
|
||||
if (networkMapService == null) advertisedServices += ServiceInfo(NetworkMapService.type)
|
||||
|
||||
return Node(this, advertisedServices, versionInfo, if (useTestClock) TestClock() else NodeClock())
|
||||
return advertisedServices
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||
import net.corda.core.utilities.debug
|
||||
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.config.NodeConfiguration
|
||||
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.cert.X509CertificateHolder
|
||||
import rx.Subscription
|
||||
import sun.security.x509.X509CertImpl
|
||||
import java.io.IOException
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyStore
|
||||
@ -156,9 +155,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
||||
registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } }
|
||||
}
|
||||
activeMQServer.start()
|
||||
printBasicNodeInfo("Listening on port", p2pPort.toString())
|
||||
Node.printBasicNodeInfo("Listening on port", p2pPort.toString())
|
||||
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.FlowStateMachine
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.StateMachineInfo
|
||||
import net.corda.core.messaging.StateMachineUpdate
|
||||
import net.corda.core.utilities.Emoji
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.jackson.JacksonSupport
|
||||
import net.corda.jackson.StringToMethodCallParser
|
||||
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.RpcContext
|
||||
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
|
||||
// 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.
|
||||
printBasicNodeInfo("SSH server access is not fully implemented, sorry.")
|
||||
Node.printBasicNodeInfo("SSH server access is not fully implemented, sorry.")
|
||||
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_NOTARY
|
||||
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.transactions.SimpleNotaryService
|
||||
import net.corda.nodeapi.ArtemisMessagingComponent
|
||||
@ -74,7 +74,7 @@ class DriverTests {
|
||||
assertThat(logConfigFile).isRegularFile()
|
||||
driver(isDebug = true, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString())) {
|
||||
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]") } }
|
||||
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.ServiceType
|
||||
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.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||
@ -698,7 +698,7 @@ class DriverDSL(
|
||||
),
|
||||
jdwpPort = debugPort,
|
||||
extraJvmArguments = extraJvmArguments,
|
||||
errorLogPath = nodeConf.baseDirectory / LOGS_DIRECTORY_NAME / "error.log",
|
||||
errorLogPath = nodeConf.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME / "error.log",
|
||||
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.WHITESPACE
|
||||
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.FullNodeConfiguration
|
||||
import net.corda.node.services.config.configOf
|
||||
@ -158,7 +159,9 @@ abstract class NodeBasedTest {
|
||||
) + 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()
|
||||
nodes += node
|
||||
thread(name = legalName.commonName) {
|
||||
|
Loading…
Reference in New Issue
Block a user