ENT-11065: Remove the need for JVM flags in client code (#7635)

This commit is contained in:
Shams Asari 2024-01-03 11:22:03 +00:00 committed by GitHub
parent 406f7ff292
commit 2e63ca6264
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 470 additions and 830 deletions

View File

@ -121,23 +121,6 @@ buildscript {
ext.fontawesomefx_commons_version = constants.getProperty("fontawesomefxCommonsVersion") ext.fontawesomefx_commons_version = constants.getProperty("fontawesomefxCommonsVersion")
ext.fontawesomefx_fontawesome_version = constants.getProperty("fontawesomefxFontawesomeVersion") ext.fontawesomefx_fontawesome_version = constants.getProperty("fontawesomefxFontawesomeVersion")
ext.javaassist_version = constants.getProperty("javaassistVersion") ext.javaassist_version = constants.getProperty("javaassistVersion")
ext.test_add_opens = [
'--add-opens', 'java.base/java.time=ALL-UNNAMED',
'--add-opens', 'java.base/java.io=ALL-UNNAMED',
'--add-opens', 'java.base/java.util=ALL-UNNAMED',
'--add-opens', 'java.base/java.net=ALL-UNNAMED',
'--add-opens', 'java.base/java.nio=ALL-UNNAMED',
'--add-opens', 'java.base/java.lang.invoke=ALL-UNNAMED',
'--add-opens', 'java.base/java.security.cert=ALL-UNNAMED',
'--add-opens', 'java.base/java.security=ALL-UNNAMED',
'--add-opens', 'java.base/javax.net.ssl=ALL-UNNAMED',
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
'--add-opens', 'java.base/java.util.concurrent=ALL-UNNAMED',
'--add-opens', 'java.sql/java.sql=ALL-UNNAMED'
]
ext.test_add_exports = [
'--add-exports', 'java.base/sun.nio.ch=ALL-UNNAMED'
]
ext.corda_revision = { ext.corda_revision = {
try { try {
@ -282,11 +265,6 @@ allprojects {
toolVersion = "0.8.7" toolVersion = "0.8.7"
} }
test {
jvmArgs test_add_opens
jvmArgs test_add_exports
}
java { java {
withSourcesJar() withSourcesJar()
withJavadocJar() withJavadocJar()
@ -332,13 +310,12 @@ allprojects {
} }
tasks.withType(Test).configureEach { tasks.withType(Test).configureEach {
jvmArgs += project(":node:capsule").file("src/main/resources/node-jvm-args.txt").readLines()
jvmArgs += "--add-modules=jdk.incubator.foreign" // For the SharedMemoryIncremental
forkEvery = 20 forkEvery = 20
ignoreFailures = project.hasProperty('tests.ignoreFailures') ? project.property('tests.ignoreFailures').toBoolean() : false ignoreFailures = project.hasProperty('tests.ignoreFailures') ? project.property('tests.ignoreFailures').toBoolean() : false
failFast = project.hasProperty('tests.failFast') ? project.property('tests.failFast').toBoolean() : false failFast = project.hasProperty('tests.failFast') ? project.property('tests.failFast').toBoolean() : false
// Prevent the project from creating temporary files outside of the build directory.
systemProperty 'java.io.tmpdir', buildDir.absolutePath
maxHeapSize = "1g" maxHeapSize = "1g"
if (project.path.startsWith(':experimental') && System.getProperty("experimental.test.enable") == null) { if (project.path.startsWith(':experimental') && System.getProperty("experimental.test.enable") == null) {
@ -351,15 +328,15 @@ allprojects {
// ex.append = false // ex.append = false
} }
maxParallelForks = (System.env.CORDA_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_TESTING_FORKS".toInteger()
systemProperty 'java.security.egd', 'file:/dev/./urandom'
}
tasks.withType(Test).configureEach {
if (name.contains("integrationTest")) { if (name.contains("integrationTest")) {
maxParallelForks = (System.env.CORDA_INT_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_INT_TESTING_FORKS".toInteger() maxParallelForks = (System.env.CORDA_INT_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_INT_TESTING_FORKS".toInteger()
} else {
maxParallelForks = (System.env.CORDA_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_TESTING_FORKS".toInteger()
} }
// Prevent the project from creating temporary files outside of the build directory.
systemProperty 'java.io.tmpdir', buildDir.absolutePath
systemProperty 'java.security.egd', 'file:/dev/./urandom'
} }
group 'net.corda' group 'net.corda'

View File

@ -10,6 +10,9 @@ description 'Corda client RPC modules'
configurations { configurations {
integrationTestImplementation.extendsFrom testImplementation integrationTestImplementation.extendsFrom testImplementation
integrationTestRuntimeOnly.extendsFrom testRuntimeOnly integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
smokeTestImplementation.extendsFrom compile
smokeTestRuntimeOnly.extendsFrom runtimeOnly
} }
sourceSets { sourceSets {
@ -28,55 +31,95 @@ sourceSets {
srcDirs "src/integration-test/resources" srcDirs "src/integration-test/resources"
} }
} }
smokeTest {
kotlin {
// We must NOT have any Node code on the classpath, so do NOT
// include the test or integrationTest dependencies here.
compileClasspath += main.output
runtimeClasspath += main.output
srcDir file('src/smoke-test/kotlin')
}
java {
compileClasspath += main.output
runtimeClasspath += main.output
srcDir file('src/smoke-test/java')
}
}
} }
dependencies { dependencies {
implementation project(':core') implementation project(':core')
implementation project(':node-api') implementation project(':node-api')
implementation project(':serialization') implementation project(':serialization')
// For caches rather than guava // For caches rather than guava
implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version" implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "io.reactivex:rxjava:$rxjava_version"
testImplementation "junit:junit:$junit_version" implementation("org.apache.activemq:artemis-core-client:${artemis_version}") {
exclude group: 'org.jgroups', module: 'jgroups'
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}" }
testRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}" implementation "com.google.guava:guava-testlib:$guava_version"
// Unit testing helpers.
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation "org.assertj:assertj-core:${assertj_version}"
testImplementation "io.dropwizard.metrics:metrics-core:$metrics_version"
testImplementation project(':node') testImplementation project(':node')
testImplementation project(':node-driver') testImplementation project(':node-driver')
testImplementation project(':client:mock') testImplementation project(':client:mock')
testImplementation project(':core-test-utils') testImplementation project(':core-test-utils')
testImplementation "junit:junit:$junit_version"
// Unit testing helpers.
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation "org.assertj:assertj-core:${assertj_version}"
testImplementation "io.dropwizard.metrics:metrics-core:$metrics_version"
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
testRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
integrationTestImplementation project(path: ':node-api', configuration: 'testArtifacts') integrationTestImplementation project(path: ':node-api', configuration: 'testArtifacts')
integrationTestImplementation project(':common-configuration-parsing') integrationTestImplementation project(':common-configuration-parsing')
integrationTestImplementation project(':finance:contracts') integrationTestImplementation project(':finance:contracts')
integrationTestImplementation project(':finance:workflows') integrationTestImplementation project(':finance:workflows')
integrationTestImplementation project(':test-utils') integrationTestImplementation project(':test-utils')
integrationTestImplementation "co.paralleluniverse:quasar-core:$quasar_version" integrationTestImplementation "co.paralleluniverse:quasar-core:$quasar_version"
integrationTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version" integrationTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version"
implementation "io.reactivex:rxjava:$rxjava_version" smokeTestImplementation project(':core')
implementation("org.apache.activemq:artemis-core-client:${artemis_version}") { smokeTestImplementation project(':node-api')
exclude group: 'org.jgroups', module: 'jgroups' smokeTestImplementation project(':finance:contracts')
} smokeTestImplementation project(':finance:workflows')
implementation "com.google.guava:guava-testlib:$guava_version" smokeTestImplementation project(':smoke-test-utils')
smokeTestImplementation project(':testing:cordapps:sleeping')
smokeTestImplementation "junit:junit:$junit_version"
smokeTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
smokeTestImplementation "com.google.guava:guava:$guava_version"
smokeTestImplementation "org.hamcrest:hamcrest-library:2.1"
smokeTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
smokeTestRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
} }
task integrationTest(type: Test) { processSmokeTestResources {
// Bring in the fully built corda.jar for use by NodeFactory in the smoke tests
from(project(":node:capsule").tasks['buildCordaJAR']) {
rename 'corda-(.*)', 'corda.jar'
}
from(project(':finance:workflows').tasks['jar']) {
rename '.*finance-workflows-.*', 'cordapp-finance-workflows.jar'
}
from(project(':finance:contracts').tasks['jar']) {
rename '.*finance-contracts-.*', 'cordapp-finance-contracts.jar'
}
from(project(':testing:cordapps:sleeping').tasks['jar']) {
rename 'testing-sleeping-cordapp-*', 'cordapp-sleeping.jar'
}
}
tasks.register('integrationTest', Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath classpath = sourceSets.integrationTest.runtimeClasspath
}
jvmArgs test_add_opens tasks.register('smokeTest', Test) {
jvmArgs test_add_exports testClassesDirs = sourceSets.smokeTest.output.classesDirs
classpath = sourceSets.smokeTest.runtimeClasspath
} }
jar { jar {

View File

@ -54,7 +54,7 @@ import org.junit.Test
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.net.URLClassLoader import java.net.URLClassLoader
import java.nio.file.Paths import java.nio.file.Paths
import java.util.* import java.util.Currency
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
@ -255,21 +255,12 @@ class CordaRPCClientTest : NodeBasedTest(FINANCE_CORDAPPS, notaries = listOf(DUM
fun `additional class loader used by WireTransaction when it deserialises its components`() { fun `additional class loader used by WireTransaction when it deserialises its components`() {
val financeLocation = Cash::class.java.location.toPath().toString() val financeLocation = Cash::class.java.location.toPath().toString()
val classPathWithoutFinance = ProcessUtilities.defaultClassPath.filter { financeLocation !in it } val classPathWithoutFinance = ProcessUtilities.defaultClassPath.filter { financeLocation !in it }
val moduleOpens = listOf(
"--add-opens", "java.base/java.time=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED",
"--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.net=ALL-UNNAMED",
"--add-opens", "java.base/java.nio=ALL-UNNAMED", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED",
"--add-opens", "java.base/java.security.cert=ALL-UNNAMED", "--add-opens", "java.base/javax.net.ssl=ALL-UNNAMED",
"--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", "--add-opens", "java.sql/java.sql=ALL-UNNAMED",
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
)
// Create a Cash.State object for the StandaloneCashRpcClient to get // Create a Cash.State object for the StandaloneCashRpcClient to get
node.services.startFlow(CashIssueFlow(100.POUNDS, OpaqueBytes.of(1), identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow() node.services.startFlow(CashIssueFlow(100.POUNDS, OpaqueBytes.of(1), identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow()
val outOfProcessRpc = ProcessUtilities.startJavaProcess<StandaloneCashRpcClient>( val outOfProcessRpc = ProcessUtilities.startJavaProcess<StandaloneCashRpcClient>(
classPath = classPathWithoutFinance, classPath = classPathWithoutFinance,
arguments = listOf(node.node.configuration.rpcOptions.address.toString(), financeLocation), arguments = listOf(node.node.configuration.rpcOptions.address.toString(), financeLocation)
extraJvmArguments = moduleOpens
) )
assertThat(outOfProcessRpc.waitFor()).isZero() // i.e. no exceptions were thrown assertThat(outOfProcessRpc.waitFor()).isZero() // i.e. no exceptions were thrown
} }

View File

@ -89,7 +89,6 @@ class RPCStabilityTests {
} }
@Test(timeout=300_000) @Test(timeout=300_000)
@Ignore("TODO JDK17:Fixme")
fun `client and server dont leak threads`() { fun `client and server dont leak threads`() {
fun startAndStop() { fun startAndStop() {
rpcDriver { rpcDriver {
@ -122,7 +121,6 @@ class RPCStabilityTests {
} }
@Test(timeout=300_000) @Test(timeout=300_000)
@Ignore("TODO JDK17:Fixme")
fun `client doesnt leak threads when it fails to start`() { fun `client doesnt leak threads when it fails to start`() {
fun startAndStop() { fun startAndStop() {
rpcDriver { rpcDriver {
@ -491,7 +489,6 @@ class RPCStabilityTests {
* In this test we create a number of out of process RPC clients that call [TrackSubscriberOps.subscribe] in a loop. * In this test we create a number of out of process RPC clients that call [TrackSubscriberOps.subscribe] in a loop.
*/ */
@Test(timeout=300_000) @Test(timeout=300_000)
@Ignore("TODO JDK17:Fixme")
fun `server cleans up queues after disconnected clients`() { fun `server cleans up queues after disconnected clients`() {
rpcDriver { rpcDriver {
val trackSubscriberOpsImpl = object : TrackSubscriberOps { val trackSubscriberOpsImpl = object : TrackSubscriberOps {
@ -547,7 +544,7 @@ class RPCStabilityTests {
} }
@Test(timeout=300_000) @Test(timeout=300_000)
@Ignore // TODO: This is ignored because Artemis slow consumers are broken. I'm not deleting it in case we can get the feature fixed. @Ignore // TODO: This is ignored because Artemis slow consumers are broken. I'm not deleting it in case we can get the feature fixed.
fun `slow consumers are kicked`() { fun `slow consumers are kicked`() {
rpcDriver { rpcDriver {
val server = startRpcServer(maxBufferedBytesPerClient = 10 * 1024 * 1024, ops = SlowConsumerRPCOpsImpl()).get() val server = startRpcServer(maxBufferedBytesPerClient = 10 * 1024 * 1024, ops = SlowConsumerRPCOpsImpl()).get()

View File

@ -40,7 +40,6 @@ import net.corda.nodeapi.internal.config.User
import net.corda.sleeping.SleepingFlow import net.corda.sleeping.SleepingFlow
import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess import net.corda.smoketesting.NodeProcess
import org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM
import org.hamcrest.text.MatchesPattern import org.hamcrest.text.MatchesPattern
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
@ -50,6 +49,7 @@ import org.junit.Test
import org.junit.rules.ExpectedException import org.junit.rules.ExpectedException
import java.io.FilterInputStream import java.io.FilterInputStream
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream.nullOutputStream
import java.util.Currency import java.util.Currency
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -67,7 +67,7 @@ class StandaloneCordaRPClientTest {
val rpcUser = User("rpcUser", "test", permissions = setOf("InvokeRpc.startFlow", "InvokeRpc.killFlow")) val rpcUser = User("rpcUser", "test", permissions = setOf("InvokeRpc.startFlow", "InvokeRpc.killFlow"))
val flowUser = User("flowUser", "test", permissions = setOf("StartFlow.net.corda.finance.flows.CashIssueFlow")) val flowUser = User("flowUser", "test", permissions = setOf("StartFlow.net.corda.finance.flows.CashIssueFlow"))
val port = AtomicInteger(15200) val port = AtomicInteger(15200)
const val attachmentSize = 2116 const val ATTACHMENT_SIZE = 2116
val timeout = 60.seconds val timeout = 60.seconds
} }
@ -111,13 +111,13 @@ class StandaloneCordaRPClientTest {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test attachments`() { fun `test attachments`() {
val attachment = InputStreamAndHash.createInMemoryTestZip(attachmentSize, 1) val attachment = InputStreamAndHash.createInMemoryTestZip(ATTACHMENT_SIZE, 1)
assertFalse(rpcProxy.attachmentExists(attachment.sha256)) assertFalse(rpcProxy.attachmentExists(attachment.sha256))
val id = attachment.inputStream.use { rpcProxy.uploadAttachment(it) } val id = attachment.inputStream.use { rpcProxy.uploadAttachment(it) }
assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash") assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash")
val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { it -> val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use {
it.copyTo(NULL_OUTPUT_STREAM) it.copyTo(nullOutputStream())
SecureHash.createSHA256(it.hash().asBytes()) SecureHash.createSHA256(it.hash().asBytes())
} }
assertEquals(attachment.sha256, hash) assertEquals(attachment.sha256, hash)
@ -126,13 +126,13 @@ class StandaloneCordaRPClientTest {
@Ignore("CORDA-1520 - After switching from Kryo to AMQP this test won't work") @Ignore("CORDA-1520 - After switching from Kryo to AMQP this test won't work")
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test wrapped attachments`() { fun `test wrapped attachments`() {
val attachment = InputStreamAndHash.createInMemoryTestZip(attachmentSize, 1) val attachment = InputStreamAndHash.createInMemoryTestZip(ATTACHMENT_SIZE, 1)
assertFalse(rpcProxy.attachmentExists(attachment.sha256)) assertFalse(rpcProxy.attachmentExists(attachment.sha256))
val id = WrapperStream(attachment.inputStream).use { rpcProxy.uploadAttachment(it) } val id = WrapperStream(attachment.inputStream).use { rpcProxy.uploadAttachment(it) }
assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash") assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash")
val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { it -> val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use {
it.copyTo(NULL_OUTPUT_STREAM) it.copyTo(nullOutputStream())
SecureHash.createSHA256(it.hash().asBytes()) SecureHash.createSHA256(it.hash().asBytes())
} }
assertEquals(attachment.sha256, hash) assertEquals(attachment.sha256, hash)

View File

@ -107,6 +107,7 @@ dependencies {
smokeTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}" smokeTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
smokeTestRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}" smokeTestRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
smokeTestRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}" smokeTestRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
smokeTestRuntimeOnly "org.slf4j:slf4j-simple:$slf4j_version"
smokeTestCompile project(':smoke-test-utils') smokeTestCompile project(':smoke-test-utils')
smokeTestCompile "org.assertj:assertj-core:${assertj_version}" smokeTestCompile "org.assertj:assertj-core:${assertj_version}"
@ -143,9 +144,6 @@ task smokeTest(type: Test) {
dependsOn smokeTestJar dependsOn smokeTestJar
testClassesDirs = sourceSets.smokeTest.output.classesDirs testClassesDirs = sourceSets.smokeTest.output.classesDirs
classpath = sourceSets.smokeTest.runtimeClasspath classpath = sourceSets.smokeTest.runtimeClasspath
jvmArgs test_add_opens
jvmArgs test_add_exports
} }
idea { idea {

View File

@ -48,7 +48,7 @@ class NodeVersioningTest {
users = listOf(superUser) users = listOf(superUser)
) )
private lateinit var notary: NodeProcess private var notary: NodeProcess? = null
@Before @Before
fun setUp() { fun setUp() {
@ -57,7 +57,7 @@ class NodeVersioningTest {
@After @After
fun done() { fun done() {
notary.close() notary?.close()
} }
@Test(timeout=300_000) @Test(timeout=300_000)

View File

@ -23,16 +23,18 @@ configurations {
} }
dependencies { dependencies {
// These are exposed in our public APIs and are thus "api" dependencies
api "org.slf4j:slf4j-api:$slf4j_version"
// RxJava: observable streams of events.
api "io.reactivex:rxjava:$rxjava_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
// SLF4J: commons-logging bindings for a SLF4J back end // SLF4J: commons-logging bindings for a SLF4J back end
implementation "org.slf4j:jcl-over-slf4j:$slf4j_version" implementation "org.slf4j:jcl-over-slf4j:$slf4j_version"
implementation "org.slf4j:slf4j-api:$slf4j_version"
// Guava: Google utilities library. // Guava: Google utilities library.
implementation "com.google.guava:guava:$guava_version" implementation "com.google.guava:guava:$guava_version"
// For caches rather than guava // For caches rather than guava
implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version" implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
// RxJava: observable streams of events.
implementation "io.reactivex:rxjava:$rxjava_version"
implementation "org.apache.commons:commons-lang3:$commons_lang3_version" implementation "org.apache.commons:commons-lang3:$commons_lang3_version"
// Java ed25519 implementation. See https://github.com/str4d/ed25519-java/ // Java ed25519 implementation. See https://github.com/str4d/ed25519-java/
implementation "net.i2p.crypto:eddsa:$eddsa_version" implementation "net.i2p.crypto:eddsa:$eddsa_version"
@ -80,10 +82,6 @@ jar {
finalizedBy(copyQuasarJar) finalizedBy(copyQuasarJar)
archiveBaseName = 'corda-core' archiveBaseName = 'corda-core'
archiveClassifier = '' archiveClassifier = ''
manifest {
attributes('Add-Opens': 'java.base/java.net java.base/java.nio')
}
} }
processTestResources { processTestResources {
@ -103,6 +101,7 @@ compileTestJava {
} }
test { test {
// TODO This obscures whether any Corda client APIs need these JVM flags as well (which they shouldn't do)
jvmArgs += [ jvmArgs += [
'--add-exports', 'java.base/sun.security.util=ALL-UNNAMED', '--add-exports', 'java.base/sun.security.util=ALL-UNNAMED',
'--add-exports', 'java.base/sun.security.x509=ALL-UNNAMED' '--add-exports', 'java.base/sun.security.x509=ALL-UNNAMED'

View File

@ -166,7 +166,7 @@ fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.c
fun InputStream.readFully(): ByteArray = use { it.readBytes() } fun InputStream.readFully(): ByteArray = use { it.readBytes() }
/** Calculate the hash of the remaining bytes in this input stream. The stream is closed at the end. */ /** Calculate the hash of the remaining bytes in this input stream. The stream is closed at the end. */
fun InputStream.hash(): SecureHash { fun InputStream.hash(): SecureHash.SHA256 {
return use { return use {
val md = MessageDigest.getInstance("SHA-256") val md = MessageDigest.getInstance("SHA-256")
val buffer = ByteArray(DEFAULT_BUFFER_SIZE) val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
@ -309,6 +309,8 @@ inline fun <T, R : Any> Stream<T>.mapNotNull(crossinline transform: (T) -> R?):
/** Similar to [Collectors.toSet] except the Set is guaranteed to be ordered. */ /** Similar to [Collectors.toSet] except the Set is guaranteed to be ordered. */
fun <T> Stream<T>.toSet(): Set<T> = collect(toCollection { LinkedHashSet<T>() }) fun <T> Stream<T>.toSet(): Set<T> = collect(toCollection { LinkedHashSet<T>() })
val Class<*>.isJdkClass: Boolean get() = module.name?.startsWith("java.") == true
fun <T> Class<T>.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null fun <T> Class<T>.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null
/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */ /** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */

View File

@ -88,7 +88,7 @@ inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption =
inline fun <reified T : Any> Path.readObject(): T = readBytes().deserialize() inline fun <reified T : Any> Path.readObject(): T = readBytes().deserialize()
/** Calculate the hash of the contents of this file. */ /** Calculate the hash of the contents of this file. */
inline val Path.hash: SecureHash get() = read { it.hash() } inline val Path.hash: SecureHash.SHA256 get() = read { it.hash() }
/* Check if the Path is symbolic link */ /* Check if the Path is symbolic link */
fun Path.safeSymbolicRead(): Path = if (isSymbolicLink()) readSymbolicLink() else this fun Path.safeSymbolicRead(): Path = if (isSymbolicLink()) readSymbolicLink() else this

View File

@ -93,26 +93,16 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
* or use a decorator and reflection to bypass the single-call-per-JVM restriction otherwise. * or use a decorator and reflection to bypass the single-call-per-JVM restriction otherwise.
*/ */
private fun setOrDecorateURLStreamHandlerFactory() { private fun setOrDecorateURLStreamHandlerFactory() {
// Retrieve the `URL.factory` field try {
val factoryField = URL::class.java.getDeclaredField("factory")
// Make it accessible
factoryField.isAccessible = true
// Check for preset factory, set directly if missing
val existingFactory: URLStreamHandlerFactory? = factoryField.get(null) as URLStreamHandlerFactory?
if (existingFactory == null) {
URL.setURLStreamHandlerFactory(AttachmentURLStreamHandlerFactory) URL.setURLStreamHandlerFactory(AttachmentURLStreamHandlerFactory)
} } catch (e: Error) {
// Otherwise, decorate the existing and replace via reflection
// as calling `URL.setURLStreamHandlerFactory` again will throw an error
else {
log.warn("The URLStreamHandlerFactory was already set in the JVM. Please be aware that this is not recommended.") log.warn("The URLStreamHandlerFactory was already set in the JVM. Please be aware that this is not recommended.")
val factoryField = URL::class.java.getDeclaredField("factory").apply { isAccessible = true }
// Retrieve the field "streamHandlerLock" of the class URL that // Retrieve the field "streamHandlerLock" of the class URL that
// is the lock used to synchronize access to the protocol handlers // is the lock used to synchronize access to the protocol handlers
val lockField = URL::class.java.getDeclaredField("streamHandlerLock") val lockField = URL::class.java.getDeclaredField("streamHandlerLock").apply { isAccessible = true }
// It is a private field so we need to make it accessible // It is a private field so we need to make it accessible
// Note: this will only work as-is in JDK8. val existingFactory = factoryField.get(null) as URLStreamHandlerFactory?
lockField.isAccessible = true
// Use the same lock to reset the factory // Use the same lock to reset the factory
synchronized(lockField.get(null)) { synchronized(lockField.get(null)) {
// Reset the value to prevent Error due to a factory already defined // Reset the value to prevent Error due to a factory already defined
@ -121,7 +111,7 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
URL.setURLStreamHandlerFactory { protocol -> URL.setURLStreamHandlerFactory { protocol ->
// route between our own and the pre-existing factory // route between our own and the pre-existing factory
AttachmentURLStreamHandlerFactory.createURLStreamHandler(protocol) AttachmentURLStreamHandlerFactory.createURLStreamHandler(protocol)
?: existingFactory.createURLStreamHandler(protocol) ?: existingFactory?.createURLStreamHandler(protocol)
} }
} }
} }

View File

@ -2,7 +2,6 @@ package net.corda.core.utilities
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.declaredField
import org.assertj.core.api.Assertions.catchThrowable import org.assertj.core.api.Assertions.catchThrowable
import org.junit.Assert.assertSame import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
@ -14,22 +13,17 @@ import kotlin.test.assertEquals
class ByteArraysTest { class ByteArraysTest {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `slice works`() { fun `slice works`() {
byteArrayOf(9, 9, 0, 1, 2, 3, 4, 9, 9).let { sliceWorksImpl(OpaqueBytesSubSequence(byteArrayOf(9, 9, 0, 1, 2, 3, 4, 9, 9), 2, 5))
sliceWorksImpl(it, OpaqueBytesSubSequence(it, 2, 5)) sliceWorksImpl(OpaqueBytes(byteArrayOf(0, 1, 2, 3, 4)))
}
byteArrayOf(0, 1, 2, 3, 4).let {
sliceWorksImpl(it, OpaqueBytes(it))
}
} }
private fun sliceWorksImpl(array: ByteArray, seq: ByteSequence) { private fun sliceWorksImpl(seq: ByteSequence) {
// Python-style negative indices can be implemented later if needed: // Python-style negative indices can be implemented later if needed:
assertSame(IllegalArgumentException::class.java, catchThrowable { seq.slice(-1) }.javaClass) assertSame(IllegalArgumentException::class.java, catchThrowable { seq.slice(-1) }.javaClass)
assertSame(IllegalArgumentException::class.java, catchThrowable { seq.slice(end = -1) }.javaClass) assertSame(IllegalArgumentException::class.java, catchThrowable { seq.slice(end = -1) }.javaClass)
fun check(expected: ByteArray, actual: ByteBuffer) { fun check(expected: ByteArray, actual: ByteBuffer) {
assertEquals(ByteBuffer.wrap(expected), actual) assertEquals(ByteBuffer.wrap(expected), actual)
assertSame(ReadOnlyBufferException::class.java, catchThrowable { actual.array() }.javaClass) assertSame(ReadOnlyBufferException::class.java, catchThrowable { actual.array() }.javaClass)
assertSame(array, actual.declaredField<ByteArray>(ByteBuffer::class, "hb").value)
} }
check(byteArrayOf(0, 1, 2, 3, 4), seq.slice()) check(byteArrayOf(0, 1, 2, 3, 4), seq.slice())
check(byteArrayOf(0, 1, 2, 3, 4), seq.slice(0, 5)) check(byteArrayOf(0, 1, 2, 3, 4), seq.slice(0, 5))
@ -48,14 +42,14 @@ class ByteArraysTest {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test hex parsing strictly uppercase`() { fun `test hex parsing strictly uppercase`() {
val HEX_REGEX = "^[0-9A-F]+\$".toRegex() val hexRegex = "^[0-9A-F]+\$".toRegex()
val privacySalt = net.corda.core.contracts.PrivacySalt() val privacySalt = net.corda.core.contracts.PrivacySalt()
val privacySaltAsHexString = privacySalt.bytes.toHexString() val privacySaltAsHexString = privacySalt.bytes.toHexString()
assertTrue(privacySaltAsHexString.matches(HEX_REGEX)) assertTrue(privacySaltAsHexString.matches(hexRegex))
val stateRef = StateRef(SecureHash.randomSHA256(), 0) val stateRef = StateRef(SecureHash.randomSHA256(), 0)
val txhashAsHexString = stateRef.txhash.bytes.toHexString() val txhashAsHexString = stateRef.txhash.bytes.toHexString()
assertTrue(txhashAsHexString.matches(HEX_REGEX)) assertTrue(txhashAsHexString.matches(hexRegex))
} }
} }

View File

@ -35,17 +35,6 @@ shadowJar {
version = null version = null
zip64 true zip64 true
exclude '**/Log4j2Plugins.dat' exclude '**/Log4j2Plugins.dat'
manifest {
attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver ' +
'java.base/java.time java.base/java.io ' +
'java.base/java.util java.base/java.net ' +
'java.base/java.nio java.base/java.lang.invoke ' +
'java.base/java.security.cert java.base/java.security ' +
'java.base/javax.net.ssl java.base/java.util.concurrent ' +
'java.sql/java.sql'
)
}
} }
enum ImageVariant { enum ImageVariant {

View File

@ -64,9 +64,6 @@ task testJar(type: Jar) {
task integrationTest(type: Test, dependsOn: []) { task integrationTest(type: Test, dependsOn: []) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath classpath = sourceSets.integrationTest.runtimeClasspath
jvmArgs test_add_opens
jvmArgs test_add_exports
} }
jar { jar {

View File

@ -1,5 +1,5 @@
kotlin.incremental=true kotlin.incremental=true
org.gradle.jvmargs=-Xmx6g -Dfile.encoding=UTF-8 --add-opens 'java.base/java.time=ALL-UNNAMED' --add-opens 'java.base/java.io=ALL-UNNAMED' org.gradle.jvmargs=-Xmx6g -Dfile.encoding=UTF-8'
org.gradle.caching=false org.gradle.caching=false
owasp.failOnError=false owasp.failOnError=false
owasp.failBuildOnCVSS=11.0 owasp.failBuildOnCVSS=11.0

View File

@ -104,10 +104,6 @@ artifacts {
jar { jar {
baseName 'corda-node-api' baseName 'corda-node-api'
manifest {
attributes('Add-Opens': 'java.base/java.io java.base/java.time java.base/java.util java.base/java.lang.invoke java.base/java.security')
}
} }
publishing { publishing {

View File

@ -17,9 +17,7 @@ import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.lang.reflect.ParameterizedType
import java.net.Proxy import java.net.Proxy
import java.net.URL import java.net.URL
import java.nio.file.Path import java.nio.file.Path
@ -28,6 +26,7 @@ import java.time.Duration
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.temporal.Temporal import java.time.temporal.Temporal
import java.time.temporal.TemporalAmount
import java.util.Properties import java.util.Properties
import java.util.UUID import java.util.UUID
import javax.security.auth.x500.X500Principal import javax.security.auth.x500.X500Principal
@ -298,104 +297,45 @@ private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T {
*/ */
fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig() fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig()
fun Any?.toConfigValue(): ConfigValue = if (this is ConfigValue) { fun Any?.toConfigValue(): ConfigValue = ConfigValueFactory.fromAnyRef(sanitiseForFromAnyRef(this))
this
} else if (this != null) {
ConfigValueFactory.fromAnyRef(convertValue(this))
} else {
ConfigValueFactory.fromAnyRef(null)
}
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
// Reflect over the fields of the receiver and generate a value Map that can use to create Config object. // Reflect over the fields of the receiver and generate a value Map that can use to create Config object.
private fun Any.toConfigMap(): Map<String, Any> { private fun Any.toConfigMap(): Map<String, Any?> {
val values = HashMap<String, Any>() val values = LinkedHashMap<String, Any?>()
for (field in javaClass.declaredFields) { for (field in javaClass.declaredFields) {
if (field.isStatic || field.isSynthetic) continue if (field.isStatic || field.isSynthetic) continue
field.isAccessible = true field.isAccessible = true
val value = field.get(this) ?: continue val value = field.get(this) ?: continue
val configValue = if (value is String || value is Boolean || value is Number) { values[field.name] = sanitiseForFromAnyRef(value)
// These types are supported by Config as use as is
value
} else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name ||
value is Path || value is URL || value is UUID || value is X500Principal) {
// These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
value.toString()
} else if (value is Enum<*>) {
// Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
value.name
} else if (value is Properties) {
// For Properties we treat keys with . as nested configs
ConfigFactory.parseMap(uncheckedCast(value)).root()
} else if (value is Iterable<*>) {
value.toConfigIterable(field)
} else {
// Else this is a custom object recursed over
value.toConfigMap()
}
values[field.name] = configValue
} }
return values return values
} }
private fun convertValue(value: Any): Any { /**
* @see ConfigValueFactory.fromAnyRef
return if (value is String || value is Boolean || value is Number) { */
private fun sanitiseForFromAnyRef(value: Any?): Any? {
return when (value) {
// These types are supported by Config as use as is // These types are supported by Config as use as is
value is String, is Boolean, is Number, is ConfigValue, is Duration, null -> value
} else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || is Enum<*> -> value.name
value is Path || value is URL || value is UUID || value is X500Principal) { // These types make sense to be represented as Strings
// These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs is Temporal, is TemporalAmount, is NetworkHostAndPort, is CordaX500Name, is Path, is URL, is UUID, is X500Principal -> value.toString()
value.toString()
} else if (value is Enum<*>) {
// Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
value.name
} else if (value is Properties) {
// For Properties we treat keys with . as nested configs // For Properties we treat keys with . as nested configs
ConfigFactory.parseMap(uncheckedCast(value)).root() is Properties -> ConfigFactory.parseMap(uncheckedCast(value)).root()
} else if (value is Iterable<*>) { is Map<*, *> -> ConfigFactory.parseMap(value.map { it.key.toString() to sanitiseForFromAnyRef(it.value) }.toMap()).root()
value.toConfigIterable() is Iterable<*> -> value.map(::sanitiseForFromAnyRef)
} else {
// Else this is a custom object recursed over // Else this is a custom object recursed over
value.toConfigMap() else -> value.toConfigMap()
} }
} }
// For Iterables figure out the type parameter and apply the same logic as above on the individual elements.
private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
return when (elementType) {
// For the types already supported by Config we can use the Iterable as is
String::class.java -> this
Integer::class.java -> this
java.lang.Long::class.java -> this
java.lang.Double::class.java -> this
java.lang.Boolean::class.java -> this
LocalDate::class.java -> map(Any?::toString)
Instant::class.java -> map(Any?::toString)
NetworkHostAndPort::class.java -> map(Any?::toString)
Path::class.java -> map(Any?::toString)
URL::class.java -> map(Any?::toString)
X500Principal::class.java -> map(Any?::toString)
UUID::class.java -> map(Any?::toString)
CordaX500Name::class.java -> map(Any?::toString)
Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() }
else -> if (elementType.isEnum) {
map { (it as Enum<*>).name }
} else {
map { it?.toConfigMap() }
}
}
}
private fun Iterable<*>.toConfigIterable(): Iterable<Any?> = map { element -> element?.let(::convertValue) }
// The typesafe .getBoolean function is case sensitive, this is a case insensitive version // The typesafe .getBoolean function is case sensitive, this is a case insensitive version
fun Config.getBooleanCaseInsensitive(path: String): Boolean { fun Config.getBooleanCaseInsensitive(path: String): Boolean {
try { try {
return getBoolean(path) return getBoolean(path)
} catch(e:Exception) { } catch (e: Exception) {
val stringVal = getString(path).toLowerCase() val stringVal = getString(path).lowercase()
if (stringVal == "true" || stringVal == "false") { if (stringVal == "true" || stringVal == "false") {
return stringVal.toBoolean() return stringVal.toBoolean()
} }

View File

@ -6,6 +6,4 @@ data class User(
val password: String, val password: String,
val permissions: Set<String>) { val permissions: Set<String>) {
override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)" override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)"
@Deprecated("Use toConfig().root().unwrapped() instead", ReplaceWith("toConfig().root().unwrapped()"))
fun toMap(): Map<String, Any> = toConfig().root().unwrapped()
} }

View File

@ -9,25 +9,18 @@ import javax.net.ssl.ManagerFactoryParameters
import javax.net.ssl.X509ExtendedKeyManager import javax.net.ssl.X509ExtendedKeyManager
import javax.net.ssl.X509KeyManager import javax.net.ssl.X509KeyManager
class CertHoldingKeyManagerFactorySpiWrapper(private val factorySpi: KeyManagerFactorySpi, private val amqpConfig: AMQPConfiguration) : KeyManagerFactorySpi() { private class CertHoldingKeyManagerFactorySpiWrapper(private val keyManagerFactory: KeyManagerFactory,
private val amqpConfig: AMQPConfiguration) : KeyManagerFactorySpi() {
override fun engineInit(keyStore: KeyStore?, password: CharArray?) { override fun engineInit(keyStore: KeyStore?, password: CharArray?) {
val engineInitMethod = KeyManagerFactorySpi::class.java.getDeclaredMethod("engineInit", KeyStore::class.java, CharArray::class.java) keyManagerFactory.init(keyStore, password)
engineInitMethod.isAccessible = true
engineInitMethod.invoke(factorySpi, keyStore, password)
} }
override fun engineInit(spec: ManagerFactoryParameters?) { override fun engineInit(spec: ManagerFactoryParameters?) {
val engineInitMethod = KeyManagerFactorySpi::class.java.getDeclaredMethod("engineInit", ManagerFactoryParameters::class.java) keyManagerFactory.init(spec)
engineInitMethod.isAccessible = true
engineInitMethod.invoke(factorySpi, spec)
} }
private fun getKeyManagersImpl(): Array<KeyManager> { private fun getKeyManagersImpl(): Array<KeyManager> {
val engineGetKeyManagersMethod = KeyManagerFactorySpi::class.java.getDeclaredMethod("engineGetKeyManagers") return keyManagerFactory.keyManagers.map {
engineGetKeyManagersMethod.isAccessible = true
@Suppress("UNCHECKED_CAST")
val keyManagers = engineGetKeyManagersMethod.invoke(factorySpi) as Array<KeyManager>
return if (factorySpi is CertHoldingKeyManagerFactorySpiWrapper) keyManagers else keyManagers.map {
val aliasProvidingKeyManager = getDefaultKeyManager(it) val aliasProvidingKeyManager = getDefaultKeyManager(it)
// Use the SNIKeyManager if keystore has several entries and only for clients and non-openSSL servers. // Use the SNIKeyManager if keystore has several entries and only for clients and non-openSSL servers.
// Condition of using SNIKeyManager: if its client, or JDKSsl server. // Condition of using SNIKeyManager: if its client, or JDKSsl server.
@ -62,15 +55,11 @@ class CertHoldingKeyManagerFactorySpiWrapper(private val factorySpi: KeyManagerF
* the wrapper is not thread safe as in it will return the last used alias/cert chain and has itself no notion * the wrapper is not thread safe as in it will return the last used alias/cert chain and has itself no notion
* of belonging to a certain channel. * of belonging to a certain channel.
*/ */
class CertHoldingKeyManagerFactoryWrapper(factory: KeyManagerFactory, amqpConfig: AMQPConfiguration) : KeyManagerFactory(getFactorySpi(factory, amqpConfig), factory.provider, factory.algorithm) { class CertHoldingKeyManagerFactoryWrapper(factory: KeyManagerFactory, amqpConfig: AMQPConfiguration) : KeyManagerFactory(
companion object { CertHoldingKeyManagerFactorySpiWrapper(factory, amqpConfig),
private fun getFactorySpi(factory: KeyManagerFactory, amqpConfig: AMQPConfiguration): KeyManagerFactorySpi { factory.provider,
val spiField = KeyManagerFactory::class.java.getDeclaredField("factorySpi") factory.algorithm
spiField.isAccessible = true ) {
return CertHoldingKeyManagerFactorySpiWrapper(spiField.get(factory) as KeyManagerFactorySpi, amqpConfig)
}
}
fun getCurrentCertChain(): Array<out X509Certificate>? { fun getCurrentCertChain(): Array<out X509Certificate>? {
val keyManager = keyManagers.firstOrNull() val keyManager = keyManagers.firstOrNull()
val alias = if (keyManager is AliasProvidingKeyMangerWrapper) keyManager.lastAlias else null val alias = if (keyManager is AliasProvidingKeyMangerWrapper) keyManager.lastAlias else null

View File

@ -7,34 +7,24 @@ import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.TrustManagerFactorySpi import javax.net.ssl.TrustManagerFactorySpi
import javax.net.ssl.X509ExtendedTrustManager import javax.net.ssl.X509ExtendedTrustManager
class LoggingTrustManagerFactorySpiWrapper(private val factorySpi: TrustManagerFactorySpi) : TrustManagerFactorySpi() { class LoggingTrustManagerFactorySpiWrapper(private val trustManagerFactory: TrustManagerFactory) : TrustManagerFactorySpi() {
override fun engineGetTrustManagers(): Array<TrustManager> { override fun engineGetTrustManagers(): Array<TrustManager> {
val engineGetTrustManagersMethod = TrustManagerFactorySpi::class.java.getDeclaredMethod("engineGetTrustManagers") return trustManagerFactory.trustManagers
engineGetTrustManagersMethod.isAccessible = true .mapNotNull { (it as? X509ExtendedTrustManager)?.let(::LoggingTrustManagerWrapper) }
@Suppress("UNCHECKED_CAST") .toTypedArray()
val trustManagers = engineGetTrustManagersMethod.invoke(factorySpi) as Array<TrustManager>
return if (factorySpi is LoggingTrustManagerFactorySpiWrapper) trustManagers else trustManagers.filterIsInstance(X509ExtendedTrustManager::class.java).map { LoggingTrustManagerWrapper(it) }.toTypedArray()
} }
override fun engineInit(ks: KeyStore?) { override fun engineInit(ks: KeyStore?) {
val engineInitMethod = TrustManagerFactorySpi::class.java.getDeclaredMethod("engineInit", KeyStore::class.java) trustManagerFactory.init(ks)
engineInitMethod.isAccessible = true
engineInitMethod.invoke(factorySpi, ks)
} }
override fun engineInit(spec: ManagerFactoryParameters?) { override fun engineInit(spec: ManagerFactoryParameters?) {
val engineInitMethod = TrustManagerFactorySpi::class.java.getDeclaredMethod("engineInit", ManagerFactoryParameters::class.java) trustManagerFactory.init(spec)
engineInitMethod.isAccessible = true
engineInitMethod.invoke(factorySpi, spec)
} }
} }
class LoggingTrustManagerFactoryWrapper(factory: TrustManagerFactory) : TrustManagerFactory(getFactorySpi(factory), factory.provider, factory.algorithm) { class LoggingTrustManagerFactoryWrapper(factory: TrustManagerFactory) : TrustManagerFactory(
companion object { LoggingTrustManagerFactorySpiWrapper(factory),
private fun getFactorySpi(factory: TrustManagerFactory): TrustManagerFactorySpi { factory.provider,
val spiField = TrustManagerFactory::class.java.getDeclaredField("factorySpi") factory.algorithm
spiField.isAccessible = true )
return LoggingTrustManagerFactorySpiWrapper(spiField.get(factory) as TrustManagerFactorySpi)
}
}
}

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.rpc.client
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.serialization.internal.NotSerializableException
import net.corda.serialization.internal.amqp.CustomSerializer import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import rx.Observable import rx.Observable
@ -20,9 +21,7 @@ class RpcClientCordaFutureSerializer (factory: SerializerFactory)
try { try {
return proxy.observable.toFuture() return proxy.observable.toFuture()
} catch (e: NotSerializableException) { } catch (e: NotSerializableException) {
throw NotSerializableException("Failed to deserialize Future from proxy Observable - ${e.message}\n").apply { throw NotSerializableException("Failed to deserialize Future from proxy Observable - ${e.message}\n", e.cause)
initCause(e.cause)
}
} }
} }

View File

@ -1,13 +1,13 @@
package net.corda.nodeapi.internal.serialization.kryo package net.corda.nodeapi.internal.serialization.kryo
import net.corda.core.internal.declaredField
import net.corda.serialization.internal.ByteBufferOutputStream import net.corda.serialization.internal.ByteBufferOutputStream
import org.assertj.core.api.Assertions.catchThrowable import org.assertj.core.api.Assertions.catchThrowable
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Test import org.junit.Test
import java.io.* import java.io.InputStream
import java.io.OutputStream
import java.nio.BufferOverflowException import java.nio.BufferOverflowException
import java.util.* import java.util.Random
import java.util.zip.DeflaterOutputStream import java.util.zip.DeflaterOutputStream
import java.util.zip.InflaterInputStream import java.util.zip.InflaterInputStream
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -67,15 +67,12 @@ class KryoStreamsTest {
fun `ByteBufferOutputStream works`() { fun `ByteBufferOutputStream works`() {
val stream = ByteBufferOutputStream(3) val stream = ByteBufferOutputStream(3)
stream.write("abc".toByteArray()) stream.write("abc".toByteArray())
val getBuf = stream.declaredField<ByteArray>(ByteArrayOutputStream::class, "buf")::value
assertEquals(3, getBuf().size)
repeat(2) { repeat(2) {
assertSame<Any>(BufferOverflowException::class.java, catchThrowable { assertSame<Any>(BufferOverflowException::class.java, catchThrowable {
stream.alsoAsByteBuffer(9) { stream.alsoAsByteBuffer(9) {
it.put("0123456789".toByteArray()) it.put("0123456789".toByteArray())
} }
}.javaClass) }.javaClass)
assertEquals(3 + 9, getBuf().size)
} }
// This time make too much space: // This time make too much space:
stream.alsoAsByteBuffer(11) { stream.alsoAsByteBuffer(11) {

View File

@ -280,10 +280,6 @@ tasks.register('integrationTest', Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath classpath = sourceSets.integrationTest.runtimeClasspath
maxParallelForks = (System.env.CORDA_NODE_INT_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_NODE_INT_TESTING_FORKS".toInteger() maxParallelForks = (System.env.CORDA_NODE_INT_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_NODE_INT_TESTING_FORKS".toInteger()
jvmArgs test_add_opens
jvmArgs test_add_exports
// CertificateRevocationListNodeTests // CertificateRevocationListNodeTests
systemProperty 'net.corda.dpcrl.connect.timeout', '4000' systemProperty 'net.corda.dpcrl.connect.timeout', '4000'
} }
@ -292,9 +288,6 @@ tasks.register('slowIntegrationTest', Test) {
testClassesDirs = sourceSets.slowIntegrationTest.output.classesDirs testClassesDirs = sourceSets.slowIntegrationTest.output.classesDirs
classpath = sourceSets.slowIntegrationTest.runtimeClasspath classpath = sourceSets.slowIntegrationTest.runtimeClasspath
maxParallelForks = 1 maxParallelForks = 1
jvmArgs test_add_opens
jvmArgs test_add_exports
} }
// quasar exclusions upon agent code instrumentation at run-time // quasar exclusions upon agent code instrumentation at run-time
@ -332,9 +325,6 @@ quasar {
jar { jar {
baseName 'corda-node' baseName 'corda-node'
manifest {
attributes('Add-Opens': 'java.base/java.time java.base/java.io java.base/java.util java.base/java.net')
}
} }
tasks.named('test', Test) { tasks.named('test', Test) {

View File

@ -57,15 +57,15 @@ tasks.register('buildCordaJAR', FatCapsule) {
with jar with jar
manifest { manifest {
attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver java.base/java.net java.base/java.lang java.base/java.time') attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver')
} }
capsuleManifest { capsuleManifest {
applicationVersion = corda_release_version applicationVersion = corda_release_version
applicationId = "net.corda.node.Corda" applicationId = "net.corda.node.Corda"
// See experimental/quasar-hook/README.md for how to generate. // See experimental/quasar-hook/README.md for how to generate.
def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;org.mockito**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.corda.djvm**;djvm**;net.bytebuddy**;net.i2p**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;io.opentelemetry**)" def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;org.mockito**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;io.opentelemetry**)"
def quasarClassLoaderExclusion = "l(net.corda.djvm.**;net.corda.core.serialization.internal.**)" def quasarClassLoaderExclusion = "l(net.corda.core.serialization.internal.**)"
def quasarOptions = "m" def quasarOptions = "m"
javaAgents = quasar_classifier ? ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarOptions}${quasarExcludeExpression}${quasarClassLoaderExclusion}"] : ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"] javaAgents = quasar_classifier ? ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarOptions}${quasarExcludeExpression}${quasarClassLoaderExclusion}"] : ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"]
systemProperties['visualvm.display.name'] = 'Corda' systemProperties['visualvm.display.name'] = 'Corda'
@ -73,7 +73,6 @@ tasks.register('buildCordaJAR', FatCapsule) {
// JVM configuration: // JVM configuration:
// - Constrain to small heap sizes to ease development on low end devices. // - Constrain to small heap sizes to ease development on low end devices.
// - Switch to the G1 GC which is going to be the default in Java 9 and gives low pause times/string dedup.
// NOTE: these can be overridden in node.conf. // NOTE: these can be overridden in node.conf.
// //
// If you change these flags, please also update Driver.kt // If you change these flags, please also update Driver.kt

View File

@ -2,23 +2,32 @@
// must also be in the default package. When using Kotlin there are a whole host of exceptions // must also be in the default package. When using Kotlin there are a whole host of exceptions
// trying to construct this from Capsule, so it is written in Java. // trying to construct this from Capsule, so it is written in Java.
import com.typesafe.config.*; import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigValue;
import sun.misc.Signal; import sun.misc.Signal;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.io.InputStream;
import java.nio.file.DirectoryStream; import java.io.InputStreamReader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.*; import java.util.ArrayList;
import java.util.jar.JarInputStream; import java.util.Arrays;
import java.util.jar.Manifest; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import static com.typesafe.config.ConfigUtil.splitPath; import static com.typesafe.config.ConfigUtil.splitPath;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toMap;
public class CordaCaplet extends Capsule { public class CordaCaplet extends Capsule {
@ -47,7 +56,7 @@ public class CordaCaplet extends Capsule {
File getConfigFile(List<String> args, String baseDir) { File getConfigFile(List<String> args, String baseDir) {
String config = getOptionMultiple(args, Arrays.asList("--config-file", "-f")); String config = getOptionMultiple(args, Arrays.asList("--config-file", "-f"));
return (config == null || config.equals("")) ? new File(baseDir, "node.conf") : new File(config); return (config == null || config.isEmpty()) ? new File(baseDir, "node.conf") : new File(config);
} }
String getBaseDirectory(List<String> args) { String getBaseDirectory(List<String> args) {
@ -77,7 +86,7 @@ public class CordaCaplet extends Capsule {
} }
if (arg.toLowerCase().startsWith(lowerCaseOption)) { if (arg.toLowerCase().startsWith(lowerCaseOption)) {
if (arg.length() > option.length() && arg.substring(option.length(), option.length() + 1).equals("=")) { if (arg.length() > option.length() && arg.charAt(option.length()) == '=') {
return arg.substring(option.length() + 1); return arg.substring(option.length() + 1);
} else { } else {
return null; return null;
@ -109,26 +118,18 @@ public class CordaCaplet extends Capsule {
// For multiple instances Capsule jvm args handling works on basis that one overrides the other. // For multiple instances Capsule jvm args handling works on basis that one overrides the other.
@Override @Override
protected int launch(ProcessBuilder pb) throws IOException, InterruptedException { protected int launch(ProcessBuilder pb) throws IOException, InterruptedException {
if (isAtLeastJavaVersion11()) {
List<String> args = pb.command(); List<String> args = pb.command();
List<String> myArgs = Arrays.asList( args.addAll(1, getNodeJvmArgs());
"--add-opens=java.management/com.sun.jmx.mbeanserver=ALL-UNNAMED",
"--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
"--add-opens=java.base/java.lang.invoke=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED",
"--add-opens=java.base/java.time=ALL-UNNAMED",
"--add-opens=java.base/java.io=ALL-UNNAMED",
"--add-opens=java.base/java.net=ALL-UNNAMED",
"--add-opens=java.base/javax.net.ssl=ALL-UNNAMED",
"--add-opens=java.base/java.security.cert=ALL-UNNAMED",
"--add-opens=java.base/java.nio=ALL-UNNAMED");
args.addAll(1, myArgs);
pb.command(args); pb.command(args);
}
return super.launch(pb); return super.launch(pb);
} }
private List<String> getNodeJvmArgs() throws IOException {
try (InputStream resource = requireNonNull(getClass().getResourceAsStream("/node-jvm-args.txt"))) {
return new BufferedReader(new InputStreamReader(resource)).lines().collect(toList());
}
}
/** /**
* Overriding the Caplet classpath generation via the intended interface in Capsule. * Overriding the Caplet classpath generation via the intended interface in Capsule.
*/ */
@ -148,12 +149,12 @@ public class CordaCaplet extends Capsule {
} }
// Add additional directories of JARs to the classpath (at the end), e.g., for JDBC drivers. // Add additional directories of JARs to the classpath (at the end), e.g., for JDBC drivers.
augmentClasspath((List<Path>) cp, new File(baseDir, "drivers")); augmentClasspath((List<Path>) cp, Path.of(baseDir, "drivers"));
try { try {
List<String> jarDirs = nodeConfig.getStringList("jarDirs"); List<String> jarDirs = nodeConfig.getStringList("jarDirs");
log(LOG_VERBOSE, "Configured JAR directories = " + jarDirs); log(LOG_VERBOSE, "Configured JAR directories = " + jarDirs);
for (String jarDir : jarDirs) { for (String jarDir : jarDirs) {
augmentClasspath((List<Path>) cp, new File(jarDir)); augmentClasspath((List<Path>) cp, Path.of(jarDir));
} }
} catch (ConfigException.Missing e) { } catch (ConfigException.Missing e) {
// Ignore since it's ok to be Missing. Other errors would be unexpected. // Ignore since it's ok to be Missing. Other errors would be unexpected.
@ -183,9 +184,6 @@ public class CordaCaplet extends Capsule {
jvmArgs.add("-XX:+HeapDumpOnOutOfMemoryError"); jvmArgs.add("-XX:+HeapDumpOnOutOfMemoryError");
jvmArgs.add("-XX:+CrashOnOutOfMemoryError"); jvmArgs.add("-XX:+CrashOnOutOfMemoryError");
} }
if (isAtLeastJavaVersion11()) {
jvmArgs.add("-Dnashorn.args=--no-deprecation-warning");
}
return (T) jvmArgs; return (T) jvmArgs;
} else if (ATTR_SYSTEM_PROPERTIES == attr) { } else if (ATTR_SYSTEM_PROPERTIES == attr) {
// Add system properties, if specified, from the config. // Add system properties, if specified, from the config.
@ -193,7 +191,7 @@ public class CordaCaplet extends Capsule {
try { try {
Map<String, ?> overrideSystemProps = nodeConfig.getConfig("systemProperties").entrySet().stream() Map<String, ?> overrideSystemProps = nodeConfig.getConfig("systemProperties").entrySet().stream()
.map(Property::create) .map(Property::create)
.collect(toMap(Property::getKey, Property::getValue)); .collect(toMap(Property::key, Property::value));
log(LOG_VERBOSE, "Configured system properties = " + overrideSystemProps); log(LOG_VERBOSE, "Configured system properties = " + overrideSystemProps);
for (Map.Entry<String, ?> entry : overrideSystemProps.entrySet()) { for (Map.Entry<String, ?> entry : overrideSystemProps.entrySet()) {
systemProps.put(entry.getKey(), entry.getValue().toString()); systemProps.put(entry.getKey(), entry.getValue().toString());
@ -207,18 +205,15 @@ public class CordaCaplet extends Capsule {
} else return super.attribute(attr); } else return super.attribute(attr);
} }
private void augmentClasspath(List<Path> classpath, File dir) { private void augmentClasspath(List<Path> classpath, Path dir) {
try { if (Files.exists(dir)) {
if (dir.exists()) { try (var files = Files.list(dir)) {
// The following might return null if the directory is not there (we check this already) or if an I/O error occurs. files.forEach((file) -> addToClasspath(classpath, file));
for (File file : dir.listFiles()) { } catch (IOException e) {
addToClasspath(classpath, file); log(LOG_QUIET, e);
} }
} else { } else {
log(LOG_VERBOSE, "Directory to add in Classpath was not found " + dir.getAbsolutePath()); log(LOG_VERBOSE, "Directory to add in Classpath was not found " + dir.toAbsolutePath());
}
} catch (SecurityException | NullPointerException e) {
log(LOG_QUIET, e);
} }
} }
@ -230,14 +225,6 @@ public class CordaCaplet extends Capsule {
} }
} }
private static boolean isAtLeastJavaVersion11() {
String version = System.getProperty("java.specification.version");
if (version != null) {
return Float.parseFloat(version) >= 11f;
}
return false;
}
private Boolean checkIfCordappDirExists(File dir) { private Boolean checkIfCordappDirExists(File dir) {
try { try {
if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case. if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case.
@ -256,18 +243,18 @@ public class CordaCaplet extends Capsule {
log(LOG_VERBOSE, "Cordapps dir could not be created"); log(LOG_VERBOSE, "Cordapps dir could not be created");
} }
private void addToClasspath(List<Path> classpath, File file) { private void addToClasspath(List<Path> classpath, Path file) {
try { try {
if (file.canRead()) { if (Files.isReadable(file)) {
if (file.isFile() && isJAR(file)) { if (Files.isRegularFile(file) && isJAR(file)) {
classpath.add(file.toPath().toAbsolutePath()); classpath.add(file.toAbsolutePath());
} else if (file.isDirectory()) { // Search in nested folders as well. TODO: check for circular symlinks. } else if (Files.isDirectory(file)) { // Search in nested folders as well. TODO: check for circular symlinks.
augmentClasspath(classpath, file); augmentClasspath(classpath, file);
} }
} else { } else {
log(LOG_VERBOSE, "File or directory to add in Classpath could not be read " + file.getAbsolutePath()); log(LOG_VERBOSE, "File or directory to add in Classpath could not be read " + file.toAbsolutePath());
} }
} catch (SecurityException | NullPointerException e) { } catch (SecurityException e) {
log(LOG_QUIET, e); log(LOG_QUIET, e);
} }
} }
@ -280,30 +267,14 @@ public class CordaCaplet extends Capsule {
}); });
} }
private Boolean isJAR(File file) { private Boolean isJAR(Path file) {
return file.getName().toLowerCase().endsWith(".jar"); return file.toString().toLowerCase().endsWith(".jar");
} }
/** /**
* Helper class so that we can parse the "systemProperties" element of node.conf. * Helper class so that we can parse the "systemProperties" element of node.conf.
*/ */
private static class Property { private record Property(String key, Object value) {
private final String key;
private final Object value;
Property(String key, Object value) {
this.key = key;
this.value = value;
}
String getKey() {
return key;
}
Object getValue() {
return value;
}
static Property create(Map.Entry<String, ConfigValue> entry) { static Property create(Map.Entry<String, ConfigValue> entry) {
// String.join is preferred here over Typesafe's joinPath method, as the joinPath method would put quotes around the system // String.join is preferred here over Typesafe's joinPath method, as the joinPath method would put quotes around the system
// property key which is undesirable here. // property key which is undesirable here.

View File

@ -0,0 +1,9 @@
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED
--add-opens=java.base/java.nio=ALL-UNNAMED
--add-opens=java.base/java.security=ALL-UNNAMED
--add-opens=java.base/java.security.cert=ALL-UNNAMED
--add-opens=java.base/java.time=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.base/java.util.concurrent=ALL-UNNAMED
--add-opens=java.sql/java.sql=ALL-UNNAMED

View File

@ -1,53 +1,12 @@
package net.corda.node package net.corda.node
import net.corda.core.internal.DeclaredField import java.io.ObjectInputFilter
import net.corda.core.internal.staticField import java.io.ObjectInputFilter.Status
import net.corda.node.internal.Node
import java.lang.reflect.Method
import java.lang.reflect.Proxy
internal object SerialFilter { internal object SerialFilter {
private val filterInterface: Class<*>
private val serialClassGetter: Method
private val undecided: Any
private val rejected: Any
private val serialFilterLock: Any
private val serialFilterField: DeclaredField<Any>
init {
// ObjectInputFilter and friends are in java.io in Java 9 but sun.misc in backports:
fun getFilterInterface(packageName: String): Class<*>? {
return try {
Class.forName("$packageName.ObjectInputFilter")
} catch (e: ClassNotFoundException) {
null
}
}
// JDK 8u121 is the earliest JDK8 JVM that supports this functionality.
filterInterface = getFilterInterface("java.io")
?: getFilterInterface("sun.misc")
?: 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)
rejected = statusEnum.getField("REJECTED").get(null)
val configClass = Class.forName("${filterInterface.name}\$Config")
serialFilterLock = configClass.staticField<Any>("serialFilterLock").value
serialFilterField = configClass.staticField("serialFilter")
}
internal fun install(acceptClass: (Class<*>) -> Boolean) { internal fun install(acceptClass: (Class<*>) -> Boolean) {
val filter = Proxy.newProxyInstance(javaClass.classLoader, arrayOf(filterInterface)) { _, _, args -> ObjectInputFilter.Config.setSerialFilter { filterInfo ->
val serialClass = serialClassGetter.invoke(args[0]) as Class<*>? if (applyPredicate(acceptClass, filterInfo.serialClass())) Status.UNDECIDED else Status.REJECTED
if (applyPredicate(acceptClass, serialClass)) {
undecided
} else {
rejected
}
}
// Can't simply use the setter as in non-trampoline mode Capsule has inited the filter in premain:
synchronized(serialFilterLock) {
serialFilterField.value = filter
} }
} }

View File

@ -36,6 +36,7 @@ import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.io.IOException import java.io.IOException
import java.lang.ProcessBuilder.Redirect import java.lang.ProcessBuilder.Redirect
import java.lang.management.ManagementFactory
import java.net.ServerSocket import java.net.ServerSocket
import java.net.Socket import java.net.Socket
import java.nio.file.Files import java.nio.file.Files
@ -179,15 +180,18 @@ class ExternalVerifierHandle(private val serviceHub: ServiceHubInternal) : AutoC
val fromVerifier: DataInputStream val fromVerifier: DataInputStream
init { init {
val logsDirectory = (serviceHub.configuration.baseDirectory / "logs").createDirectories() val inheritedJvmArgs = ManagementFactory.getRuntimeMXBean().inputArguments.filter { "--add-opens" in it }
val command = listOf( val command = ArrayList<String>()
"${Path(System.getProperty("java.home"), "bin", "java")}", command += "${Path(System.getProperty("java.home"), "bin", "java")}"
command += inheritedJvmArgs
command += listOf(
"-jar", "-jar",
"$verifierJar", "$verifierJar",
"${server.localPort}", "${server.localPort}",
System.getProperty("log4j2.level")?.lowercase() ?: "info" // TODO System.getProperty("log4j2.level")?.lowercase() ?: "info"
) )
log.debug { "Verifier command: $command" } log.debug { "Verifier command: $command" }
val logsDirectory = (serviceHub.configuration.baseDirectory / "logs").createDirectories()
verifierProcess = ProcessBuilder(command) verifierProcess = ProcessBuilder(command)
.redirectOutput(Redirect.appendTo((logsDirectory / "verifier-stdout.log").toFile())) .redirectOutput(Redirect.appendTo((logsDirectory / "verifier-stdout.log").toFile()))
.redirectError(Redirect.appendTo((logsDirectory / "verifier-stderr.log").toFile())) .redirectError(Redirect.appendTo((logsDirectory / "verifier-stderr.log").toFile()))

View File

@ -78,9 +78,6 @@ dependencies {
task integrationTest(type: Test, dependsOn: []) { task integrationTest(type: Test, dependsOn: []) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath classpath = sourceSets.integrationTest.runtimeClasspath
jvmArgs test_add_opens
jvmArgs test_add_exports
} }
def nodeTask = tasks.getByPath(':node:capsule:assemble') def nodeTask = tasks.getByPath(':node:capsule:assemble')
@ -147,9 +144,6 @@ task runSender(type: JavaExec, dependsOn: jar) {
classpath = sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
main = 'net.corda.attachmentdemo.AttachmentDemoKt' main = 'net.corda.attachmentdemo.AttachmentDemoKt'
jvmArgs test_add_opens
jvmArgs test_add_exports
args '--role' args '--role'
args 'SENDER' args 'SENDER'
} }
@ -158,9 +152,6 @@ task runRecipient(type: JavaExec, dependsOn: jar) {
classpath = sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
main = 'net.corda.attachmentdemo.AttachmentDemoKt' main = 'net.corda.attachmentdemo.AttachmentDemoKt'
jvmArgs test_add_opens
jvmArgs test_add_exports
args '--role' args '--role'
args 'RECIPIENT' args 'RECIPIENT'
} }

View File

@ -5,28 +5,23 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_BANK_B_NAME import net.corda.testing.core.DUMMY_BANK_B_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.DummyClusterSpec
import net.corda.testing.node.internal.findCordapp import net.corda.testing.node.internal.findCordapp
import org.junit.Test import org.junit.Test
import java.util.concurrent.CompletableFuture.supplyAsync import java.util.concurrent.CompletableFuture.supplyAsync
class AttachmentDemoTest { class AttachmentDemoTest {
// run with a 10,000,000 bytes in-memory zip file. In practice, a slightly bigger file will be used (~10,002,000 bytes).
@Test(timeout=300_000) @Test(timeout=300_000)
fun `attachment demo using a 10MB zip file`() { fun `attachment demo using a 10MB zip file`() {
val numOfExpectedBytes = 10_000_000 val numOfExpectedBytes = 10_000_000
driver(DriverParameters( driver(DriverParameters(
portAllocation = incrementalPortAllocation(), portAllocation = incrementalPortAllocation(),
startNodesInProcess = true, startNodesInProcess = true,
cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows")), cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows"))
notarySpecs = listOf(NotarySpec(name = DUMMY_NOTARY_NAME, cluster = DummyClusterSpec(clusterSize = 1)))) )) {
) {
val demoUser = listOf(User("demo", "demo", setOf(all()))) val demoUser = listOf(User("demo", "demo", setOf(all())))
val (nodeA, nodeB) = listOf( val (nodeA, nodeB) = listOf(
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = demoUser, maximumHeapSize = "1g"), startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = demoUser, maximumHeapSize = "1g"),

View File

@ -7,7 +7,7 @@ import net.corda.client.rpc.CordaRPCClient
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.Emoji import net.corda.core.internal.Emoji
import net.corda.core.internal.InputStreamAndHash import net.corda.core.internal.hash
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startTrackedFlow import net.corda.core.messaging.startTrackedFlow
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
@ -16,9 +16,14 @@ import java.io.InputStream
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
import java.util.jar.JarInputStream import java.util.jar.JarInputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.servlet.http.HttpServletResponse.SC_OK import javax.servlet.http.HttpServletResponse.SC_OK
import javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION import javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION
import javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM import javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM
import kotlin.io.path.createTempFile
import kotlin.io.path.inputStream
import kotlin.io.path.outputStream
import kotlin.system.exitProcess import kotlin.system.exitProcess
internal enum class Role { internal enum class Role {
@ -57,11 +62,16 @@ fun main(args: Array<String>) {
} }
} }
/** An in memory test zip attachment of at least numOfClearBytes size, will be used. */ /** A temp zip file attachment of at least numOfClearBytes size, will be used. */
// DOCSTART 2 // DOCSTART 2
fun sender(rpc: CordaRPCOps, numOfClearBytes: Int = 1024) { // default size 1K. fun sender(rpc: CordaRPCOps, numOfClearBytes: Int = 1024) { // default size 1K.
val (inputStream, hash) = InputStreamAndHash.createInMemoryTestZip(numOfClearBytes, 0) val attachmentFile = createTempFile("attachment-demo").apply { toFile().deleteOnExit() }
sender(rpc, inputStream, hash) ZipOutputStream(attachmentFile.outputStream()).use { zip ->
zip.putNextEntry(ZipEntry("test"))
zip.write(ByteArray(numOfClearBytes))
zip.closeEntry()
}
sender(rpc, attachmentFile.inputStream(), attachmentFile.hash)
} }
private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.SHA256) { private fun sender(rpc: CordaRPCOps, inputStream: InputStream, hash: SecureHash.SHA256) {

View File

@ -117,36 +117,24 @@ tasks.register('runRPCCashIssue', JavaExec) {
classpath = sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
mainClass = 'net.corda.bank.IssueCash' mainClass = 'net.corda.bank.IssueCash'
jvmArgs test_add_opens
jvmArgs test_add_exports
args '--role' args '--role'
args 'ISSUE_CASH_RPC' args 'ISSUE_CASH_RPC'
args '--quantity' args '--quantity'
args 20000 args 20000
args '--currency' args '--currency'
args 'USD' args 'USD'
jvmArgs test_add_opens
jvmArgs test_add_exports
} }
tasks.register('runWebCashIssue', JavaExec) { tasks.register('runWebCashIssue', JavaExec) {
classpath = sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
mainClass = 'net.corda.bank.IssueCash' mainClass = 'net.corda.bank.IssueCash'
jvmArgs test_add_opens
jvmArgs test_add_exports
args '--role' args '--role'
args 'ISSUE_CASH_WEB' args 'ISSUE_CASH_WEB'
args '--quantity' args '--quantity'
args 30000 args 30000
args '--currency' args '--currency'
args 'GBP' args 'GBP'
jvmArgs test_add_opens
jvmArgs test_add_exports
} }
jar { jar {

View File

@ -170,9 +170,6 @@ task deployNodes(type: net.corda.plugins.Cordform) {
task integrationTest(type: Test, dependsOn: []) { task integrationTest(type: Test, dependsOn: []) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath classpath = sourceSets.integrationTest.runtimeClasspath
jvmArgs test_add_opens
jvmArgs test_add_exports
} }
cordapp { cordapp {

View File

@ -73,9 +73,6 @@ dependencies {
task integrationTest(type: Test, dependsOn: []) { task integrationTest(type: Test, dependsOn: []) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath classpath = sourceSets.integrationTest.runtimeClasspath
jvmArgs test_add_opens
jvmArgs test_add_exports
} }
configurations.cordaCordapp.canBeResolved = true configurations.cordaCordapp.canBeResolved = true
@ -149,16 +146,6 @@ task deployNodes(type: net.corda.plugins.Cordform) {
jar { jar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy = DuplicatesStrategy.EXCLUDE
manifest {
attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver ' +
'java.base/java.time java.base/java.io ' +
'java.base/java.util java.base/java.net ' +
'java.base/java.nio java.base/java.lang.invoke ' +
'java.base/java.security.cert java.base/java.security ' +
'java.base/javax.net.ssl java.base/java.util.concurrent ' +
'java.sql/java.sql'
)
}
} }
idea { idea {

View File

@ -54,7 +54,6 @@ import org.apache.qpid.proton.codec.DecoderImpl
import org.apache.qpid.proton.codec.EncoderImpl import org.apache.qpid.proton.codec.EncoderImpl
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.assertj.core.api.Assertions.catchThrowable import org.assertj.core.api.Assertions.catchThrowable
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
@ -73,6 +72,7 @@ import org.junit.runners.Parameterized.Parameters
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.whenever import org.mockito.kotlin.whenever
import java.io.IOException import java.io.IOException
import java.io.InputStream
import java.io.NotSerializableException import java.io.NotSerializableException
import java.math.BigDecimal import java.math.BigDecimal
import java.math.BigInteger import java.math.BigInteger
@ -148,13 +148,13 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
data class Foo(val bar: String, val pub: Int) data class Foo(val bar: String, val pub: Int)
data class testFloat(val f: Float) data class TestFloat(val f: Float)
data class testDouble(val d: Double) data class TestDouble(val d: Double)
data class testShort(val s: Short) data class TestShort(val s: Short)
data class testBoolean(val b: Boolean) data class TestBoolean(val b: Boolean)
interface FooInterface { interface FooInterface {
val pub: Int val pub: Int
@ -340,25 +340,25 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test float`() { fun `test float`() {
val obj = testFloat(10.0F) val obj = TestFloat(10.0F)
serdes(obj) serdes(obj)
} }
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test double`() { fun `test double`() {
val obj = testDouble(10.0) val obj = TestDouble(10.0)
serdes(obj) serdes(obj)
} }
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test short`() { fun `test short`() {
val obj = testShort(1) val obj = TestShort(1)
serdes(obj) serdes(obj)
} }
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test bool`() { fun `test bool`() {
val obj = testBoolean(true) val obj = TestBoolean(true)
serdes(obj) serdes(obj)
} }
@ -377,7 +377,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test dislike of HashMap`() { fun `test dislike of HashMap`() {
val obj = WrapHashMap(HashMap()) val obj = WrapHashMap(HashMap())
assertThatIllegalArgumentException().isThrownBy { assertThatExceptionOfType(NotSerializableException::class.java).isThrownBy {
serdes(obj) serdes(obj)
} }
} }
@ -1303,7 +1303,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
) )
factory2.register(net.corda.serialization.internal.amqp.custom.InputStreamSerializer) factory2.register(net.corda.serialization.internal.amqp.custom.InputStreamSerializer)
val bytes = ByteArray(10) { it.toByte() } val bytes = ByteArray(10) { it.toByte() }
val obj = bytes.inputStream() val obj: InputStream = bytes.inputStream()
val obj2 = serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false) val obj2 = serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false)
val obj3 = bytes.inputStream() // Can't use original since the stream pointer has moved. val obj3 = bytes.inputStream() // Can't use original since the stream pointer has moved.
assertEquals(obj3.available(), obj2.available()) assertEquals(obj3.available(), obj2.available())

View File

@ -57,10 +57,6 @@ artifacts {
jar { jar {
archiveBaseName = 'corda-serialization' archiveBaseName = 'corda-serialization'
archiveClassifier = '' archiveClassifier = ''
manifest {
attributes('Add-Opens': 'java.base/java.time java.base/java.io')
}
} }
publishing { publishing {

View File

@ -43,14 +43,8 @@ class ByteBufferInputStream(val byteBuffer: ByteBuffer) : InputStream() {
} }
class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) { class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) {
companion object {
private val ensureCapacity = ByteArrayOutputStream::class.java.getDeclaredMethod("ensureCapacity", Int::class.java).apply {
isAccessible = true
}
}
fun <T> alsoAsByteBuffer(remaining: Int, task: (ByteBuffer) -> T): T { fun <T> alsoAsByteBuffer(remaining: Int, task: (ByteBuffer) -> T): T {
ensureCapacity.invoke(this, count + remaining) ensureCapacity(count + remaining)
val buffer = ByteBuffer.wrap(buf, count, remaining) val buffer = ByteBuffer.wrap(buf, count, remaining)
val result = task(buffer) val result = task(buffer)
count = buffer.position() count = buffer.position()
@ -60,4 +54,10 @@ class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) {
fun copyTo(stream: OutputStream) { fun copyTo(stream: OutputStream) {
stream.write(buf, 0, count) stream.write(buf, 0, count)
} }
private fun ensureCapacity(minCapacity: Int) {
if (minCapacity > buf.size) {
buf = buf.copyOf(minCapacity)
}
}
} }

View File

@ -0,0 +1,8 @@
package net.corda.serialization.internal
import java.io.NotSerializableException
@Suppress("FunctionNaming")
fun NotSerializableException(message: String?, cause: Throwable?): NotSerializableException {
return NotSerializableException(message).apply { initCause(cause) }
}

View File

@ -1,19 +1,11 @@
package net.corda.serialization.internal.amqp package net.corda.serialization.internal.amqp
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import net.corda.serialization.internal.NotSerializableException
import org.slf4j.Logger import org.slf4j.Logger
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.Type import java.lang.reflect.Type
/**
* Not a public property so will have to use reflection
*/
private fun Throwable.setMessage(newMsg: String) {
val detailMessageField = Throwable::class.java.getDeclaredField("detailMessage")
detailMessageField.isAccessible = true
detailMessageField.set(this, newMsg)
}
/** /**
* Utility function which helps tracking the path in the object graph when exceptions are thrown. * Utility function which helps tracking the path in the object graph when exceptions are thrown.
* Since there might be a chain of nested calls it is useful to record which part of the graph caused an issue. * Since there might be a chain of nested calls it is useful to record which part of the graph caused an issue.
@ -22,15 +14,13 @@ private fun Throwable.setMessage(newMsg: String) {
internal inline fun <T> ifThrowsAppend(strToAppendFn: () -> String, block: () -> T): T { internal inline fun <T> ifThrowsAppend(strToAppendFn: () -> String, block: () -> T): T {
try { try {
return block() return block()
} catch (th: Throwable) { } catch (e: AMQPNotSerializableException) {
when (th) { e.classHierarchy += strToAppendFn()
is AMQPNotSerializableException -> th.classHierarchy.add(strToAppendFn()) throw e
// Do not overwrite the message of these exceptions as it may be used. } catch (e: Exception) {
is ClassNotFoundException -> {} // Avoid creating heavily nested NotSerializableExceptions
is NoClassDefFoundError -> {} val cause = if (e.message?.contains(" -> ") == true) { e.cause ?: e } else { e }
else -> th.setMessage("${strToAppendFn()} -> ${th.message}") throw NotSerializableException("${strToAppendFn()} -> ${e.message}", cause)
}
throw th
} }
} }
@ -77,8 +67,3 @@ open class AMQPNotSerializableException(
logger.debug("", cause) logger.debug("", cause)
} }
} }
class SyntheticParameterException(type: Type) : AMQPNotSerializableException(
type,
"Type '${type.typeName} has synthetic "
+ "fields and is likely a nested inner class. This is not support by the Corda AMQP serialization framework")

View File

@ -11,6 +11,7 @@ import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.serialization.internal.ByteBufferInputStream import net.corda.serialization.internal.ByteBufferInputStream
import net.corda.serialization.internal.CordaSerializationEncoding import net.corda.serialization.internal.CordaSerializationEncoding
import net.corda.serialization.internal.NotSerializableException
import net.corda.serialization.internal.NullEncodingWhitelist import net.corda.serialization.internal.NullEncodingWhitelist
import net.corda.serialization.internal.SectionId import net.corda.serialization.internal.SectionId
import net.corda.serialization.internal.encodingNotPermittedFormat import net.corda.serialization.internal.encodingNotPermittedFormat
@ -120,11 +121,11 @@ class DeserializationInput constructor(
return generator() return generator()
} catch (amqp : AMQPNotSerializableException) { } catch (amqp : AMQPNotSerializableException) {
amqp.log("Deserialize", logger) amqp.log("Deserialize", logger)
throw NotSerializableException(amqp.mitigation) throw NotSerializableException(amqp.mitigation, amqp)
} catch (nse: NotSerializableException) { } catch (nse: NotSerializableException) {
throw nse throw nse
} catch (e: Exception) { } catch (e: Exception) {
throw NotSerializableException("Internal deserialization failure: ${e.javaClass.name}: ${e.message}").apply { initCause(e) } throw NotSerializableException("Internal deserialization failure: ${e.javaClass.name}: ${e.message}", e)
} finally { } finally {
objectHistory.clear() objectHistory.clear()
} }

View File

@ -1,5 +1,6 @@
package net.corda.serialization.internal.amqp package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.NotSerializableException
import net.corda.serialization.internal.model.LocalConstructorInformation import net.corda.serialization.internal.model.LocalConstructorInformation
import net.corda.serialization.internal.model.LocalPropertyInformation import net.corda.serialization.internal.model.LocalPropertyInformation
import net.corda.serialization.internal.model.LocalTypeInformation import net.corda.serialization.internal.model.LocalTypeInformation
@ -32,17 +33,12 @@ private class ConstructorCaller(private val javaConstructor: Constructor<Any>) :
try { try {
javaConstructor.newInstance(*parameters) javaConstructor.newInstance(*parameters)
} catch (e: InvocationTargetException) { } catch (e: InvocationTargetException) {
@Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9)
throw NotSerializableException( throw NotSerializableException(
"Constructor for ${javaConstructor.declaringClass} (isAccessible=${javaConstructor.isAccessible}) " + "Constructor for ${javaConstructor.declaringClass.name} failed when called with parameters ${parameters.asList()}: ${e.cause?.message}",
"failed when called with parameters ${parameters.toList()}: ${e.cause!!.message}" e.cause
) )
} catch (e: IllegalAccessException) { } catch (e: IllegalAccessException) {
@Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9) throw NotSerializableException("Constructor for ${javaConstructor.declaringClass.name} not accessible: ${e.message}")
throw NotSerializableException(
"Constructor for ${javaConstructor.declaringClass} (isAccessible=${javaConstructor.isAccessible}) " +
"not accessible: ${e.message}"
)
} }
} }

View File

@ -2,9 +2,9 @@ package net.corda.serialization.internal.amqp.custom
import net.corda.core.serialization.DESERIALIZATION_CACHE_PROPERTY import net.corda.core.serialization.DESERIALIZATION_CACHE_PROPERTY
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.NotSerializableException
import net.corda.serialization.internal.amqp.CustomSerializer import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import java.io.NotSerializableException
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.CertificateException import java.security.cert.CertificateException
import java.security.cert.CertificateFactory import java.security.cert.CertificateFactory
@ -23,9 +23,7 @@ class CertPathSerializer(
val cf = CertificateFactory.getInstance(proxy.type) val cf = CertificateFactory.getInstance(proxy.type)
return cf.generateCertPath(proxy.encoded.inputStream()) return cf.generateCertPath(proxy.encoded.inputStream())
} catch (ce: CertificateException) { } catch (ce: CertificateException) {
val nse = NotSerializableException("java.security.cert.CertPath: $type") throw NotSerializableException("java.security.cert.CertPath: $type", ce)
nse.initCause(ce)
throw nse
} }
} }

View File

@ -11,12 +11,7 @@ import java.lang.reflect.Type
/** /**
* A serializer that writes out the content of an input stream as bytes and deserializes into a [ByteArrayInputStream]. * A serializer that writes out the content of an input stream as bytes and deserializes into a [ByteArrayInputStream].
*/ */
object InputStreamSerializer object InputStreamSerializer : CustomSerializer.Implements<InputStream>(InputStream::class.java) {
: CustomSerializer.Implements<InputStream>(
InputStream::class.java
) {
override val revealSubclassesInSchema: Boolean = true
override val schemaForDocumentation = Schema( override val schemaForDocumentation = Schema(
listOf( listOf(
RestrictedType( RestrictedType(

View File

@ -2,7 +2,6 @@ package net.corda.serialization.internal.amqp.custom
import net.corda.serialization.internal.amqp.CustomSerializer import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import java.lang.reflect.Method
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.ZoneOffset import java.time.ZoneOffset
@ -18,21 +17,6 @@ class ZonedDateTimeSerializer(
ZonedDateTimeProxy::class.java, ZonedDateTimeProxy::class.java,
factory factory
) { ) {
// Java deserialization of `ZonedDateTime` uses a private method. We will resolve this somewhat statically
// so that any change to internals of `ZonedDateTime` is detected early.
companion object {
val ofLenient: Method = ZonedDateTime::class.java.getDeclaredMethod(
"ofLenient",
LocalDateTime::class.java,
ZoneOffset::class.java,
ZoneId::class.java
)
init {
ofLenient.isAccessible = true
}
}
override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf( override val additionalSerializers: Iterable<CustomSerializer<out Any>> = listOf(
LocalDateTimeSerializer(factory), LocalDateTimeSerializer(factory),
ZoneIdSerializer(factory) ZoneIdSerializer(factory)
@ -40,12 +24,7 @@ class ZonedDateTimeSerializer(
override fun toProxy(obj: ZonedDateTime): ZonedDateTimeProxy = ZonedDateTimeProxy(obj.toLocalDateTime(), obj.offset, obj.zone) override fun toProxy(obj: ZonedDateTime): ZonedDateTimeProxy = ZonedDateTimeProxy(obj.toLocalDateTime(), obj.offset, obj.zone)
override fun fromProxy(proxy: ZonedDateTimeProxy): ZonedDateTime = ofLenient.invoke( override fun fromProxy(proxy: ZonedDateTimeProxy): ZonedDateTime = ZonedDateTime.ofLocal(proxy.dateTime, proxy.zone, proxy.offset)
null,
proxy.dateTime,
proxy.offset,
proxy.zone
) as ZonedDateTime
data class ZonedDateTimeProxy(val dateTime: LocalDateTime, val offset: ZoneOffset, val zone: ZoneId) data class ZonedDateTimeProxy(val dateTime: LocalDateTime, val offset: ZoneOffset, val zone: ZoneId)
} }

View File

@ -2,18 +2,25 @@ package net.corda.serialization.internal.model
import net.corda.core.internal.isAbstractClass import net.corda.core.internal.isAbstractClass
import net.corda.core.internal.isConcreteClass import net.corda.core.internal.isConcreteClass
import net.corda.core.internal.isJdkClass
import net.corda.core.internal.kotlinObjectInstance import net.corda.core.internal.kotlinObjectInstance
import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.utilities.loggerFor
import net.corda.serialization.internal.NotSerializableDetailedException import net.corda.serialization.internal.NotSerializableDetailedException
import net.corda.serialization.internal.amqp.* import net.corda.serialization.internal.amqp.PropertyDescriptor
import net.corda.serialization.internal.amqp.TransformsAnnotationProcessor
import net.corda.serialization.internal.amqp.asClass
import net.corda.serialization.internal.amqp.calculatedPropertyDescriptors
import net.corda.serialization.internal.amqp.componentType
import net.corda.serialization.internal.amqp.propertyDescriptors
import net.corda.serialization.internal.model.LocalTypeInformation.ACollection
import net.corda.serialization.internal.model.LocalTypeInformation.AMap
import net.corda.serialization.internal.model.LocalTypeInformation.Abstract import net.corda.serialization.internal.model.LocalTypeInformation.Abstract
import net.corda.serialization.internal.model.LocalTypeInformation.AnArray import net.corda.serialization.internal.model.LocalTypeInformation.AnArray
import net.corda.serialization.internal.model.LocalTypeInformation.AnEnum import net.corda.serialization.internal.model.LocalTypeInformation.AnEnum
import net.corda.serialization.internal.model.LocalTypeInformation.AnInterface import net.corda.serialization.internal.model.LocalTypeInformation.AnInterface
import net.corda.serialization.internal.model.LocalTypeInformation.Atomic import net.corda.serialization.internal.model.LocalTypeInformation.Atomic
import net.corda.serialization.internal.model.LocalTypeInformation.ACollection
import net.corda.serialization.internal.model.LocalTypeInformation.AMap
import net.corda.serialization.internal.model.LocalTypeInformation.Composable import net.corda.serialization.internal.model.LocalTypeInformation.Composable
import net.corda.serialization.internal.model.LocalTypeInformation.Cycle import net.corda.serialization.internal.model.LocalTypeInformation.Cycle
import net.corda.serialization.internal.model.LocalTypeInformation.NonComposable import net.corda.serialization.internal.model.LocalTypeInformation.NonComposable
@ -22,11 +29,12 @@ import net.corda.serialization.internal.model.LocalTypeInformation.Singleton
import net.corda.serialization.internal.model.LocalTypeInformation.Top import net.corda.serialization.internal.model.LocalTypeInformation.Top
import net.corda.serialization.internal.model.LocalTypeInformation.Unknown import net.corda.serialization.internal.model.LocalTypeInformation.Unknown
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.InaccessibleObjectException
import java.lang.reflect.Method import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type import java.lang.reflect.Type
import kotlin.collections.LinkedHashMap
import kotlin.reflect.KFunction import kotlin.reflect.KFunction
import kotlin.reflect.KVisibility
import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor import kotlin.reflect.full.primaryConstructor
@ -298,7 +306,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
private fun propertiesSatisfyConstructor(constructorInformation: LocalConstructorInformation, properties: Map<PropertyName, LocalPropertyInformation>): Boolean { private fun propertiesSatisfyConstructor(constructorInformation: LocalConstructorInformation, properties: Map<PropertyName, LocalPropertyInformation>): Boolean {
if (!constructorInformation.hasParameters) return true if (!constructorInformation.hasParameters) return true
val indicesAddressedByProperties = properties.values.asSequence().mapNotNullTo(LinkedHashSet()) { val indicesAddressedByProperties = properties.values.mapNotNullTo(LinkedHashSet()) {
when (it) { when (it) {
is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex
is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex
@ -317,7 +325,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
): List<LocalConstructorParameterInformation> { ): List<LocalConstructorParameterInformation> {
if (!constructorInformation.hasParameters) return emptyList() if (!constructorInformation.hasParameters) return emptyList()
val indicesAddressedByProperties = properties.values.asSequence().mapNotNullTo(LinkedHashSet()) { val indicesAddressedByProperties = properties.values.mapNotNullTo(LinkedHashSet()) {
when (it) { when (it) {
is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex
is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex
@ -520,8 +528,7 @@ private fun constructorForDeserialization(type: Type): KFunction<Any>? {
val defaultCtor = kotlinCtors.firstOrNull { it.parameters.isEmpty() } val defaultCtor = kotlinCtors.firstOrNull { it.parameters.isEmpty() }
val nonDefaultCtors = kotlinCtors.filter { it != defaultCtor } val nonDefaultCtors = kotlinCtors.filter { it != defaultCtor }
val preferredCandidate = clazz.kotlin.primaryConstructor ?: val preferredCandidate = clazz.kotlin.primaryConstructor ?: when (nonDefaultCtors.size) {
when(nonDefaultCtors.size) {
1 -> nonDefaultCtors.first() 1 -> nonDefaultCtors.first()
0 -> defaultCtor 0 -> defaultCtor
else -> null else -> null
@ -531,6 +538,19 @@ private fun constructorForDeserialization(type: Type): KFunction<Any>? {
preferredCandidate.apply { isAccessible = true } preferredCandidate.apply { isAccessible = true }
} catch (e: SecurityException) { } catch (e: SecurityException) {
null null
} catch (e: InaccessibleObjectException) {
if (!clazz.isJdkClass || preferredCandidate.visibility == KVisibility.PUBLIC) {
// We shouldn't be using private JDK constructors. For non-JDK classes, then re-throw as the client may need to open up that
// module to us. Also throw if we can't get access to a public JDK constructor, which can probably happen if the class is not
// exported (i.e. internal API).
throw e
}
with(loggerFor<LocalTypeInformationBuilder>()) {
if (isTraceEnabled) {
trace("Ignoring private JDK constructor", e)
}
}
null
} }
} }

View File

@ -3,7 +3,7 @@ package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThatThrownBy
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.Test import org.junit.Test
import java.io.NotSerializableException import java.io.NotSerializableException
@ -86,8 +86,9 @@ class DeserializeMapTests {
val c = C(v) val c = C(v)
// expected to throw // expected to throw
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) } assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Unable to serialise deprecated type class java.util.Dictionary.") .isInstanceOf(NotSerializableException::class.java)
.hasMessageContaining("Unable to serialise deprecated type class java.util.Dictionary.")
} }
@Test(timeout=300_000) @Test(timeout=300_000)
@ -100,7 +101,7 @@ class DeserializeMapTests {
val c = C(v) val c = C(v)
// expected to throw // expected to throw
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) } assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Unable to serialise deprecated type class java.util.Hashtable. Suggested fix: prefer java.util.map implementations") .isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Unable to serialise deprecated type class java.util.Hashtable. Suggested fix: prefer java.util.map implementations")
} }
@ -111,7 +112,7 @@ class DeserializeMapTests {
val c = C(HashMap(mapOf("A" to 1, "B" to 2))) val c = C(HashMap(mapOf("A" to 1, "B" to 2)))
// expect this to throw // expect this to throw
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) } assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.") .isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
} }
@ -121,7 +122,7 @@ class DeserializeMapTests {
val c = C(WeakHashMap(mapOf("A" to 1, "B" to 2))) val c = C(WeakHashMap(mapOf("A" to 1, "B" to 2)))
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) } assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Weak references with map types not supported. Suggested fix: use java.util.LinkedHashMap instead.") .isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Weak references with map types not supported. Suggested fix: use java.util.LinkedHashMap instead.")
} }

View File

@ -55,7 +55,6 @@ include 'client:jfx'
include 'client:mock' include 'client:mock'
include 'client:rpc' include 'client:rpc'
include 'docker' include 'docker'
include 'testing:client-rpc'
include 'testing:testserver' include 'testing:testserver'
include 'testing:testserver:testcapsule:' include 'testing:testserver:testcapsule:'
include 'experimental' include 'experimental'

View File

@ -1,73 +0,0 @@
apply plugin: 'org.jetbrains.kotlin.jvm'
configurations {
smokeTestImplementation.extendsFrom compile
smokeTestRuntimeOnly.extendsFrom runtimeOnly
}
sourceSets {
smokeTest {
kotlin {
// We must NOT have any Node code on the classpath, so do NOT
// include the test or integrationTest dependencies here.
compileClasspath += main.output
runtimeClasspath += main.output
srcDir file('src/smoke-test/kotlin')
}
java {
compileClasspath += main.output
runtimeClasspath += main.output
srcDir file('src/smoke-test/java')
}
}
}
processSmokeTestResources {
// Bring in the fully built corda.jar for use by NodeFactory in the smoke tests
from(project(":node:capsule").tasks['buildCordaJAR']) {
rename 'corda-(.*)', 'corda.jar'
}
from(project(':finance:workflows').tasks['jar']) {
rename '.*finance-workflows-.*', 'cordapp-finance-workflows.jar'
}
from(project(':finance:contracts').tasks['jar']) {
rename '.*finance-contracts-.*', 'cordapp-finance-contracts.jar'
}
from(project(':testing:cordapps:sleeping').tasks['jar']) {
rename 'testing-sleeping-cordapp-*', 'cordapp-sleeping.jar'
}
}
dependencies {
// Smoke tests do NOT have any Node code on the classpath!
smokeTestImplementation project(':core')
smokeTestImplementation project(':client:rpc')
smokeTestImplementation project(':node-api')
smokeTestImplementation project(':smoke-test-utils')
smokeTestImplementation project(':finance:contracts')
smokeTestImplementation project(':finance:workflows')
smokeTestImplementation project(':testing:cordapps:sleeping')
smokeTestImplementation "io.reactivex:rxjava:$rxjava_version"
smokeTestImplementation "commons-io:commons-io:$commons_io_version"
smokeTestImplementation "org.hamcrest:hamcrest-library:2.1"
smokeTestImplementation "com.google.guava:guava-testlib:$guava_version"
smokeTestImplementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
smokeTestImplementation "org.apache.logging.log4j:log4j-core:$log4j_version"
smokeTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
smokeTestImplementation "org.assertj:assertj-core:${assertj_version}"
smokeTestImplementation "junit:junit:$junit_version"
smokeTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
smokeTestRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
// JDK11: required by Quasar at run-time
smokeTestRuntimeOnly "com.esotericsoftware:kryo:$kryo_version"
}
task smokeTest(type: Test) {
testClassesDirs = sourceSets.smokeTest.output.classesDirs
classpath = sourceSets.smokeTest.runtimeClasspath
jvmArgs test_add_opens
jvmArgs test_add_exports
}

View File

@ -102,17 +102,20 @@ dependencies {
compileJava { compileJava {
doFirst { doFirst {
options.compilerArgs = [ options.compilerArgs = [
'--add-exports', 'java.base/sun.nio.ch=ALL-UNNAMED' '--add-modules', 'jdk.incubator.foreign'
] ]
} }
} }
processResources {
from(project(":node:capsule").files("src/main/resources/node-jvm-args.txt")) {
into("net/corda/testing/node/internal")
}
}
task integrationTest(type: Test) { task integrationTest(type: Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath classpath = sourceSets.integrationTest.runtimeClasspath
jvmArgs test_add_opens
jvmArgs test_add_exports
} }
jar { jar {

View File

@ -1,10 +1,14 @@
package net.corda.testing.node package net.corda.testing.node
import net.corda.core.internal.deleteRecursively
import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess
import net.corda.testing.node.internal.nodeJvmArgs
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import kotlin.io.path.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.div import kotlin.io.path.div
import kotlin.test.assertEquals
class MockNetworkIntegrationTests { class MockNetworkIntegrationTests {
companion object { companion object {
@ -22,16 +26,16 @@ class MockNetworkIntegrationTests {
fun `does not leak non-daemon threads`() { fun `does not leak non-daemon threads`() {
val quasar = projectRootDir / "lib" / "quasar.jar" val quasar = projectRootDir / "lib" / "quasar.jar"
val quasarOptions = "m" val quasarOptions = "m"
val moduleOpens = listOf(
"--add-opens", "java.base/java.time=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED",
"--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.net=ALL-UNNAMED",
"--add-opens", "java.base/java.nio=ALL-UNNAMED", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED",
"--add-opens", "java.base/java.security.cert=ALL-UNNAMED", "--add-opens", "java.base/javax.net.ssl=ALL-UNNAMED",
"--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", "--add-opens", "java.sql/java.sql=ALL-UNNAMED",
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
)
assertEquals(0, startJavaProcess<MockNetworkIntegrationTests>(emptyList(), val workingDirectory = Path("build", "MockNetworkIntegrationTests").apply {
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + moduleOpens).waitFor()) deleteRecursively()
createDirectories()
}
val process = startJavaProcess<MockNetworkIntegrationTests>(
emptyList(),
workingDirectory = workingDirectory,
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs
)
assertThat(process.waitFor()).isZero()
} }
} }

View File

@ -9,7 +9,6 @@ import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.stream.Collectors import java.util.stream.Collectors
@RunWith(value = Parameterized::class) @RunWith(value = Parameterized::class)
class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputRegexPattern: String) { class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputRegexPattern: String) {
@ -31,10 +30,7 @@ class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputR
@Test(timeout=300_000) @Test(timeout=300_000)
fun `Run CordaCliWrapper sample app with arguments and check error output matches regExp`() { fun `Run CordaCliWrapper sample app with arguments and check error output matches regExp`() {
val process = ProcessUtilities.startJavaProcess( val process = ProcessUtilities.startJavaProcess(className = className, arguments = arguments)
className = className,
arguments = arguments,
inheritIO = false)
process.waitFor() process.waitFor()

View File

@ -1,10 +1,13 @@
package net.corda.testing.node.internal package net.corda.testing.node.internal
import net.corda.core.internal.deleteRecursively
import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import kotlin.io.path.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.div import kotlin.io.path.div
import kotlin.test.assertEquals
class InternalMockNetworkIntegrationTests { class InternalMockNetworkIntegrationTests {
companion object { companion object {
@ -22,17 +25,16 @@ class InternalMockNetworkIntegrationTests {
fun `does not leak non-daemon threads`() { fun `does not leak non-daemon threads`() {
val quasar = projectRootDir / "lib" / "quasar.jar" val quasar = projectRootDir / "lib" / "quasar.jar"
val quasarOptions = "m" val quasarOptions = "m"
val moduleOpens = listOf(
"--add-opens", "java.base/java.time=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED",
"--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.net=ALL-UNNAMED",
"--add-opens", "java.base/java.nio=ALL-UNNAMED", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED",
"--add-opens", "java.base/java.security.cert=ALL-UNNAMED", "--add-opens", "java.base/javax.net.ssl=ALL-UNNAMED",
"--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", "--add-opens", "java.sql/java.sql=ALL-UNNAMED",
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
)
assertEquals(0, startJavaProcess<InternalMockNetworkIntegrationTests>(emptyList(), val workingDirectory = Path("build", "InternalMockNetworkIntegrationTests").apply {
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + moduleOpens deleteRecursively()
).waitFor()) createDirectories()
}
val process = startJavaProcess<InternalMockNetworkIntegrationTests>(
emptyList(),
workingDirectory = workingDirectory,
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs
)
assertThat(process.waitFor()).isZero()
} }
} }

View File

@ -1,97 +1,108 @@
package net.corda.testing.driver; package net.corda.testing.driver;
import sun.misc.Unsafe; import jdk.incubator.foreign.MemoryHandles;
import sun.nio.ch.DirectBuffer; import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.UncheckedIOException;
import java.lang.reflect.Field; import java.lang.invoke.VarHandle;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer; import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
/** import static java.nio.file.StandardOpenOption.READ;
* JDK11 upgrade: rewritten in Java to gain access to private internal JDK classes via module directives (not available to Kotlin compiler): import static java.nio.file.StandardOpenOption.WRITE;
* import sun.misc.Unsafe;
* import sun.nio.ch.DirectBuffer; // This was originally (re)written in Java to access internal JDK APIs. Since it's no longer doing that, this can be converted back to Kotlin.
*/
public class SharedMemoryIncremental extends PortAllocation { public class SharedMemoryIncremental extends PortAllocation {
private static final int DEFAULT_START_PORT = 10_000;
private static final int FIRST_EPHEMERAL_PORT = 30_000;
static private final int DEFAULT_START_PORT = 10_000; private final int startPort;
static private final int FIRST_EPHEMERAL_PORT = 30_000; private final int endPort;
private int startPort; private final MemorySegment memorySegment;
private int endPort; private final VarHandle intHandle;
private final MappedByteBuffer unsafeBuffer;
private MappedByteBuffer mb;
private Long startingAddress;
private File file = new File(System.getProperty("user.home"), "corda-" + startPort + "-to-" + endPort + "-port-allocator.bin");
private RandomAccessFile backingFile;
{
try {
backingFile = new RandomAccessFile(file, "rw");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
private SharedMemoryIncremental(int startPort, int endPort) { private SharedMemoryIncremental(int startPort, int endPort) {
this.startPort = startPort; this.startPort = startPort;
this.endPort = endPort; this.endPort = endPort;
Path file = Path.of(System.getProperty("user.home"), "corda-" + startPort + "-to-" + endPort + "-port-allocator.bin");
try { try {
mb = backingFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 16); try {
startingAddress = ((DirectBuffer) mb).address(); Files.createFile(file);
} catch (FileAlreadyExistsException ignored) {}
if (isFfmAvailable()) {
memorySegment = MemorySegment.mapFile(file, 0, Integer.SIZE, MapMode.READ_WRITE, ResourceScope.globalScope());
intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());
unsafeBuffer = null;
} else {
LoggerFactory.getLogger(getClass()).warn("Using unsafe port allocator which may lead to the same port being allocated " +
"twice. Consider adding --add-modules=jdk.incubator.foreign to the test JVM.");
memorySegment = null;
intHandle = null;
unsafeBuffer = FileChannel.open(file, READ, WRITE).map(MapMode.READ_WRITE, 0, Integer.SIZE);
}
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); throw new UncheckedIOException(e);
}
}
private static boolean isFfmAvailable() {
try {
Class.forName("jdk.incubator.foreign.MemorySegment");
return true;
} catch (ClassNotFoundException e) {
return false;
} }
} }
public static SharedMemoryIncremental INSTANCE = new SharedMemoryIncremental(DEFAULT_START_PORT, FIRST_EPHEMERAL_PORT); public static SharedMemoryIncremental INSTANCE = new SharedMemoryIncremental(DEFAULT_START_PORT, FIRST_EPHEMERAL_PORT);
static private Unsafe UNSAFE = getUnsafe();
static private Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
@Override @Override
public int nextPort() { public int nextPort() {
long oldValue; while (true) {
long newValue; int oldValue;
boolean loopSuccess; if (intHandle != null) {
do { oldValue = (int) intHandle.getVolatile(memorySegment, 0L);
oldValue = UNSAFE.getLongVolatile(null, startingAddress); } else {
oldValue = unsafeBuffer.getInt(0);
}
int newValue;
if (oldValue + 1 >= endPort || oldValue < startPort) { if (oldValue + 1 >= endPort || oldValue < startPort) {
newValue = startPort; newValue = startPort;
} else { } else {
newValue = (oldValue + 1); newValue = (oldValue + 1);
} }
boolean reserveSuccess = UNSAFE.compareAndSwapLong(null, startingAddress, oldValue, newValue); if (intHandle != null) {
loopSuccess = reserveSuccess && isLocalPortAvailable(newValue); if (!intHandle.compareAndSet(memorySegment, 0L, oldValue, newValue)) {
} while (!loopSuccess); continue;
}
return (int) newValue; } else {
unsafeBuffer.putInt(0, newValue);
}
if (isLocalPortAvailable(newValue)) {
return newValue;
}
}
} }
private boolean isLocalPortAvailable(int portToTest) {
private boolean isLocalPortAvailable(Long portToTest) { try (ServerSocket ignored = new ServerSocket(portToTest)) {
try (ServerSocket serverSocket = new ServerSocket(Math.toIntExact(portToTest))) { return true;
} catch (IOException e) { } catch (IOException e) {
// Don't catch anything other than IOException here in case we // Don't catch anything other than IOException here in case we
// accidentally create an infinite loop. For example, installing // accidentally create an infinite loop. For example, installing
// a SecurityManager could throw AccessControlException. // a SecurityManager could throw AccessControlException.
return false; return false;
} }
return true;
} }
} }

View File

@ -49,6 +49,7 @@ import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.coretesting.internal.DEV_ROOT_CA import net.corda.coretesting.internal.DEV_ROOT_CA
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.internal.cordapp.JarScanningCordappLoader
@ -76,7 +77,6 @@ import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.internal.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.configureDatabase import net.corda.testing.internal.configureDatabase
import net.corda.testing.internal.services.InternalMockAttachmentStorage import net.corda.testing.internal.services.InternalMockAttachmentStorage
import net.corda.testing.node.internal.DriverDSLImpl
import net.corda.testing.node.internal.MockCryptoService import net.corda.testing.node.internal.MockCryptoService
import net.corda.testing.node.internal.MockKeyManagementService import net.corda.testing.node.internal.MockKeyManagementService
import net.corda.testing.node.internal.MockNetworkParametersStorage import net.corda.testing.node.internal.MockNetworkParametersStorage
@ -85,6 +85,7 @@ import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.getCallerPackage import net.corda.testing.node.internal.getCallerPackage
import net.corda.testing.services.MockAttachmentStorage import net.corda.testing.services.MockAttachmentStorage
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Paths import java.nio.file.Paths
import java.security.KeyPair import java.security.KeyPair
import java.sql.Connection import java.sql.Connection
@ -141,8 +142,8 @@ open class MockServices private constructor(
val dbPath = dbDir.resolve("persistence") val dbPath = dbDir.resolve("persistence")
try { try {
DatabaseSnapshot.copyDatabaseSnapshot(dbDir) DatabaseSnapshot.copyDatabaseSnapshot(dbDir)
} catch (ex: java.nio.file.FileAlreadyExistsException) { } catch (e: FileAlreadyExistsException) {
DriverDSLImpl.log.warn("Database already exists on disk, not attempting to pre-migrate database.") loggerFor<MockServices>().warn("Database already exists on disk, not attempting to pre-migrate database.")
} }
val props = Properties() val props = Properties()
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource") props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")

View File

@ -45,6 +45,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import net.corda.core.utilities.toHexString import net.corda.core.utilities.toHexString
import net.corda.coretesting.internal.stubs.CertificateStoreStubs import net.corda.coretesting.internal.stubs.CertificateStoreStubs
@ -845,7 +846,7 @@ class DriverDSLImpl(
companion object { companion object {
private val RPC_CONNECT_POLL_INTERVAL: Duration = 100.millis private val RPC_CONNECT_POLL_INTERVAL: Duration = 100.millis
internal val log = contextLogger() private val log = contextLogger()
// While starting with inProcess mode, we need to have different names to avoid clashes // While starting with inProcess mode, we need to have different names to avoid clashes
private val inMemoryCounter = AtomicInteger() private val inMemoryCounter = AtomicInteger()
@ -960,7 +961,7 @@ class DriverDSLImpl(
"org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" + "org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;" +
"org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;" + "org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;" +
"com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;)" "com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**;)"
val excludeClassloaderPattern = "l(net.corda.djvm.**;net.corda.core.serialization.internal.**)" val excludeClassloaderPattern = "l(net.corda.core.serialization.internal.**)"
val quasarOptions = "m" val quasarOptions = "m"
val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } + val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } +
"-javaagent:$quasarJarPath=$quasarOptions$excludePackagePattern$excludeClassloaderPattern" "-javaagent:$quasarJarPath=$quasarOptions$excludePackagePattern$excludeClassloaderPattern"
@ -1002,24 +1003,11 @@ class DriverDSLImpl(
&& !cpPathEntry.isExcludedJar && !cpPathEntry.isExcludedJar
} }
val moduleOpens = listOf(
"--add-opens", "java.base/java.time=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED",
"--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.net=ALL-UNNAMED",
"--add-opens", "java.base/java.nio=ALL-UNNAMED", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED",
"--add-opens", "java.base/java.security.cert=ALL-UNNAMED", "--add-opens", "java.base/javax.net.ssl=ALL-UNNAMED",
"--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", "--add-opens", "java.sql/java.sql=ALL-UNNAMED",
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
)
val moduleExports = listOf(
"--add-exports", "java.base/sun.nio.ch=ALL-UNNAMED"
)
return ProcessUtilities.startJavaProcess( return ProcessUtilities.startJavaProcess(
className = "net.corda.node.Corda", // cannot directly get class for this, so just use string className = "net.corda.node.Corda", // cannot directly get class for this, so just use string
arguments = arguments, arguments = arguments,
jdwpPort = debugPort, jdwpPort = debugPort,
extraJvmArguments = extraJvmArguments + bytemanJvmArgs + moduleOpens + moduleExports + "-Dnet.corda.node.printErrorsToStdErr=true", extraJvmArguments = extraJvmArguments + bytemanJvmArgs + nodeJvmArgs + "-Dnet.corda.node.printErrorsToStdErr=true",
workingDirectory = config.corda.baseDirectory, workingDirectory = config.corda.baseDirectory,
maximumHeapSize = maximumHeapSize, maximumHeapSize = maximumHeapSize,
classPath = cp, classPath = cp,
@ -1066,22 +1054,13 @@ class DriverDSLImpl(
} }
private fun startWebserver(handle: NodeHandleInternal, debugPort: Int?, maximumHeapSize: String): Process { private fun startWebserver(handle: NodeHandleInternal, debugPort: Int?, maximumHeapSize: String): Process {
val className = "net.corda.webserver.WebServer"
val moduleOpens = listOf(
"--add-opens", "java.base/java.time=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED",
"--add-opens", "java.base/java.util=ALL-UNNAMED", "--add-opens", "java.base/java.net=ALL-UNNAMED",
"--add-opens", "java.base/java.nio=ALL-UNNAMED", "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED",
"--add-opens", "java.base/java.security.cert=ALL-UNNAMED", "--add-opens", "java.base/javax.net.ssl=ALL-UNNAMED",
"--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", "--add-opens", "java.sql/java.sql=ALL-UNNAMED",
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
)
writeConfig(handle.baseDirectory, "web-server.conf", handle.toWebServerConfig()) writeConfig(handle.baseDirectory, "web-server.conf", handle.toWebServerConfig())
return ProcessUtilities.startJavaProcess( return ProcessUtilities.startJavaProcess(
className = className, // cannot directly get class for this, so just use string className = "net.corda.webserver.WebServer", // cannot directly get class for this, so just use string
workingDirectory = handle.baseDirectory,
arguments = listOf(BASE_DIR, handle.baseDirectory.toString()), arguments = listOf(BASE_DIR, handle.baseDirectory.toString()),
jdwpPort = debugPort, jdwpPort = debugPort,
extraJvmArguments = listOf("-Dname=node-${handle.p2pAddress}-webserver") + moduleOpens + extraJvmArguments = listOf("-Dname=node-${handle.p2pAddress}-webserver") +
inheritFromParentProcess().map { "-D${it.first}=${it.second}" }, inheritFromParentProcess().map { "-D${it.first}=${it.second}" },
maximumHeapSize = maximumHeapSize maximumHeapSize = maximumHeapSize
) )
@ -1101,12 +1080,11 @@ class DriverDSLImpl(
} }
private fun NodeHandleInternal.toWebServerConfig(): Config { private fun NodeHandleInternal.toWebServerConfig(): Config {
var config = ConfigFactory.empty() var config = ConfigFactory.empty()
config += "webAddress" to webAddress.toString() config += "webAddress" to webAddress.toString()
config += "myLegalName" to configuration.myLegalName.toString() config += "myLegalName" to configuration.myLegalName.toString()
config += "rpcAddress" to configuration.rpcOptions.address.toString() config += "rpcAddress" to configuration.rpcOptions.address.toString()
config += "rpcUsers" to configuration.toConfig().getValue("rpcUsers") config += "rpcUsers" to configuration.rpcUsers.map { it.toConfig().root().unwrapped() }
config += "useHTTPS" to useHTTPS config += "useHTTPS" to useHTTPS
config += "baseDirectory" to configuration.baseDirectory.toAbsolutePath().toString() config += "baseDirectory" to configuration.baseDirectory.toAbsolutePath().toString()
@ -1276,7 +1254,7 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
driverDsl.start() driverDsl.start()
return dsl(coerce(driverDsl)) return dsl(coerce(driverDsl))
} catch (exception: Throwable) { } catch (exception: Throwable) {
DriverDSLImpl.log.error("Driver shutting down because of exception", exception) loggerFor<DriverDSL>().error("Driver shutting down because of exception", exception)
throw exception throw exception
} finally { } finally {
driverDsl.shutdown() driverDsl.shutdown()

View File

@ -301,6 +301,10 @@ fun DriverDSL.assertUncompletedCheckpoints(name: CordaX500Name, expected: Long)
} }
} }
val nodeJvmArgs: List<String> by lazy {
DriverDSLImpl::class.java.getResourceAsStream("node-jvm-args.txt")!!.use { it.bufferedReader().readLines() }
}
/** /**
* Should only be used by Driver and MockNode. * Should only be used by Driver and MockNode.
*/ */

View File

@ -39,7 +39,6 @@ object ProcessUtilities {
maximumHeapSize: String? = null, maximumHeapSize: String? = null,
identifier: String = "", identifier: String = "",
environmentVariables: Map<String,String> = emptyMap(), environmentVariables: Map<String,String> = emptyMap(),
inheritIO: Boolean = true
): Process { ): Process {
val command = mutableListOf<String>().apply { val command = mutableListOf<String>().apply {
add(javaPath) add(javaPath)
@ -50,7 +49,6 @@ object ProcessUtilities {
addAll(arguments) addAll(arguments)
} }
return ProcessBuilder(command).apply { return ProcessBuilder(command).apply {
if (inheritIO) inheritIO()
environment().putAll(environmentVariables) environment().putAll(environmentVariables)
environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator) environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator)
if (workingDirectory != null) { if (workingDirectory != null) {

View File

@ -148,24 +148,25 @@ class NodeProcess(
private fun createSchema(nodeDir: Path){ private fun createSchema(nodeDir: Path){
val process = startNode(nodeDir, arrayOf("run-migration-scripts", "--core-schemas", "--app-schemas")) val process = startNode(nodeDir, "run-migration-scripts", "--core-schemas", "--app-schemas")
if (!process.waitFor(schemaCreationTimeOutSeconds, SECONDS)) { if (!process.waitFor(schemaCreationTimeOutSeconds, SECONDS)) {
process.destroy() process.destroy()
throw SchemaCreationTimedOutError(nodeDir) throw SchemaCreationTimedOutError(nodeDir)
} }
if (process.exitValue() != 0){ if (process.exitValue() != 0) {
throw SchemaCreationFailedError(nodeDir) throw SchemaCreationFailedError(nodeDir)
} }
} }
@Suppress("SpreadOperator") private fun startNode(nodeDir: Path, vararg extraArgs: String): Process {
private fun startNode(nodeDir: Path, extraArgs: Array<String> = emptyArray()): Process { val command = arrayListOf(javaPath.toString(), "-Dcapsule.log=verbose", "-jar", cordaJar.toString())
command += extraArgs
val now = formatter.format(Instant.now())
val builder = ProcessBuilder() val builder = ProcessBuilder()
.command(javaPath.toString(), "-Dcapsule.log=verbose", "-jar", cordaJar.toString(), *extraArgs) .command(command)
.directory(nodeDir.toFile()) .directory(nodeDir.toFile())
.redirectError(ProcessBuilder.Redirect.INHERIT) .redirectError((nodeDir / "$now-stderr.log").toFile())
.redirectOutput(ProcessBuilder.Redirect.INHERIT) .redirectOutput((nodeDir / "$now-stdout.log").toFile())
builder.environment().putAll(mapOf( builder.environment().putAll(mapOf(
"CAPSULE_CACHE_DIR" to (buildDirectory / "capsule").toString() "CAPSULE_CACHE_DIR" to (buildDirectory / "capsule").toString()
)) ))

View File

@ -80,9 +80,6 @@ dependencies {
tasks.register('integrationTest', Test) { tasks.register('integrationTest', Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath classpath = sourceSets.integrationTest.runtimeClasspath
jvmArgs test_add_opens
jvmArgs test_add_exports
} }
jar { jar {

View File

@ -2,14 +2,22 @@
// must also be in the default package. When using Kotlin there are a whole host of exceptions // must also be in the default package. When using Kotlin there are a whole host of exceptions
// trying to construct this from Capsule, so it is written in Java. // trying to construct this from Capsule, so it is written in Java.
import com.typesafe.config.*; import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigValue;
import sun.misc.Signal; import sun.misc.Signal;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
public class CordaWebserverCaplet extends Capsule { public class CordaWebserverCaplet extends Capsule {
@ -37,11 +45,6 @@ public class CordaWebserverCaplet extends Capsule {
} }
} }
File getConfigFile(List<String> args, String baseDir) {
String config = getOptionMultiple(args, Arrays.asList("--config-file", "-f"));
return (config == null || config.equals("")) ? new File(baseDir, "node.conf") : new File(config);
}
String getBaseDirectory(List<String> args) { String getBaseDirectory(List<String> args) {
String baseDir = getOptionMultiple(args, Arrays.asList("--base-directory", "-b")); String baseDir = getOptionMultiple(args, Arrays.asList("--base-directory", "-b"));
return Paths.get((baseDir == null) ? "." : baseDir).toAbsolutePath().normalize().toString(); return Paths.get((baseDir == null) ? "." : baseDir).toAbsolutePath().normalize().toString();
@ -69,7 +72,7 @@ public class CordaWebserverCaplet extends Capsule {
} }
if (arg.toLowerCase().startsWith(lowerCaseOption)) { if (arg.toLowerCase().startsWith(lowerCaseOption)) {
if (arg.length() > option.length() && arg.substring(option.length(), option.length() + 1).equals("=")) { if (arg.length() > option.length() && arg.charAt(option.length()) == '=') {
return arg.substring(option.length() + 1); return arg.substring(option.length() + 1);
} else { } else {
return null; return null;
@ -87,23 +90,6 @@ public class CordaWebserverCaplet extends Capsule {
return super.prelaunch(jvmArgs, args); return super.prelaunch(jvmArgs, args);
} }
// Capsule does not handle multiple instances of same option hence we add in the args here to process builder
// For multiple instances Capsule jvm args handling works on basis that one overrides the other.
@Override
protected int launch(ProcessBuilder pb) throws IOException, InterruptedException {
if (isAtLeastJavaVersion11()) {
List<String> args = pb.command();
List<String> myArgs = Arrays.asList(
"--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.time=ALL-UNNAMED",
"--add-opens=java.base/java.io=ALL-UNNAMED",
"--add-opens=java.base/java.nio=ALL-UNNAMED");
args.addAll(1, myArgs);
pb.command(args);
}
return super.launch(pb);
}
// Add working directory variable to capsules string replacement variables. // Add working directory variable to capsules string replacement variables.
@Override @Override
protected String getVarValue(String var) { protected String getVarValue(String var) {
@ -157,9 +143,6 @@ public class CordaWebserverCaplet extends Capsule {
} catch (ConfigException e) { } catch (ConfigException e) {
log(LOG_QUIET, e); log(LOG_QUIET, e);
} }
if (isAtLeastJavaVersion11()) {
jvmArgs.add("-Dnashorn.args=--no-deprecation-warning");
}
return (T) jvmArgs; return (T) jvmArgs;
} else if (ATTR_SYSTEM_PROPERTIES == attr) { } else if (ATTR_SYSTEM_PROPERTIES == attr) {
// Add system properties, if specified, from the config. // Add system properties, if specified, from the config.
@ -202,14 +185,6 @@ public class CordaWebserverCaplet extends Capsule {
} }
} }
private static boolean isAtLeastJavaVersion11() {
String version = System.getProperty("java.specification.version");
if (version != null) {
return Float.parseFloat(version) >= 11f;
}
return false;
}
private Boolean checkIfCordappDirExists(File dir) { private Boolean checkIfCordappDirExists(File dir) {
try { try {
if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case. if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case.

View File

@ -55,10 +55,6 @@ tasks.register('buildWebserverJar', FatCapsule) {
// If you change these flags, please also update Driver.kt // If you change these flags, please also update Driver.kt
jvmArgs = ['-Xmx200m'] jvmArgs = ['-Xmx200m']
} }
manifest {
attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver java.base/java.lang')
}
} }
artifacts { artifacts {

View File

@ -16,20 +16,3 @@ dependencies {
runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
} }
jar {
manifest {
attributes("Add-Opens":
"java.base/java.lang " +
"java.base/java.lang.reflect " +
"java.base/java.lang.invoke " +
"java.base/java.util " +
"java.base/java.time " +
"java.base/java.io " +
"java.base/java.net " +
"java.base/javax.net.ssl " +
"java.base/java.security.cert " +
"java.base/java.nio"
)
}
}