mirror of
https://github.com/corda/corda.git
synced 2025-06-16 22:28:15 +00:00
[CORDA-1822]: Derive error code from exception signature (#3774)
This commit is contained in:
committed by
GitHub
parent
f979d9d3cf
commit
7a1b75ef35
@ -3,8 +3,6 @@ package net.corda.core.utilities
|
|||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.Try.Failure
|
|
||||||
import net.corda.core.utilities.Try.Success
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of an operation that has either succeeded with a result (represented by [Success]) or failed with an
|
* Representation of an operation that has either succeeded with a result (represented by [Success]) or failed with an
|
||||||
@ -60,6 +58,35 @@ sealed class Try<out A> {
|
|||||||
is Failure -> uncheckedCast(this)
|
is Failure -> uncheckedCast(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Applies the given action to the value if [Success], or does nothing if [Failure]. Returns `this` for chaining. */
|
||||||
|
fun doOnSuccess(action: (A) -> Unit): Try<A> {
|
||||||
|
when (this) {
|
||||||
|
is Success -> action.invoke(value)
|
||||||
|
is Failure -> {}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Applies the given action to the error if [Failure], or does nothing if [Success]. Returns `this` for chaining. */
|
||||||
|
fun doOnFailure(action: (Throwable) -> Unit): Try<A> {
|
||||||
|
when (this) {
|
||||||
|
is Success -> {}
|
||||||
|
is Failure -> action.invoke(exception)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Applies the given action to the exception if [Failure], rethrowing [Error]s. Does nothing if [Success]. Returns `this` for chaining. */
|
||||||
|
fun doOnException(action: (Exception) -> Unit): Try<A> {
|
||||||
|
return doOnFailure { error ->
|
||||||
|
if (error is Exception) {
|
||||||
|
action.invoke(error)
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
data class Success<out A>(val value: A) : Try<A>() {
|
data class Success<out A>(val value: A) : Try<A>() {
|
||||||
override val isSuccess: Boolean get() = true
|
override val isSuccess: Boolean get() = true
|
||||||
|
@ -97,102 +97,108 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
|
|
||||||
drawBanner(versionInfo)
|
drawBanner(versionInfo)
|
||||||
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
|
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
|
||||||
val conf = try {
|
|
||||||
val (rawConfig, conf0Result) = loadConfigFile(cmdlineOptions)
|
|
||||||
if (cmdlineOptions.devMode) {
|
|
||||||
println("Config:\n${rawConfig.root().render(ConfigRenderOptions.defaults())}")
|
|
||||||
}
|
|
||||||
val conf0 = conf0Result.getOrThrow()
|
|
||||||
if (cmdlineOptions.bootstrapRaftCluster) {
|
|
||||||
if (conf0 is NodeConfigurationImpl) {
|
|
||||||
println("Bootstrapping raft cluster (starting up as seed node).")
|
|
||||||
// Ignore the configured clusterAddresses to make the node bootstrap a cluster instead of joining.
|
|
||||||
conf0.copy(notary = conf0.notary?.copy(raft = conf0.notary?.raft?.copy(clusterAddresses = emptyList())))
|
|
||||||
} else {
|
|
||||||
println("bootstrap-raft-notaries flag not recognized, exiting...")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
conf0
|
|
||||||
}
|
|
||||||
} catch (e: UnknownConfigurationKeysException) {
|
|
||||||
logger.error(e.message)
|
|
||||||
return false
|
|
||||||
} catch (e: ConfigException.IO) {
|
|
||||||
println("""
|
|
||||||
Unable to load the node config file from '${cmdlineOptions.configFile}'.
|
|
||||||
|
|
||||||
Try experimenting with the --base-directory flag to change which directory the node
|
val configuration = (attempt { loadConfiguration(cmdlineOptions) }.doOnException(handleConfigurationLoadingError(cmdlineOptions.configFile)) as? Try.Success)?.let(Try.Success<NodeConfiguration>::value) ?: return false
|
||||||
is looking in, or use the --config-file flag to specify it explicitly.
|
|
||||||
""".trimIndent())
|
val errors = configuration.validate()
|
||||||
return false
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logger.error("Unexpected error whilst reading node configuration", e)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val errors = conf.validate()
|
|
||||||
if (errors.isNotEmpty()) {
|
if (errors.isNotEmpty()) {
|
||||||
logger.error("Invalid node configuration. Errors where:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}")
|
logger.error("Invalid node configuration. Errors were:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success ?: return false
|
||||||
banJavaSerialisation(conf)
|
|
||||||
preNetworkRegistration(conf)
|
attempt { preNetworkRegistration(configuration) }.doOnException(handleRegistrationError) as? Try.Success ?: return false
|
||||||
if (cmdlineOptions.nodeRegistrationOption != null) {
|
|
||||||
// Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig]
|
cmdlineOptions.nodeRegistrationOption?.let {
|
||||||
registerWithNetwork(conf, versionInfo, cmdlineOptions.nodeRegistrationOption)
|
// Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig]
|
||||||
// At this point the node registration was successful. We can delete the marker file.
|
attempt { registerWithNetwork(configuration, versionInfo, cmdlineOptions.nodeRegistrationOption) }.doOnException(handleRegistrationError) as? Try.Success ?: return false
|
||||||
deleteNodeRegistrationMarker(cmdlineOptions.baseDirectory)
|
|
||||||
return true
|
// At this point the node registration was successful. We can delete the marker file.
|
||||||
}
|
deleteNodeRegistrationMarker(cmdlineOptions.baseDirectory)
|
||||||
logStartupInfo(versionInfo, cmdlineOptions, conf)
|
return true
|
||||||
} catch (e: NodeRegistrationException) {
|
|
||||||
logger.warn("Node registration service is unavailable. Perhaps try to perform the initial registration again after a while.", e)
|
|
||||||
return false
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logger.error("Exception during node registration", e)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
logStartupInfo(versionInfo, cmdlineOptions, configuration)
|
||||||
cmdlineOptions.baseDirectory.createDirectories()
|
|
||||||
startNode(conf, versionInfo, startTime, cmdlineOptions)
|
|
||||||
} catch (e: MultipleCordappsForFlowException) {
|
|
||||||
logger.error(e.message)
|
|
||||||
return false
|
|
||||||
} catch (e: CouldNotCreateDataSourceException) {
|
|
||||||
logger.error(e.message, e.cause)
|
|
||||||
return false
|
|
||||||
} catch (e: CheckpointIncompatibleException) {
|
|
||||||
logger.error(e.message)
|
|
||||||
return false
|
|
||||||
} catch (e: AddressBindingException) {
|
|
||||||
logger.error(e.message)
|
|
||||||
return false
|
|
||||||
} catch (e: NetworkParametersReader.Error) {
|
|
||||||
logger.error(e.message)
|
|
||||||
return false
|
|
||||||
} catch (e: DatabaseIncompatibleException) {
|
|
||||||
e.message?.let { Node.printWarning(it) }
|
|
||||||
logger.error(e.message)
|
|
||||||
return false
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (e is Errors.NativeIoException && e.message?.contains("Address already in use") == true) {
|
|
||||||
logger.error("One of the ports required by the Corda node is already in use.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (e.message?.startsWith("Unknown named curve:") == true) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Node exiting successfully")
|
return attempt { startNode(configuration, versionInfo, startTime, cmdlineOptions) }.doOnSuccess { logger.info("Node exiting successfully") }.doOnException(handleStartError).isSuccess
|
||||||
return true
|
}
|
||||||
|
|
||||||
|
private fun <RESULT> attempt(action: () -> RESULT): Try<RESULT> = Try.on(action)
|
||||||
|
|
||||||
|
private fun Exception.isExpectedWhenStartingNode() = startNodeExpectedErrors.any { error -> error.isInstance(this) }
|
||||||
|
|
||||||
|
private val startNodeExpectedErrors = setOf(MultipleCordappsForFlowException::class, CheckpointIncompatibleException::class, AddressBindingException::class, NetworkParametersReader::class, DatabaseIncompatibleException::class)
|
||||||
|
|
||||||
|
private fun Exception.logAsExpected(message: String? = this.message, print: (String?) -> Unit = logger::error) = print("$message [errorCode=${errorCode()}]")
|
||||||
|
|
||||||
|
private fun Exception.logAsUnexpected(message: String? = this.message, error: Exception = this, print: (String?, Throwable) -> Unit = logger::error) = print("$message [errorCode=${errorCode()}]", error)
|
||||||
|
|
||||||
|
private fun Exception.isOpenJdkKnownIssue() = message?.startsWith("Unknown named curve:") == true
|
||||||
|
|
||||||
|
private fun Exception.errorCode(): String {
|
||||||
|
|
||||||
|
val hash = staticLocationBasedHash()
|
||||||
|
return Integer.toOctalString(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Throwable.staticLocationBasedHash(visited: Set<Throwable> = setOf(this)): Int {
|
||||||
|
|
||||||
|
val cause = this.cause
|
||||||
|
return when {
|
||||||
|
cause != null && !visited.contains(cause) -> Objects.hash(this::class.java.name, stackTrace, cause.staticLocationBasedHash(visited + cause))
|
||||||
|
else -> Objects.hash(this::class.java.name, stackTrace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val handleRegistrationError = { error: Exception ->
|
||||||
|
when (error) {
|
||||||
|
is NodeRegistrationException -> error.logAsExpected("Node registration service is unavailable. Perhaps try to perform the initial registration again after a while.")
|
||||||
|
else -> error.logAsUnexpected("Exception during node registration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val handleStartError = { error: Exception ->
|
||||||
|
when {
|
||||||
|
error.isExpectedWhenStartingNode() -> error.logAsExpected()
|
||||||
|
error is CouldNotCreateDataSourceException -> error.logAsUnexpected()
|
||||||
|
error is Errors.NativeIoException && error.message?.contains("Address already in use") == true -> error.logAsExpected("One of the ports required by the Corda node is already in use.")
|
||||||
|
error.isOpenJdkKnownIssue() -> error.logAsExpected("Exception during node startup - ${error.message}. This is a known OpenJDK issue on some Linux distributions, please use OpenJDK from zulu.org or Oracle JDK.")
|
||||||
|
else -> error.logAsUnexpected("Exception during node startup")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleConfigurationLoadingError(configFile: Path) = { error: Exception ->
|
||||||
|
when (error) {
|
||||||
|
is UnknownConfigurationKeysException -> error.logAsExpected()
|
||||||
|
is ConfigException.IO -> error.logAsExpected(configFileNotFoundMessage(configFile), ::println)
|
||||||
|
else -> error.logAsUnexpected("Unexpected error whilst reading node configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configFileNotFoundMessage(configFile: Path): String {
|
||||||
|
return """
|
||||||
|
Unable to load the node config file from '$configFile'.
|
||||||
|
|
||||||
|
Try setting the --base-directory flag to change which directory the node
|
||||||
|
is looking in, or use the --config-file flag to specify it explicitly.
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadConfiguration(cmdlineOptions: CmdLineOptions): NodeConfiguration {
|
||||||
|
|
||||||
|
val (rawConfig, configurationResult) = loadConfigFile(cmdlineOptions)
|
||||||
|
if (cmdlineOptions.devMode) {
|
||||||
|
println("Config:\n${rawConfig.root().render(ConfigRenderOptions.defaults())}")
|
||||||
|
}
|
||||||
|
val configuration = configurationResult.getOrThrow()
|
||||||
|
return if (cmdlineOptions.bootstrapRaftCluster) {
|
||||||
|
println("Bootstrapping raft cluster (starting up as seed node).")
|
||||||
|
// Ignore the configured clusterAddresses to make the node bootstrap a cluster instead of joining.
|
||||||
|
(configuration as NodeConfigurationImpl).copy(notary = configuration.notary?.copy(raft = configuration.notary?.raft?.copy(clusterAddresses = emptyList())))
|
||||||
|
} else {
|
||||||
|
configuration
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkRegistrationMode(): Boolean {
|
private fun checkRegistrationMode(): Boolean {
|
||||||
@ -234,7 +240,7 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
marker.delete()
|
marker.delete()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.warn("Could not delete the marker file that was created for `--initial-registration`.", e)
|
e.logAsUnexpected("Could not delete the marker file that was created for `--initial-registration`.", print = logger::warn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,6 +249,8 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
protected open fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo)
|
protected open fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo)
|
||||||
|
|
||||||
protected open fun startNode(conf: NodeConfiguration, versionInfo: VersionInfo, startTime: Long, cmdlineOptions: CmdLineOptions) {
|
protected open fun startNode(conf: NodeConfiguration, versionInfo: VersionInfo, startTime: Long, cmdlineOptions: CmdLineOptions) {
|
||||||
|
|
||||||
|
cmdlineOptions.baseDirectory.createDirectories()
|
||||||
val node = createNode(conf, versionInfo)
|
val node = createNode(conf, versionInfo)
|
||||||
if (cmdlineOptions.clearNetworkMapCache) {
|
if (cmdlineOptions.clearNetworkMapCache) {
|
||||||
node.clearNetworkMapCache()
|
node.clearNetworkMapCache()
|
||||||
|
Reference in New Issue
Block a user