mirror of
https://github.com/corda/corda.git
synced 2025-02-04 10:11:14 +00:00
Prohibit Java deserialisation in the Corda process (#566)
This commit is contained in:
parent
4cb21257e6
commit
160d13b6f7
@ -38,6 +38,9 @@ buildscript {
|
|||||||
ext.requery_version = '1.2.1'
|
ext.requery_version = '1.2.1'
|
||||||
ext.dokka_version = '0.9.13'
|
ext.dokka_version = '0.9.13'
|
||||||
|
|
||||||
|
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
||||||
|
ext.java8_minUpdateVersion = '131'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -129,7 +132,7 @@ allprojects {
|
|||||||
// We recommend a specific minor version (unfortunately, not checkable directly) because JavaFX adds APIs in
|
// We recommend a specific minor version (unfortunately, not checkable directly) because JavaFX adds APIs in
|
||||||
// minor releases, so we can't work with just any Java 8, it has to be a recent one.
|
// minor releases, so we can't work with just any Java 8, it has to be a recent one.
|
||||||
if (!JavaVersion.current().java8Compatible)
|
if (!JavaVersion.current().java8Compatible)
|
||||||
throw new GradleException("Corda requires Java 8, please upgrade to at least 1.8.0_112")
|
throw new GradleException("Corda requires Java 8, please upgrade to at least 1.8.0_$java8_minUpdateVersion")
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -5,6 +5,7 @@ import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
|
|||||||
import com.esotericsoftware.kryo.serializers.FieldSerializer
|
import com.esotericsoftware.kryo.serializers.FieldSerializer
|
||||||
import com.esotericsoftware.kryo.util.MapReferenceResolver
|
import com.esotericsoftware.kryo.util.MapReferenceResolver
|
||||||
import de.javakaffee.kryoserializers.ArraysAsListSerializer
|
import de.javakaffee.kryoserializers.ArraysAsListSerializer
|
||||||
|
import de.javakaffee.kryoserializers.BitSetSerializer
|
||||||
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
|
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
|
||||||
import de.javakaffee.kryoserializers.guava.*
|
import de.javakaffee.kryoserializers.guava.*
|
||||||
import net.corda.core.crypto.CompositeKey
|
import net.corda.core.crypto.CompositeKey
|
||||||
@ -80,7 +81,7 @@ object DefaultKryoCustomizer {
|
|||||||
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
|
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
|
||||||
|
|
||||||
register(MetaData::class.java, MetaDataSerializer)
|
register(MetaData::class.java, MetaDataSerializer)
|
||||||
register(BitSet::class.java, ReferencesAwareJavaSerializer)
|
register(BitSet::class.java, BitSetSerializer())
|
||||||
|
|
||||||
addDefaultSerializer(Logger::class.java, LoggerSerializer)
|
addDefaultSerializer(Logger::class.java, LoggerSerializer)
|
||||||
|
|
||||||
|
@ -509,29 +509,6 @@ fun <T> Kryo.withoutReferences(block: () -> T): T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Improvement to the builtin JavaSerializer by honouring the [Kryo.getReferences] setting.
|
|
||||||
*/
|
|
||||||
object ReferencesAwareJavaSerializer : JavaSerializer() {
|
|
||||||
override fun write(kryo: Kryo, output: Output, obj: Any) {
|
|
||||||
if (kryo.references) {
|
|
||||||
super.write(kryo, output, obj)
|
|
||||||
} else {
|
|
||||||
ObjectOutputStream(output).use {
|
|
||||||
it.writeObject(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(kryo: Kryo, input: Input, type: Class<Any>): Any {
|
|
||||||
return if (kryo.references) {
|
|
||||||
super.read(kryo, input, type)
|
|
||||||
} else {
|
|
||||||
ObjectInputStream(input).use(ObjectInputStream::readObject)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val ATTACHMENT_STORAGE = "ATTACHMENT_STORAGE"
|
val ATTACHMENT_STORAGE = "ATTACHMENT_STORAGE"
|
||||||
|
|
||||||
val Kryo.attachmentStorage: AttachmentStorage?
|
val Kryo.attachmentStorage: AttachmentStorage?
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.core.utilities
|
|||||||
import org.apache.logging.log4j.Level
|
import org.apache.logging.log4j.Level
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.apache.logging.log4j.core.LoggerContext
|
import org.apache.logging.log4j.core.LoggerContext
|
||||||
|
import org.apache.logging.log4j.core.config.Configurator
|
||||||
import org.apache.logging.log4j.core.config.LoggerConfig
|
import org.apache.logging.log4j.core.config.LoggerConfig
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@ -60,4 +61,19 @@ object LogHelper {
|
|||||||
config.addLogger(name, loggerConfig)
|
config.addLogger(name, loggerConfig)
|
||||||
loggerContext.updateLoggers(config)
|
loggerContext.updateLoggers(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* May fail to restore the original level due to unavoidable race if called by multiple threads.
|
||||||
|
*/
|
||||||
|
inline fun <T> withLevel(logName: String, levelName: String, block: () -> T) = let {
|
||||||
|
val level = Level.valueOf(levelName)
|
||||||
|
val oldLevel = LogManager.getLogger(logName).level
|
||||||
|
Configurator.setLevel(logName, level)
|
||||||
|
try {
|
||||||
|
block()
|
||||||
|
} finally {
|
||||||
|
Configurator.setLevel(logName, oldLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,9 @@ sourceSets {
|
|||||||
runtimeClasspath += main.output + test.output
|
runtimeClasspath += main.output + test.output
|
||||||
srcDir file('src/integration-test/kotlin')
|
srcDir file('src/integration-test/kotlin')
|
||||||
}
|
}
|
||||||
|
resources {
|
||||||
|
srcDir file('src/integration-test/resources')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
test {
|
test {
|
||||||
resources {
|
resources {
|
||||||
@ -61,6 +64,7 @@ dependencies {
|
|||||||
// Log4J: logging framework (with SLF4J bindings)
|
// Log4J: logging framework (with SLF4J bindings)
|
||||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
||||||
compile "org.apache.logging.log4j:log4j-web:${log4j_version}"
|
compile "org.apache.logging.log4j:log4j-web:${log4j_version}"
|
||||||
|
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
|
||||||
|
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
|
@ -48,8 +48,7 @@ task buildCordaJAR(type: FatCapsule) {
|
|||||||
javaAgents = ["quasar-core-${quasar_version}-jdk8.jar"]
|
javaAgents = ["quasar-core-${quasar_version}-jdk8.jar"]
|
||||||
systemProperties['visualvm.display.name'] = 'Corda'
|
systemProperties['visualvm.display.name'] = 'Corda'
|
||||||
minJavaVersion = '1.8.0'
|
minJavaVersion = '1.8.0'
|
||||||
// This version is known to work and avoids earlier 8u versions that have bugs.
|
minUpdateVersion['1.8'] = java8_minUpdateVersion
|
||||||
minUpdateVersion['1.8'] = '102'
|
|
||||||
caplets = ['CordaCaplet']
|
caplets = ['CordaCaplet']
|
||||||
|
|
||||||
// JVM configuration:
|
// JVM configuration:
|
||||||
|
43
node/src/integration-test/kotlin/net/corda/node/BootTests.kt
Normal file
43
node/src/integration-test/kotlin/net/corda/node/BootTests.kt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package net.corda.node
|
||||||
|
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.getOrThrow
|
||||||
|
import net.corda.core.messaging.startFlow
|
||||||
|
import net.corda.core.node.CordaPluginRegistry
|
||||||
|
import net.corda.node.driver.driver
|
||||||
|
import net.corda.node.services.startFlowPermission
|
||||||
|
import net.corda.nodeapi.User
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.*
|
||||||
|
|
||||||
|
class BootTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `java deserialization is disabled`() {
|
||||||
|
driver {
|
||||||
|
val user = User("u", "p", setOf(startFlowPermission<ObjectInputStreamFlow>()))
|
||||||
|
val future = startNode(rpcUsers = listOf(user)).getOrThrow().rpcClientToNode().apply {
|
||||||
|
start(user.username, user.password)
|
||||||
|
}.proxy().startFlow(::ObjectInputStreamFlow).returnValue
|
||||||
|
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(InvalidClassException::class.java).hasMessage("filter status: REJECTED")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObjectInputStreamFlow : FlowLogic<Unit>() {
|
||||||
|
|
||||||
|
override fun call() {
|
||||||
|
System.clearProperty("jdk.serialFilter") // This checks that the node has already consumed the property.
|
||||||
|
val data = ByteArrayOutputStream().apply { ObjectOutputStream(this).use { it.writeObject(object : Serializable {}) } }.toByteArray()
|
||||||
|
ObjectInputStream(data.inputStream()).use { it.readObject() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class BootTestsPlugin : CordaPluginRegistry() {
|
||||||
|
|
||||||
|
override val requiredFlows: Map<String, Set<String>> = mapOf(ObjectInputStreamFlow::class.java.name to setOf())
|
||||||
|
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
package net.corda.node.driver
|
package net.corda.node.driver
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import net.corda.core.div
|
import net.corda.core.div
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
import net.corda.core.list
|
import net.corda.core.list
|
||||||
import net.corda.core.node.NodeInfo
|
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.core.readLines
|
import net.corda.core.readLines
|
||||||
import net.corda.core.utilities.DUMMY_BANK_A
|
import net.corda.core.utilities.DUMMY_BANK_A
|
||||||
@ -19,54 +19,51 @@ import java.util.concurrent.Executors
|
|||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
|
|
||||||
class DriverTests {
|
class DriverTests {
|
||||||
companion object {
|
|
||||||
val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2)
|
|
||||||
|
|
||||||
fun nodeMustBeUp(nodeInfo: NodeInfo) {
|
companion object {
|
||||||
|
|
||||||
|
private val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2)
|
||||||
|
|
||||||
|
private fun nodeMustBeUp(handleFuture: ListenableFuture<NodeHandle>) = handleFuture.getOrThrow().apply {
|
||||||
val hostAndPort = ArtemisMessagingComponent.toHostAndPort(nodeInfo.address)
|
val hostAndPort = ArtemisMessagingComponent.toHostAndPort(nodeInfo.address)
|
||||||
// Check that the port is bound
|
// Check that the port is bound
|
||||||
addressMustBeBound(executorService, hostAndPort)
|
addressMustBeBound(executorService, hostAndPort, process)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nodeMustBeDown(nodeInfo: NodeInfo) {
|
private fun nodeMustBeDown(handle: NodeHandle) {
|
||||||
val hostAndPort = ArtemisMessagingComponent.toHostAndPort(nodeInfo.address)
|
val hostAndPort = ArtemisMessagingComponent.toHostAndPort(handle.nodeInfo.address)
|
||||||
// Check that the port is bound
|
// Check that the port is bound
|
||||||
addressMustNotBeBound(executorService, hostAndPort)
|
addressMustNotBeBound(executorService, hostAndPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `simple node startup and shutdown`() {
|
fun `simple node startup and shutdown`() {
|
||||||
val (notary, regulator) = driver {
|
val handles = driver {
|
||||||
val notary = startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type)))
|
val notary = startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||||
val regulator = startNode("Regulator", setOf(ServiceInfo(RegulatorService.type)))
|
val regulator = startNode("Regulator", setOf(ServiceInfo(RegulatorService.type)))
|
||||||
|
listOf(nodeMustBeUp(notary), nodeMustBeUp(regulator))
|
||||||
nodeMustBeUp(notary.getOrThrow().nodeInfo)
|
|
||||||
nodeMustBeUp(regulator.getOrThrow().nodeInfo)
|
|
||||||
Pair(notary.getOrThrow(), regulator.getOrThrow())
|
|
||||||
}
|
}
|
||||||
nodeMustBeDown(notary.nodeInfo)
|
handles.map { nodeMustBeDown(it) }
|
||||||
nodeMustBeDown(regulator.nodeInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `starting node with no services`() {
|
fun `starting node with no services`() {
|
||||||
val noService = driver {
|
val noService = driver {
|
||||||
val noService = startNode(DUMMY_BANK_A.name)
|
val noService = startNode(DUMMY_BANK_A.name)
|
||||||
nodeMustBeUp(noService.getOrThrow().nodeInfo)
|
nodeMustBeUp(noService)
|
||||||
noService.getOrThrow()
|
|
||||||
}
|
}
|
||||||
nodeMustBeDown(noService.nodeInfo)
|
nodeMustBeDown(noService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `random free port allocation`() {
|
fun `random free port allocation`() {
|
||||||
val nodeHandle = driver(portAllocation = PortAllocation.RandomFree) {
|
val nodeHandle = driver(portAllocation = PortAllocation.RandomFree) {
|
||||||
val nodeInfo = startNode(DUMMY_BANK_A.name)
|
val nodeInfo = startNode(DUMMY_BANK_A.name)
|
||||||
nodeMustBeUp(nodeInfo.getOrThrow().nodeInfo)
|
nodeMustBeUp(nodeInfo)
|
||||||
nodeInfo.getOrThrow()
|
|
||||||
}
|
}
|
||||||
nodeMustBeDown(nodeHandle.nodeInfo)
|
nodeMustBeDown(nodeHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -81,4 +78,5 @@ class DriverTests {
|
|||||||
assertThat(debugLinesPresent).isTrue()
|
assertThat(debugLinesPresent).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
net.corda.node.BootTestsPlugin
|
@ -9,6 +9,7 @@ import net.corda.core.*
|
|||||||
import net.corda.core.node.NodeVersionInfo
|
import net.corda.core.node.NodeVersionInfo
|
||||||
import net.corda.core.node.Version
|
import net.corda.core.node.Version
|
||||||
import net.corda.core.utilities.Emoji
|
import net.corda.core.utilities.Emoji
|
||||||
|
import net.corda.core.utilities.LogHelper.withLevel
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.services.config.FullNodeConfiguration
|
import net.corda.node.services.config.FullNodeConfiguration
|
||||||
import net.corda.node.shell.InteractiveShell
|
import net.corda.node.shell.InteractiveShell
|
||||||
@ -17,10 +18,12 @@ import net.corda.node.utilities.registration.NetworkRegistrationHelper
|
|||||||
import org.fusesource.jansi.Ansi
|
import org.fusesource.jansi.Ansi
|
||||||
import org.fusesource.jansi.AnsiConsole
|
import org.fusesource.jansi.AnsiConsole
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.slf4j.bridge.SLF4JBridgeHandler
|
||||||
|
import java.io.*
|
||||||
import java.lang.management.ManagementFactory
|
import java.lang.management.ManagementFactory
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
import java.util.Locale
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
private var renderBasicInfoToConsole = true
|
private var renderBasicInfoToConsole = true
|
||||||
@ -34,9 +37,21 @@ fun printBasicNodeInfo(description: String, info: String? = null) {
|
|||||||
|
|
||||||
val LOGS_DIRECTORY_NAME = "logs"
|
val LOGS_DIRECTORY_NAME = "logs"
|
||||||
|
|
||||||
|
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()
|
val startTime = System.currentTimeMillis()
|
||||||
checkJavaVersion()
|
assertCanNormalizeEmptyPath()
|
||||||
|
|
||||||
val argsParser = ArgsParser()
|
val argsParser = ArgsParser()
|
||||||
|
|
||||||
@ -48,13 +63,8 @@ fun main(args: Array<String>) {
|
|||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up logging. These properties are referenced from the XML config file.
|
initLogging(cmdlineOptions)
|
||||||
val loggingLevel = cmdlineOptions.loggingLevel.name.toLowerCase()
|
disableJavaDeserialization() // Should be after initLogging to avoid TMI.
|
||||||
System.setProperty("defaultLogLevel", loggingLevel)
|
|
||||||
if (cmdlineOptions.logToConsole) {
|
|
||||||
System.setProperty("consoleLogLevel", loggingLevel)
|
|
||||||
renderBasicInfoToConsole = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manifest properties are only available if running from the corda jar
|
// 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
|
fun manifestValue(name: String): String? = if (Manifests.exists(name)) Manifests.read(name) else null
|
||||||
@ -79,9 +89,6 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
drawBanner(nodeVersionInfo)
|
drawBanner(nodeVersionInfo)
|
||||||
|
|
||||||
val dir: Path = cmdlineOptions.baseDirectory
|
|
||||||
System.setProperty("log-path", (dir / "logs").toString())
|
|
||||||
|
|
||||||
val log = LoggerFactory.getLogger("Main")
|
val log = LoggerFactory.getLogger("Main")
|
||||||
printBasicNodeInfo("Logs can be found in", System.getProperty("log-path"))
|
printBasicNodeInfo("Logs can be found in", System.getProperty("log-path"))
|
||||||
|
|
||||||
@ -137,7 +144,7 @@ fun main(args: Array<String>) {
|
|||||||
val runShell = !cmdlineOptions.noLocalShell && System.console() != null
|
val runShell = !cmdlineOptions.noLocalShell && System.console() != null
|
||||||
node.startupComplete then {
|
node.startupComplete then {
|
||||||
try {
|
try {
|
||||||
InteractiveShell.startShell(dir, runShell, cmdlineOptions.sshdServer, node)
|
InteractiveShell.startShell(cmdlineOptions.baseDirectory, runShell, cmdlineOptions.sshdServer, node)
|
||||||
} catch(e: Throwable) {
|
} catch(e: Throwable) {
|
||||||
log.error("Shell failed to start", e)
|
log.error("Shell failed to start", e)
|
||||||
}
|
}
|
||||||
@ -155,15 +162,34 @@ fun main(args: Array<String>) {
|
|||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkJavaVersion() {
|
private fun assertCanNormalizeEmptyPath() {
|
||||||
// Check we're not running a version of Java with a known bug: https://github.com/corda/corda/issues/83
|
// Check we're not running a version of Java with a known bug: https://github.com/corda/corda/issues/83
|
||||||
try {
|
try {
|
||||||
Paths.get("").normalize()
|
Paths.get("").normalize()
|
||||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||||
println("""
|
javaIsTooOld()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun javaIsTooOld(): Nothing {
|
||||||
|
println("""
|
||||||
You are using a version of Java that is not supported (${System.getProperty("java.version")}). Please upgrade to the latest version.
|
You are using a version of Java that is not supported (${System.getProperty("java.version")}). Please upgrade to the latest version.
|
||||||
Corda will now exit...""")
|
Corda will now exit...""")
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun disableJavaDeserialization() {
|
||||||
|
// ObjectInputFilter and friends are in java.io in Java 9 but sun.misc in backports, so we use the system property interface for portability:
|
||||||
|
System.setProperty("jdk.serialFilter", "maxbytes=0")
|
||||||
|
// Attempt a deserialization so that ObjectInputFilter (permanently) inits itself:
|
||||||
|
val data = ByteArrayOutputStream().apply { ObjectOutputStream(this).use { it.writeObject(object : Serializable {}) } }.toByteArray()
|
||||||
|
try {
|
||||||
|
withLevel("java.io.serialization", "WARN") {
|
||||||
|
ObjectInputStream(data.inputStream()).use { it.readObject() } // Logs REJECTED at INFO, which we don't want users to see.
|
||||||
|
}
|
||||||
|
javaIsTooOld()
|
||||||
|
} catch (e: InvalidClassException) {
|
||||||
|
// Good, our system property is honoured (assuming ObjectInputFilter wasn't inited earlier).
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ interface DriverDSLExposedInterface {
|
|||||||
*
|
*
|
||||||
* @param handle The handle for the node that this webserver connects to via RPC.
|
* @param handle The handle for the node that this webserver connects to via RPC.
|
||||||
*/
|
*/
|
||||||
fun startWebserver(handle: NodeHandle): ListenableFuture<HostAndPort>
|
fun startWebserver(handle: NodeHandle): ListenableFuture<WebserverHandle>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a network map service node. Note that only a single one should ever be running, so you will probably want
|
* Starts a network map service node. Note that only a single one should ever be running, so you will probably want
|
||||||
@ -122,6 +122,11 @@ data class NodeHandle(
|
|||||||
fun rpcClientToNode(): CordaRPCClient = CordaRPCClient(configuration.rpcAddress!!)
|
fun rpcClientToNode(): CordaRPCClient = CordaRPCClient(configuration.rpcAddress!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class WebserverHandle(
|
||||||
|
val listenAddress: HostAndPort,
|
||||||
|
val process: Process
|
||||||
|
)
|
||||||
|
|
||||||
sealed class PortAllocation {
|
sealed class PortAllocation {
|
||||||
abstract fun nextPort(): Int
|
abstract fun nextPort(): Int
|
||||||
fun nextHostAndPort(): HostAndPort = HostAndPort.fromParts("localhost", nextPort())
|
fun nextHostAndPort(): HostAndPort = HostAndPort.fromParts("localhost", nextPort())
|
||||||
@ -228,8 +233,16 @@ fun getTimestampAsDirectoryName(): String {
|
|||||||
return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(UTC).format(Instant.now())
|
return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(UTC).format(Instant.now())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addressMustBeBound(executorService: ScheduledExecutorService, hostAndPort: HostAndPort): ListenableFuture<Unit> {
|
class ListenProcessDeathException(message: String) : Exception(message)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ListenProcessDeathException if [listenProcess] dies before the check succeeds, i.e. the check can't succeed as intended.
|
||||||
|
*/
|
||||||
|
fun addressMustBeBound(executorService: ScheduledExecutorService, hostAndPort: HostAndPort, listenProcess: Process): ListenableFuture<Unit> {
|
||||||
return poll(executorService, "address $hostAndPort to bind") {
|
return poll(executorService, "address $hostAndPort to bind") {
|
||||||
|
if (!listenProcess.isAlive) {
|
||||||
|
throw ListenProcessDeathException("The process that was expected to listen on $hostAndPort has died with status: ${listenProcess.exitValue()}")
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Socket(hostAndPort.host, hostAndPort.port).close()
|
Socket(hostAndPort.host, hostAndPort.port).close()
|
||||||
Unit
|
Unit
|
||||||
@ -265,12 +278,17 @@ fun <A> poll(
|
|||||||
}
|
}
|
||||||
var counter = 0
|
var counter = 0
|
||||||
fun schedulePoll() {
|
fun schedulePoll() {
|
||||||
executorService.schedule({
|
executorService.schedule(task@ {
|
||||||
counter++
|
counter++
|
||||||
if (counter == warnCount) {
|
if (counter == warnCount) {
|
||||||
log.warn("Been polling $pollName for ${pollIntervalMs * warnCount / 1000.0} seconds...")
|
log.warn("Been polling $pollName for ${pollIntervalMs * warnCount / 1000.0} seconds...")
|
||||||
}
|
}
|
||||||
val result = check()
|
val result = try {
|
||||||
|
check()
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
resultFuture.setException(t)
|
||||||
|
return@task
|
||||||
|
}
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
schedulePoll()
|
schedulePoll()
|
||||||
} else {
|
} else {
|
||||||
@ -482,7 +500,7 @@ class DriverDSL(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queryWebserver(handle: NodeHandle, process: Process): HostAndPort {
|
private fun queryWebserver(handle: NodeHandle, process: Process): WebserverHandle {
|
||||||
val protocol = if (handle.configuration.useHTTPS) "https://" else "http://"
|
val protocol = if (handle.configuration.useHTTPS) "https://" else "http://"
|
||||||
val url = URL("$protocol${handle.webAddress}/api/status")
|
val url = URL("$protocol${handle.webAddress}/api/status")
|
||||||
val client = OkHttpClient.Builder().connectTimeout(5, SECONDS).readTimeout(60, SECONDS).build()
|
val client = OkHttpClient.Builder().connectTimeout(5, SECONDS).readTimeout(60, SECONDS).build()
|
||||||
@ -490,7 +508,7 @@ class DriverDSL(
|
|||||||
while (process.isAlive) try {
|
while (process.isAlive) try {
|
||||||
val response = client.newCall(Request.Builder().url(url).build()).execute()
|
val response = client.newCall(Request.Builder().url(url).build()).execute()
|
||||||
if (response.isSuccessful && (response.body().string() == "started")) {
|
if (response.isSuccessful && (response.body().string() == "started")) {
|
||||||
return handle.webAddress
|
return WebserverHandle(handle.webAddress, process)
|
||||||
}
|
}
|
||||||
} catch(e: ConnectException) {
|
} catch(e: ConnectException) {
|
||||||
log.debug("Retrying webserver info at ${handle.webAddress}")
|
log.debug("Retrying webserver info at ${handle.webAddress}")
|
||||||
@ -499,13 +517,11 @@ class DriverDSL(
|
|||||||
throw IllegalStateException("Webserver at ${handle.webAddress} has died")
|
throw IllegalStateException("Webserver at ${handle.webAddress} has died")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startWebserver(handle: NodeHandle): ListenableFuture<HostAndPort> {
|
override fun startWebserver(handle: NodeHandle): ListenableFuture<WebserverHandle> {
|
||||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||||
val process = DriverDSL.startWebserver(executorService, handle, debugPort)
|
val processFuture = DriverDSL.startWebserver(executorService, handle, debugPort)
|
||||||
registerProcess(process)
|
registerProcess(processFuture)
|
||||||
return process.map {
|
return processFuture.map { queryWebserver(handle, it) }
|
||||||
queryWebserver(handle, it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
@ -577,7 +593,7 @@ class DriverDSL(
|
|||||||
errorLogPath = nodeConf.baseDirectory / LOGS_DIRECTORY_NAME / "error.log",
|
errorLogPath = nodeConf.baseDirectory / LOGS_DIRECTORY_NAME / "error.log",
|
||||||
workingDirectory = nodeConf.baseDirectory
|
workingDirectory = nodeConf.baseDirectory
|
||||||
)
|
)
|
||||||
}.flatMap { process -> addressMustBeBound(executorService, nodeConf.p2pAddress).map { process } }
|
}.flatMap { process -> addressMustBeBound(executorService, nodeConf.p2pAddress, process).map { process } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startWebserver(
|
private fun startWebserver(
|
||||||
@ -594,7 +610,7 @@ class DriverDSL(
|
|||||||
extraJvmArguments = listOf("-Dname=node-${handle.configuration.p2pAddress}-webserver"),
|
extraJvmArguments = listOf("-Dname=node-${handle.configuration.p2pAddress}-webserver"),
|
||||||
errorLogPath = Paths.get("error.$className.log")
|
errorLogPath = Paths.get("error.$className.log")
|
||||||
)
|
)
|
||||||
}.flatMap { process -> addressMustBeBound(executorService, handle.webAddress).map { process } }
|
}.flatMap { process -> addressMustBeBound(executorService, handle.webAddress, process).map { process } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package net.corda.node.services.transactions
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
|
import io.atomix.catalyst.buffer.BufferInput
|
||||||
|
import io.atomix.catalyst.buffer.BufferOutput
|
||||||
import io.atomix.catalyst.serializer.Serializer
|
import io.atomix.catalyst.serializer.Serializer
|
||||||
|
import io.atomix.catalyst.serializer.TypeSerializer
|
||||||
import io.atomix.catalyst.transport.Address
|
import io.atomix.catalyst.transport.Address
|
||||||
import io.atomix.catalyst.transport.Transport
|
import io.atomix.catalyst.transport.Transport
|
||||||
import io.atomix.catalyst.transport.netty.NettyTransport
|
import io.atomix.catalyst.transport.netty.NettyTransport
|
||||||
@ -69,7 +72,21 @@ class RaftUniquenessProvider(
|
|||||||
val address = Address(myAddress.host, myAddress.port)
|
val address = Address(myAddress.host, myAddress.port)
|
||||||
val storage = buildStorage(storagePath)
|
val storage = buildStorage(storagePath)
|
||||||
val transport = buildTransport(config)
|
val transport = buildTransport(config)
|
||||||
val serializer = Serializer()
|
val serializer = Serializer().apply {
|
||||||
|
// Add serializers so Catalyst doesn't attempt to fall back on Java serialization for these types, which is disabled process-wide:
|
||||||
|
register(DistributedImmutableMap.Commands.PutAll::class.java) {
|
||||||
|
object : TypeSerializer<DistributedImmutableMap.Commands.PutAll<*, *>> {
|
||||||
|
override fun write(obj: DistributedImmutableMap.Commands.PutAll<*, *>, buffer: BufferOutput<out BufferOutput<*>>, serializer: Serializer) = writeMap(obj.entries, buffer, serializer)
|
||||||
|
override fun read(type: Class<DistributedImmutableMap.Commands.PutAll<*, *>>, buffer: BufferInput<out BufferInput<*>>, serializer: Serializer) = DistributedImmutableMap.Commands.PutAll(readMap(buffer, serializer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
register(LinkedHashMap::class.java) {
|
||||||
|
object : TypeSerializer<LinkedHashMap<*, *>> {
|
||||||
|
override fun write(obj: LinkedHashMap<*, *>, buffer: BufferOutput<out BufferOutput<*>>, serializer: Serializer) = writeMap(obj, buffer, serializer)
|
||||||
|
override fun read(type: Class<LinkedHashMap<*, *>>, buffer: BufferInput<out BufferInput<*>>, serializer: Serializer) = readMap(buffer, serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
server = CopycatServer.builder(address)
|
server = CopycatServer.builder(address)
|
||||||
.withStateMachine(stateMachineFactory)
|
.withStateMachine(stateMachineFactory)
|
||||||
@ -141,4 +158,16 @@ class RaftUniquenessProvider(
|
|||||||
fun String.toStateRef() = split(":").let { StateRef(SecureHash.parse(it[0]), it[1].toInt()) }
|
fun String.toStateRef() = split(":").let { StateRef(SecureHash.parse(it[0]), it[1].toInt()) }
|
||||||
return items.map { it.key.toStateRef() to it.value.deserialize<UniquenessProvider.ConsumingTx>() }.toMap()
|
return items.map { it.key.toStateRef() to it.value.deserialize<UniquenessProvider.ConsumingTx>() }.toMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun writeMap(map: Map<*, *>, buffer: BufferOutput<out BufferOutput<*>>, serializer: Serializer) = with(map) {
|
||||||
|
buffer.writeInt(size)
|
||||||
|
forEach {
|
||||||
|
with(serializer) {
|
||||||
|
writeObject(it.key, buffer)
|
||||||
|
writeObject(it.value, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readMap(buffer: BufferInput<out BufferInput<*>>, serializer: Serializer) = LinkedHashMap<Any, Any>().apply { repeat(buffer.readInt()) { put(serializer.readObject(buffer), serializer.readObject(buffer)) } }
|
||||||
|
@ -54,8 +54,6 @@ import java.util.*
|
|||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
import java.util.logging.Level
|
|
||||||
import java.util.logging.Logger
|
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
// TODO: Add command history.
|
// TODO: Add command history.
|
||||||
@ -80,8 +78,6 @@ object InteractiveShell {
|
|||||||
this.node = node
|
this.node = node
|
||||||
var runSSH = runSSHServer
|
var runSSH = runSSHServer
|
||||||
|
|
||||||
Logger.getLogger("").level = Level.OFF // TODO: Is this really needed?
|
|
||||||
|
|
||||||
val config = Properties()
|
val config = Properties()
|
||||||
if (runSSH) {
|
if (runSSH) {
|
||||||
// TODO: Finish and enable SSH access.
|
// TODO: Finish and enable SSH access.
|
||||||
|
@ -18,7 +18,7 @@ class BankOfCordaHttpAPITest {
|
|||||||
startNode(BOC.name, setOf(ServiceInfo(SimpleNotaryService.type))),
|
startNode(BOC.name, setOf(ServiceInfo(SimpleNotaryService.type))),
|
||||||
startNode("BigCorporation")
|
startNode("BigCorporation")
|
||||||
).getOrThrow()
|
).getOrThrow()
|
||||||
val nodeBankOfCordaApiAddr = startWebserver(nodeBankOfCorda).getOrThrow()
|
val nodeBankOfCordaApiAddr = startWebserver(nodeBankOfCorda).getOrThrow().listenAddress
|
||||||
assert(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", "BigCorporation", "1", BOC.name)))
|
assert(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", "BigCorporation", "1", BOC.name)))
|
||||||
}, isDebug = true)
|
}, isDebug = true)
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ class IRSDemoTest : IntegrationTestCategory {
|
|||||||
startWebserver(controller),
|
startWebserver(controller),
|
||||||
startWebserver(nodeA),
|
startWebserver(nodeA),
|
||||||
startWebserver(nodeB)
|
startWebserver(nodeB)
|
||||||
).getOrThrow()
|
).getOrThrow().map { it.listenAddress }
|
||||||
|
|
||||||
val nextFixingDates = getFixingDateObservable(nodeA.configuration)
|
val nextFixingDates = getFixingDateObservable(nodeA.configuration)
|
||||||
val numADeals = getTradeCount(nodeAAddr)
|
val numADeals = getTradeCount(nodeAAddr)
|
||||||
|
@ -36,7 +36,7 @@ class SimmValuationTest : IntegrationTestCategory {
|
|||||||
val (nodeA, nodeB) = Futures.allAsList(startNode(nodeALegalName), startNode(nodeBLegalName)).getOrThrow()
|
val (nodeA, nodeB) = Futures.allAsList(startNode(nodeALegalName), startNode(nodeBLegalName)).getOrThrow()
|
||||||
val (nodeAApi, nodeBApi) = Futures.allAsList(startWebserver(nodeA), startWebserver(nodeB))
|
val (nodeAApi, nodeBApi) = Futures.allAsList(startWebserver(nodeA), startWebserver(nodeB))
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
.map { HttpApi.fromHostAndPort(it, "api/simmvaluationdemo") }
|
.map { HttpApi.fromHostAndPort(it.listenAddress, "api/simmvaluationdemo") }
|
||||||
val nodeBParty = getPartyWithName(nodeAApi, nodeBLegalName)
|
val nodeBParty = getPartyWithName(nodeAApi, nodeBLegalName)
|
||||||
val nodeAParty = getPartyWithName(nodeBApi, nodeALegalName)
|
val nodeAParty = getPartyWithName(nodeBApi, nodeALegalName)
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.testing.node
|
package net.corda.testing.node
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
|
import net.corda.core.getOrThrow
|
||||||
import net.corda.node.driver.DriverDSLExposedInterface
|
import net.corda.node.driver.DriverDSLExposedInterface
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -9,7 +11,7 @@ import kotlin.concurrent.thread
|
|||||||
abstract class DriverBasedTest {
|
abstract class DriverBasedTest {
|
||||||
private val stopDriver = CountDownLatch(1)
|
private val stopDriver = CountDownLatch(1)
|
||||||
private var driverThread: Thread? = null
|
private var driverThread: Thread? = null
|
||||||
private lateinit var driverStarted: CountDownLatch
|
private lateinit var driverStarted: SettableFuture<Unit>
|
||||||
|
|
||||||
protected sealed class RunTestToken {
|
protected sealed class RunTestToken {
|
||||||
internal object Token : RunTestToken()
|
internal object Token : RunTestToken()
|
||||||
@ -18,18 +20,22 @@ abstract class DriverBasedTest {
|
|||||||
protected abstract fun setup(): RunTestToken
|
protected abstract fun setup(): RunTestToken
|
||||||
|
|
||||||
protected fun DriverDSLExposedInterface.runTest(): RunTestToken {
|
protected fun DriverDSLExposedInterface.runTest(): RunTestToken {
|
||||||
driverStarted.countDown()
|
driverStarted.set(Unit)
|
||||||
stopDriver.await()
|
stopDriver.await()
|
||||||
return RunTestToken.Token
|
return RunTestToken.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun start() {
|
fun start() {
|
||||||
driverStarted = CountDownLatch(1)
|
driverStarted = SettableFuture.create()
|
||||||
driverThread = thread {
|
driverThread = thread {
|
||||||
setup()
|
try {
|
||||||
|
setup()
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
driverStarted.setException(t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
driverStarted.await()
|
driverStarted.getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -39,8 +39,7 @@ task buildExplorerJAR(type: FatCapsule) {
|
|||||||
applicationVersion = corda_version
|
applicationVersion = corda_version
|
||||||
systemProperties['visualvm.display.name'] = 'Node Explorer'
|
systemProperties['visualvm.display.name'] = 'Node Explorer'
|
||||||
minJavaVersion = '1.8.0'
|
minJavaVersion = '1.8.0'
|
||||||
// This version is known to work and avoids earlier 8u versions that have bugs.
|
minUpdateVersion['1.8'] = java8_minUpdateVersion
|
||||||
minUpdateVersion['1.8'] = '102'
|
|
||||||
caplets = ['ExplorerCaplet']
|
caplets = ['ExplorerCaplet']
|
||||||
|
|
||||||
// JVM configuration:
|
// JVM configuration:
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.webserver
|
|||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
import net.corda.core.utilities.DUMMY_BANK_A
|
import net.corda.core.utilities.DUMMY_BANK_A
|
||||||
|
import net.corda.node.driver.WebserverHandle
|
||||||
import net.corda.node.driver.addressMustBeBound
|
import net.corda.node.driver.addressMustBeBound
|
||||||
import net.corda.node.driver.addressMustNotBeBound
|
import net.corda.node.driver.addressMustNotBeBound
|
||||||
import net.corda.node.driver.driver
|
import net.corda.node.driver.driver
|
||||||
@ -13,8 +14,8 @@ class DriverTests {
|
|||||||
companion object {
|
companion object {
|
||||||
val executorService = Executors.newScheduledThreadPool(2)
|
val executorService = Executors.newScheduledThreadPool(2)
|
||||||
|
|
||||||
fun webserverMustBeUp(webserverAddr: HostAndPort) {
|
fun webserverMustBeUp(webserverHandle: WebserverHandle) {
|
||||||
addressMustBeBound(executorService, webserverAddr)
|
addressMustBeBound(executorService, webserverHandle.listenAddress, webserverHandle.process)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun webserverMustBeDown(webserverAddr: HostAndPort) {
|
fun webserverMustBeDown(webserverAddr: HostAndPort) {
|
||||||
@ -26,9 +27,9 @@ class DriverTests {
|
|||||||
fun `starting a node and independent web server works`() {
|
fun `starting a node and independent web server works`() {
|
||||||
val addr = driver {
|
val addr = driver {
|
||||||
val node = startNode(DUMMY_BANK_A.name).getOrThrow()
|
val node = startNode(DUMMY_BANK_A.name).getOrThrow()
|
||||||
val webserverAddr = startWebserver(node).getOrThrow()
|
val webserverHandle = startWebserver(node).getOrThrow()
|
||||||
webserverMustBeUp(webserverAddr)
|
webserverMustBeUp(webserverHandle)
|
||||||
webserverAddr
|
webserverHandle.listenAddress
|
||||||
}
|
}
|
||||||
webserverMustBeDown(addr)
|
webserverMustBeDown(addr)
|
||||||
}
|
}
|
||||||
|
@ -53,8 +53,7 @@ task buildWebserverJar(type: FatCapsule) {
|
|||||||
systemProperties['visualvm.display.name'] = 'Corda Webserver'
|
systemProperties['visualvm.display.name'] = 'Corda Webserver'
|
||||||
systemProperties['corda.version'] = corda_version
|
systemProperties['corda.version'] = corda_version
|
||||||
minJavaVersion = '1.8.0'
|
minJavaVersion = '1.8.0'
|
||||||
// This version is known to work and avoids earlier 8u versions that have bugs.
|
minUpdateVersion['1.8'] = java8_minUpdateVersion
|
||||||
minUpdateVersion['1.8'] = '102'
|
|
||||||
caplets = ['CordaCaplet']
|
caplets = ['CordaCaplet']
|
||||||
|
|
||||||
// JVM configuration:
|
// JVM configuration:
|
||||||
@ -81,4 +80,4 @@ publish {
|
|||||||
name = 'corda-webserver'
|
name = 'corda-webserver'
|
||||||
publishWar = false // TODO: Use WAR instead of JAR
|
publishWar = false // TODO: Use WAR instead of JAR
|
||||||
disableDefaultJar = true
|
disableDefaultJar = true
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user