From 2e63ca6264626c3137327484d39b35ff2177a99b Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 3 Jan 2024 11:22:03 +0000 Subject: [PATCH] ENT-11065: Remove the need for JVM flags in client code (#7635) --- build.gradle | 41 ++---- client/rpc/build.gradle | 85 +++++++++--- .../corda/client/rpc/CordaRPCClientTest.kt | 13 +- .../net/corda/client/rpc/RPCStabilityTests.kt | 5 +- .../rpc/StandaloneCordaRPCJavaClientTest.java | 0 .../kotlin/rpc/StandaloneCordaRPClientTest.kt | 16 +-- core-tests/build.gradle | 4 +- .../net/corda/coretests/NodeVersioningTest.kt | 4 +- core/build.gradle | 13 +- .../net/corda/core/internal/InternalUtils.kt | 4 +- .../net/corda/core/internal/PathUtils.kt | 2 +- .../internal/AttachmentsClassLoader.kt | 22 +-- .../corda/core/utilities/ByteArraysTest.kt | 18 +-- docker/build.gradle | 11 -- finance/workflows/build.gradle | 3 - gradle.properties | 2 +- node-api/build.gradle | 4 - .../internal/config/ConfigUtilities.kt | 100 +++----------- .../net/corda/nodeapi/internal/config/User.kt | 2 - .../CertHoldingKeyManagerFactoryWrapper.kt | 33 ++--- .../netty/TrustManagerFactoryWrapper.kt | 32 ++--- .../client/RpcClientCordaFutureSerializer.kt | 5 +- .../serialization/kryo/KryoStreamsTest.kt | 9 +- node/build.gradle | 10 -- node/capsule/build.gradle | 7 +- node/capsule/src/main/java/CordaCaplet.java | 125 +++++++---------- .../src/main/resources/node-jvm-args.txt | 9 ++ .../kotlin/net/corda/node/SerialFilter.kt | 49 +------ .../verification/ExternalVerifierHandle.kt | 12 +- samples/attachment-demo/build.gradle | 9 -- .../attachmentdemo/AttachmentDemoTest.kt | 9 +- .../corda/attachmentdemo/AttachmentDemo.kt | 18 ++- samples/bank-of-corda-demo/build.gradle | 12 -- samples/simm-valuation-demo/build.gradle | 3 - samples/trader-demo/build.gradle | 13 -- .../internal/amqp/SerializationOutputTests.kt | 22 +-- serialization/build.gradle | 4 - .../internal/ByteBufferStreams.kt | 14 +- .../internal/SerializationUtils.kt | 8 ++ .../internal/amqp/AMQPExceptions.kt | 31 ++--- .../internal/amqp/DeserializationInput.kt | 5 +- .../internal/amqp/ObjectBuilder.kt | 12 +- .../amqp/custom/CertPathSerializer.kt | 6 +- .../amqp/custom/InputStreamSerializer.kt | 7 +- .../amqp/custom/ZonedDateTimeSerializer.kt | 25 +--- .../model/LocalTypeInformationBuilder.kt | 36 +++-- .../internal/amqp/DeserializeMapTests.kt | 13 +- settings.gradle | 1 - testing/client-rpc/build.gradle | 73 ---------- testing/node-driver/build.gradle | 11 +- .../node/MockNetworkIntegrationTests.kt | 26 ++-- .../CordaCliWrapperErrorHandlingTests.kt | 6 +- .../InternalMockNetworkIntegrationTests.kt | 26 ++-- .../driver/SharedMemoryIncremental.java | 127 ++++++++++-------- .../net/corda/testing/node/MockServices.kt | 7 +- .../testing/node/internal/DriverDSLImpl.kt | 40 ++---- .../node/internal/InternalTestUtils.kt | 4 + .../testing/node/internal/ProcessUtilities.kt | 2 - .../net/corda/smoketesting/NodeProcess.kt | 17 +-- testing/testserver/build.gradle | 3 - .../src/main/java/CordaWebserverCaplet.java | 49 ++----- testing/testserver/testcapsule/build.gradle | 4 - verifier/build.gradle | 17 --- 63 files changed, 470 insertions(+), 830 deletions(-) rename {testing/client-rpc => client/rpc}/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java (100%) rename {testing/client-rpc => client/rpc}/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt (97%) create mode 100644 node/capsule/src/main/resources/node-jvm-args.txt create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/SerializationUtils.kt delete mode 100644 testing/client-rpc/build.gradle diff --git a/build.gradle b/build.gradle index cfb95addf7..8f6404af26 100644 --- a/build.gradle +++ b/build.gradle @@ -121,23 +121,6 @@ buildscript { ext.fontawesomefx_commons_version = constants.getProperty("fontawesomefxCommonsVersion") ext.fontawesomefx_fontawesome_version = constants.getProperty("fontawesomefxFontawesomeVersion") 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 = { try { @@ -282,11 +265,6 @@ allprojects { toolVersion = "0.8.7" } - test { - jvmArgs test_add_opens - jvmArgs test_add_exports - } - java { withSourcesJar() withJavadocJar() @@ -330,15 +308,14 @@ allprojects { attributes('Corda-Docs-Link': corda_docs_link) } } - + 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 ignoreFailures = project.hasProperty('tests.ignoreFailures') ? project.property('tests.ignoreFailures').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" if (project.path.startsWith(':experimental') && System.getProperty("experimental.test.enable") == null) { @@ -351,15 +328,15 @@ allprojects { // 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")) { 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' diff --git a/client/rpc/build.gradle b/client/rpc/build.gradle index 30afb18723..835ed7f4a8 100644 --- a/client/rpc/build.gradle +++ b/client/rpc/build.gradle @@ -10,6 +10,9 @@ description 'Corda client RPC modules' configurations { integrationTestImplementation.extendsFrom testImplementation integrationTestRuntimeOnly.extendsFrom testRuntimeOnly + + smokeTestImplementation.extendsFrom compile + smokeTestRuntimeOnly.extendsFrom runtimeOnly } sourceSets { @@ -28,55 +31,95 @@ sourceSets { 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 { implementation project(':core') implementation project(':node-api') implementation project(':serialization') - // For caches rather than guava implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version" - implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - - testImplementation "junit:junit:$junit_version" - - testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}" - testRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_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" + implementation "io.reactivex:rxjava:$rxjava_version" + implementation("org.apache.activemq:artemis-core-client:${artemis_version}") { + exclude group: 'org.jgroups', module: 'jgroups' + } + implementation "com.google.guava:guava-testlib:$guava_version" testImplementation project(':node') testImplementation project(':node-driver') testImplementation project(':client:mock') 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(':common-configuration-parsing') integrationTestImplementation project(':finance:contracts') integrationTestImplementation project(':finance:workflows') integrationTestImplementation project(':test-utils') - integrationTestImplementation "co.paralleluniverse:quasar-core:$quasar_version" integrationTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version" - implementation "io.reactivex:rxjava:$rxjava_version" - implementation("org.apache.activemq:artemis-core-client:${artemis_version}") { - exclude group: 'org.jgroups', module: 'jgroups' - } - implementation "com.google.guava:guava-testlib:$guava_version" + smokeTestImplementation project(':core') + smokeTestImplementation project(':node-api') + smokeTestImplementation project(':finance:contracts') + smokeTestImplementation project(':finance:workflows') + 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 classpath = sourceSets.integrationTest.runtimeClasspath +} - jvmArgs test_add_opens - jvmArgs test_add_exports +tasks.register('smokeTest', Test) { + testClassesDirs = sourceSets.smokeTest.output.classesDirs + classpath = sourceSets.smokeTest.runtimeClasspath } jar { diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 8c55b9e373..1e8d08c4ac 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -54,7 +54,7 @@ import org.junit.Test import rx.subjects.PublishSubject import java.net.URLClassLoader import java.nio.file.Paths -import java.util.* +import java.util.Currency import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors 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`() { val financeLocation = Cash::class.java.location.toPath().toString() 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 node.services.startFlow(CashIssueFlow(100.POUNDS, OpaqueBytes.of(1), identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow() val outOfProcessRpc = ProcessUtilities.startJavaProcess( classPath = classPathWithoutFinance, - arguments = listOf(node.node.configuration.rpcOptions.address.toString(), financeLocation), - extraJvmArguments = moduleOpens + arguments = listOf(node.node.configuration.rpcOptions.address.toString(), financeLocation) ) assertThat(outOfProcessRpc.waitFor()).isZero() // i.e. no exceptions were thrown } diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt index 19f0edf6b8..09c8c5a4a9 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt @@ -89,7 +89,6 @@ class RPCStabilityTests { } @Test(timeout=300_000) - @Ignore("TODO JDK17:Fixme") fun `client and server dont leak threads`() { fun startAndStop() { rpcDriver { @@ -122,7 +121,6 @@ class RPCStabilityTests { } @Test(timeout=300_000) - @Ignore("TODO JDK17:Fixme") fun `client doesnt leak threads when it fails to start`() { fun startAndStop() { 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. */ @Test(timeout=300_000) - @Ignore("TODO JDK17:Fixme") fun `server cleans up queues after disconnected clients`() { rpcDriver { val trackSubscriberOpsImpl = object : TrackSubscriberOps { @@ -547,7 +544,7 @@ class RPCStabilityTests { } @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`() { rpcDriver { val server = startRpcServer(maxBufferedBytesPerClient = 10 * 1024 * 1024, ops = SlowConsumerRPCOpsImpl()).get() diff --git a/testing/client-rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java similarity index 100% rename from testing/client-rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java rename to client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java diff --git a/testing/client-rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt similarity index 97% rename from testing/client-rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt rename to client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index 54504da08b..f63d67a467 100644 --- a/testing/client-rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -40,7 +40,6 @@ import net.corda.nodeapi.internal.config.User import net.corda.sleeping.SleepingFlow import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeProcess -import org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM import org.hamcrest.text.MatchesPattern import org.junit.After import org.junit.Before @@ -50,6 +49,7 @@ import org.junit.Test import org.junit.rules.ExpectedException import java.io.FilterInputStream import java.io.InputStream +import java.io.OutputStream.nullOutputStream import java.util.Currency import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicInteger @@ -67,7 +67,7 @@ class StandaloneCordaRPClientTest { 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 port = AtomicInteger(15200) - const val attachmentSize = 2116 + const val ATTACHMENT_SIZE = 2116 val timeout = 60.seconds } @@ -111,13 +111,13 @@ class StandaloneCordaRPClientTest { @Test(timeout=300_000) fun `test attachments`() { - val attachment = InputStreamAndHash.createInMemoryTestZip(attachmentSize, 1) + val attachment = InputStreamAndHash.createInMemoryTestZip(ATTACHMENT_SIZE, 1) assertFalse(rpcProxy.attachmentExists(attachment.sha256)) val id = attachment.inputStream.use { rpcProxy.uploadAttachment(it) } assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash") - val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { it -> - it.copyTo(NULL_OUTPUT_STREAM) + val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { + it.copyTo(nullOutputStream()) SecureHash.createSHA256(it.hash().asBytes()) } assertEquals(attachment.sha256, hash) @@ -126,13 +126,13 @@ class StandaloneCordaRPClientTest { @Ignore("CORDA-1520 - After switching from Kryo to AMQP this test won't work") @Test(timeout=300_000) fun `test wrapped attachments`() { - val attachment = InputStreamAndHash.createInMemoryTestZip(attachmentSize, 1) + val attachment = InputStreamAndHash.createInMemoryTestZip(ATTACHMENT_SIZE, 1) assertFalse(rpcProxy.attachmentExists(attachment.sha256)) val id = WrapperStream(attachment.inputStream).use { rpcProxy.uploadAttachment(it) } assertEquals(attachment.sha256, id, "Attachment has incorrect SHA256 hash") - val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { it -> - it.copyTo(NULL_OUTPUT_STREAM) + val hash = HashingInputStream(Hashing.sha256(), rpcProxy.openAttachment(id)).use { + it.copyTo(nullOutputStream()) SecureHash.createSHA256(it.hash().asBytes()) } assertEquals(attachment.sha256, hash) diff --git a/core-tests/build.gradle b/core-tests/build.gradle index 93fd926983..8d9150999c 100644 --- a/core-tests/build.gradle +++ b/core-tests/build.gradle @@ -107,6 +107,7 @@ dependencies { smokeTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}" smokeTestRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}" smokeTestRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}" + smokeTestRuntimeOnly "org.slf4j:slf4j-simple:$slf4j_version" smokeTestCompile project(':smoke-test-utils') smokeTestCompile "org.assertj:assertj-core:${assertj_version}" @@ -143,9 +144,6 @@ task smokeTest(type: Test) { dependsOn smokeTestJar testClassesDirs = sourceSets.smokeTest.output.classesDirs classpath = sourceSets.smokeTest.runtimeClasspath - - jvmArgs test_add_opens - jvmArgs test_add_exports } idea { diff --git a/core-tests/src/smoke-test/kotlin/net/corda/coretests/NodeVersioningTest.kt b/core-tests/src/smoke-test/kotlin/net/corda/coretests/NodeVersioningTest.kt index 6b534aa221..c9f5d9f15d 100644 --- a/core-tests/src/smoke-test/kotlin/net/corda/coretests/NodeVersioningTest.kt +++ b/core-tests/src/smoke-test/kotlin/net/corda/coretests/NodeVersioningTest.kt @@ -48,7 +48,7 @@ class NodeVersioningTest { users = listOf(superUser) ) - private lateinit var notary: NodeProcess + private var notary: NodeProcess? = null @Before fun setUp() { @@ -57,7 +57,7 @@ class NodeVersioningTest { @After fun done() { - notary.close() + notary?.close() } @Test(timeout=300_000) diff --git a/core/build.gradle b/core/build.gradle index 7267a11336..427179cb67 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -23,16 +23,18 @@ configurations { } 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" // SLF4J: commons-logging bindings for a SLF4J back end implementation "org.slf4j:jcl-over-slf4j:$slf4j_version" - implementation "org.slf4j:slf4j-api:$slf4j_version" // Guava: Google utilities library. implementation "com.google.guava:guava:$guava_version" // For caches rather than guava 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" // Java ed25519 implementation. See https://github.com/str4d/ed25519-java/ implementation "net.i2p.crypto:eddsa:$eddsa_version" @@ -80,10 +82,6 @@ jar { finalizedBy(copyQuasarJar) archiveBaseName = 'corda-core' archiveClassifier = '' - - manifest { - attributes('Add-Opens': 'java.base/java.net java.base/java.nio') - } } processTestResources { @@ -103,6 +101,7 @@ compileTestJava { } test { + // TODO This obscures whether any Corda client APIs need these JVM flags as well (which they shouldn't do) jvmArgs += [ '--add-exports', 'java.base/sun.security.util=ALL-UNNAMED', '--add-exports', 'java.base/sun.security.x509=ALL-UNNAMED' diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 99a60aa8e3..490994e8dd 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -166,7 +166,7 @@ fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.c 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. */ -fun InputStream.hash(): SecureHash { +fun InputStream.hash(): SecureHash.SHA256 { return use { val md = MessageDigest.getInstance("SHA-256") val buffer = ByteArray(DEFAULT_BUFFER_SIZE) @@ -309,6 +309,8 @@ inline fun Stream.mapNotNull(crossinline transform: (T) -> R?): /** Similar to [Collectors.toSet] except the Set is guaranteed to be ordered. */ fun Stream.toSet(): Set = collect(toCollection { LinkedHashSet() }) +val Class<*>.isJdkClass: Boolean get() = module.name?.startsWith("java.") == true + fun Class.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]. */ diff --git a/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt b/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt index 6dca0d8c7f..4abfe8ee60 100644 --- a/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/PathUtils.kt @@ -88,7 +88,7 @@ inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption = inline fun Path.readObject(): T = readBytes().deserialize() /** 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 */ fun Path.safeSymbolicRead(): Path = if (isSymbolicLink()) readSymbolicLink() else this diff --git a/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt b/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt index ee1a02a44b..669b2ea777 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/internal/AttachmentsClassLoader.kt @@ -93,26 +93,16 @@ class AttachmentsClassLoader(attachments: List, * or use a decorator and reflection to bypass the single-call-per-JVM restriction otherwise. */ private fun setOrDecorateURLStreamHandlerFactory() { - // Retrieve the `URL.factory` field - 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) { + try { URL.setURLStreamHandlerFactory(AttachmentURLStreamHandlerFactory) - } - // Otherwise, decorate the existing and replace via reflection - // as calling `URL.setURLStreamHandlerFactory` again will throw an error - else { + } catch (e: Error) { 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 // 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 - // Note: this will only work as-is in JDK8. - lockField.isAccessible = true + val existingFactory = factoryField.get(null) as URLStreamHandlerFactory? // Use the same lock to reset the factory synchronized(lockField.get(null)) { // Reset the value to prevent Error due to a factory already defined @@ -121,7 +111,7 @@ class AttachmentsClassLoader(attachments: List, URL.setURLStreamHandlerFactory { protocol -> // route between our own and the pre-existing factory AttachmentURLStreamHandlerFactory.createURLStreamHandler(protocol) - ?: existingFactory.createURLStreamHandler(protocol) + ?: existingFactory?.createURLStreamHandler(protocol) } } } diff --git a/core/src/test/kotlin/net/corda/core/utilities/ByteArraysTest.kt b/core/src/test/kotlin/net/corda/core/utilities/ByteArraysTest.kt index 11454f0723..1ff21dda36 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/ByteArraysTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/ByteArraysTest.kt @@ -2,7 +2,6 @@ package net.corda.core.utilities import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash -import net.corda.core.internal.declaredField import org.assertj.core.api.Assertions.catchThrowable import org.junit.Assert.assertSame import org.junit.Assert.assertTrue @@ -14,22 +13,17 @@ import kotlin.test.assertEquals class ByteArraysTest { @Test(timeout=300_000) fun `slice works`() { - byteArrayOf(9, 9, 0, 1, 2, 3, 4, 9, 9).let { - sliceWorksImpl(it, OpaqueBytesSubSequence(it, 2, 5)) - } - byteArrayOf(0, 1, 2, 3, 4).let { - sliceWorksImpl(it, OpaqueBytes(it)) - } + sliceWorksImpl(OpaqueBytesSubSequence(byteArrayOf(9, 9, 0, 1, 2, 3, 4, 9, 9), 2, 5)) + sliceWorksImpl(OpaqueBytes(byteArrayOf(0, 1, 2, 3, 4))) } - private fun sliceWorksImpl(array: ByteArray, seq: ByteSequence) { + private fun sliceWorksImpl(seq: ByteSequence) { // 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(end = -1) }.javaClass) fun check(expected: ByteArray, actual: ByteBuffer) { assertEquals(ByteBuffer.wrap(expected), actual) assertSame(ReadOnlyBufferException::class.java, catchThrowable { actual.array() }.javaClass) - assertSame(array, actual.declaredField(ByteBuffer::class, "hb").value) } check(byteArrayOf(0, 1, 2, 3, 4), seq.slice()) check(byteArrayOf(0, 1, 2, 3, 4), seq.slice(0, 5)) @@ -48,14 +42,14 @@ class ByteArraysTest { @Test(timeout=300_000) 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 privacySaltAsHexString = privacySalt.bytes.toHexString() - assertTrue(privacySaltAsHexString.matches(HEX_REGEX)) + assertTrue(privacySaltAsHexString.matches(hexRegex)) val stateRef = StateRef(SecureHash.randomSHA256(), 0) val txhashAsHexString = stateRef.txhash.bytes.toHexString() - assertTrue(txhashAsHexString.matches(HEX_REGEX)) + assertTrue(txhashAsHexString.matches(hexRegex)) } } diff --git a/docker/build.gradle b/docker/build.gradle index 3c6e95a83c..e5fe02bbaf 100644 --- a/docker/build.gradle +++ b/docker/build.gradle @@ -35,17 +35,6 @@ shadowJar { version = null zip64 true 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 { diff --git a/finance/workflows/build.gradle b/finance/workflows/build.gradle index 9e18e1ba0b..602248fc2c 100644 --- a/finance/workflows/build.gradle +++ b/finance/workflows/build.gradle @@ -64,9 +64,6 @@ task testJar(type: Jar) { task integrationTest(type: Test, dependsOn: []) { testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath - - jvmArgs test_add_opens - jvmArgs test_add_exports } jar { diff --git a/gradle.properties b/gradle.properties index 1e6c30d918..b41380bb02 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ 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 owasp.failOnError=false owasp.failBuildOnCVSS=11.0 diff --git a/node-api/build.gradle b/node-api/build.gradle index 16d7c46244..8b7dbdea93 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -104,10 +104,6 @@ artifacts { jar { 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 { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt index 6cbfb9936a..b23286c3f1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt @@ -17,9 +17,7 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.utilities.NetworkHostAndPort import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.lang.reflect.Field import java.lang.reflect.InvocationTargetException -import java.lang.reflect.ParameterizedType import java.net.Proxy import java.net.URL import java.nio.file.Path @@ -28,6 +26,7 @@ import java.time.Duration import java.time.Instant import java.time.LocalDate import java.time.temporal.Temporal +import java.time.temporal.TemporalAmount import java.util.Properties import java.util.UUID import javax.security.auth.x500.X500Principal @@ -298,104 +297,45 @@ private fun > enumBridge(clazz: Class, name: String): T { */ fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig() -fun Any?.toConfigValue(): ConfigValue = if (this is ConfigValue) { - this -} else if (this != null) { - ConfigValueFactory.fromAnyRef(convertValue(this)) -} else { - ConfigValueFactory.fromAnyRef(null) -} +fun Any?.toConfigValue(): ConfigValue = ConfigValueFactory.fromAnyRef(sanitiseForFromAnyRef(this)) -@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. -private fun Any.toConfigMap(): Map { - val values = HashMap() +private fun Any.toConfigMap(): Map { + val values = LinkedHashMap() for (field in javaClass.declaredFields) { if (field.isStatic || field.isSynthetic) continue field.isAccessible = true val value = field.get(this) ?: continue - val configValue = if (value is String || value is Boolean || value is Number) { - // 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 + values[field.name] = sanitiseForFromAnyRef(value) } return values } -private fun convertValue(value: Any): Any { - - return if (value is String || value is Boolean || value is Number) { +/** + * @see ConfigValueFactory.fromAnyRef + */ +private fun sanitiseForFromAnyRef(value: Any?): Any? { + return when (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) { + is String, is Boolean, is Number, is ConfigValue, is Duration, null -> value + is Enum<*> -> value.name + // These types make sense to be represented as Strings + is Temporal, is TemporalAmount, is NetworkHostAndPort, is CordaX500Name, is Path, is URL, is UUID, is X500Principal -> value.toString() // For Properties we treat keys with . as nested configs - ConfigFactory.parseMap(uncheckedCast(value)).root() - } else if (value is Iterable<*>) { - value.toConfigIterable() - } else { + is Properties -> ConfigFactory.parseMap(uncheckedCast(value)).root() + is Map<*, *> -> ConfigFactory.parseMap(value.map { it.key.toString() to sanitiseForFromAnyRef(it.value) }.toMap()).root() + is Iterable<*> -> value.map(::sanitiseForFromAnyRef) // 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 { - 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 = map { element -> element?.let(::convertValue) } - // The typesafe .getBoolean function is case sensitive, this is a case insensitive version fun Config.getBooleanCaseInsensitive(path: String): Boolean { try { return getBoolean(path) - } catch(e:Exception) { - val stringVal = getString(path).toLowerCase() + } catch (e: Exception) { + val stringVal = getString(path).lowercase() if (stringVal == "true" || stringVal == "false") { return stringVal.toBoolean() } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/User.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/User.kt index b5c8f09b2f..f6aa1012ad 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/User.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/User.kt @@ -6,6 +6,4 @@ data class User( val password: String, val permissions: Set) { override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)" - @Deprecated("Use toConfig().root().unwrapped() instead", ReplaceWith("toConfig().root().unwrapped()")) - fun toMap(): Map = toConfig().root().unwrapped() } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/CertHoldingKeyManagerFactoryWrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/CertHoldingKeyManagerFactoryWrapper.kt index 752b249a71..ea6d708b5d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/CertHoldingKeyManagerFactoryWrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/CertHoldingKeyManagerFactoryWrapper.kt @@ -9,25 +9,18 @@ import javax.net.ssl.ManagerFactoryParameters import javax.net.ssl.X509ExtendedKeyManager 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?) { - val engineInitMethod = KeyManagerFactorySpi::class.java.getDeclaredMethod("engineInit", KeyStore::class.java, CharArray::class.java) - engineInitMethod.isAccessible = true - engineInitMethod.invoke(factorySpi, keyStore, password) + keyManagerFactory.init(keyStore, password) } override fun engineInit(spec: ManagerFactoryParameters?) { - val engineInitMethod = KeyManagerFactorySpi::class.java.getDeclaredMethod("engineInit", ManagerFactoryParameters::class.java) - engineInitMethod.isAccessible = true - engineInitMethod.invoke(factorySpi, spec) + keyManagerFactory.init(spec) } private fun getKeyManagersImpl(): Array { - val engineGetKeyManagersMethod = KeyManagerFactorySpi::class.java.getDeclaredMethod("engineGetKeyManagers") - engineGetKeyManagersMethod.isAccessible = true - @Suppress("UNCHECKED_CAST") - val keyManagers = engineGetKeyManagersMethod.invoke(factorySpi) as Array - return if (factorySpi is CertHoldingKeyManagerFactorySpiWrapper) keyManagers else keyManagers.map { + return keyManagerFactory.keyManagers.map { val aliasProvidingKeyManager = getDefaultKeyManager(it) // 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. @@ -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 * of belonging to a certain channel. */ -class CertHoldingKeyManagerFactoryWrapper(factory: KeyManagerFactory, amqpConfig: AMQPConfiguration) : KeyManagerFactory(getFactorySpi(factory, amqpConfig), factory.provider, factory.algorithm) { - companion object { - private fun getFactorySpi(factory: KeyManagerFactory, amqpConfig: AMQPConfiguration): KeyManagerFactorySpi { - val spiField = KeyManagerFactory::class.java.getDeclaredField("factorySpi") - spiField.isAccessible = true - return CertHoldingKeyManagerFactorySpiWrapper(spiField.get(factory) as KeyManagerFactorySpi, amqpConfig) - } - } - +class CertHoldingKeyManagerFactoryWrapper(factory: KeyManagerFactory, amqpConfig: AMQPConfiguration) : KeyManagerFactory( + CertHoldingKeyManagerFactorySpiWrapper(factory, amqpConfig), + factory.provider, + factory.algorithm +) { fun getCurrentCertChain(): Array? { val keyManager = keyManagers.firstOrNull() val alias = if (keyManager is AliasProvidingKeyMangerWrapper) keyManager.lastAlias else null @@ -78,4 +67,4 @@ class CertHoldingKeyManagerFactoryWrapper(factory: KeyManagerFactory, amqpConfig keyManager.getCertificateChain(alias) } else null } -} \ No newline at end of file +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/TrustManagerFactoryWrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/TrustManagerFactoryWrapper.kt index 7565b1cdc2..934279f75c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/TrustManagerFactoryWrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/protonwrapper/netty/TrustManagerFactoryWrapper.kt @@ -7,34 +7,24 @@ import javax.net.ssl.TrustManagerFactory import javax.net.ssl.TrustManagerFactorySpi import javax.net.ssl.X509ExtendedTrustManager -class LoggingTrustManagerFactorySpiWrapper(private val factorySpi: TrustManagerFactorySpi) : TrustManagerFactorySpi() { +class LoggingTrustManagerFactorySpiWrapper(private val trustManagerFactory: TrustManagerFactory) : TrustManagerFactorySpi() { override fun engineGetTrustManagers(): Array { - val engineGetTrustManagersMethod = TrustManagerFactorySpi::class.java.getDeclaredMethod("engineGetTrustManagers") - engineGetTrustManagersMethod.isAccessible = true - @Suppress("UNCHECKED_CAST") - val trustManagers = engineGetTrustManagersMethod.invoke(factorySpi) as Array - return if (factorySpi is LoggingTrustManagerFactorySpiWrapper) trustManagers else trustManagers.filterIsInstance(X509ExtendedTrustManager::class.java).map { LoggingTrustManagerWrapper(it) }.toTypedArray() + return trustManagerFactory.trustManagers + .mapNotNull { (it as? X509ExtendedTrustManager)?.let(::LoggingTrustManagerWrapper) } + .toTypedArray() } override fun engineInit(ks: KeyStore?) { - val engineInitMethod = TrustManagerFactorySpi::class.java.getDeclaredMethod("engineInit", KeyStore::class.java) - engineInitMethod.isAccessible = true - engineInitMethod.invoke(factorySpi, ks) + trustManagerFactory.init(ks) } override fun engineInit(spec: ManagerFactoryParameters?) { - val engineInitMethod = TrustManagerFactorySpi::class.java.getDeclaredMethod("engineInit", ManagerFactoryParameters::class.java) - engineInitMethod.isAccessible = true - engineInitMethod.invoke(factorySpi, spec) + trustManagerFactory.init(spec) } } -class LoggingTrustManagerFactoryWrapper(factory: TrustManagerFactory) : TrustManagerFactory(getFactorySpi(factory), factory.provider, factory.algorithm) { - companion object { - private fun getFactorySpi(factory: TrustManagerFactory): TrustManagerFactorySpi { - val spiField = TrustManagerFactory::class.java.getDeclaredField("factorySpi") - spiField.isAccessible = true - return LoggingTrustManagerFactorySpiWrapper(spiField.get(factory) as TrustManagerFactorySpi) - } - } -} \ No newline at end of file +class LoggingTrustManagerFactoryWrapper(factory: TrustManagerFactory) : TrustManagerFactory( + LoggingTrustManagerFactorySpiWrapper(factory), + factory.provider, + factory.algorithm +) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/rpc/client/RpcClientCordaFutureSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/rpc/client/RpcClientCordaFutureSerializer.kt index f7c23472f4..e7d091fbdb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/rpc/client/RpcClientCordaFutureSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/rpc/client/RpcClientCordaFutureSerializer.kt @@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.rpc.client import net.corda.core.concurrent.CordaFuture import net.corda.core.toFuture +import net.corda.serialization.internal.NotSerializableException import net.corda.serialization.internal.amqp.CustomSerializer import net.corda.serialization.internal.amqp.SerializerFactory import rx.Observable @@ -20,9 +21,7 @@ class RpcClientCordaFutureSerializer (factory: SerializerFactory) try { return proxy.observable.toFuture() } catch (e: NotSerializableException) { - throw NotSerializableException("Failed to deserialize Future from proxy Observable - ${e.message}\n").apply { - initCause(e.cause) - } + throw NotSerializableException("Failed to deserialize Future from proxy Observable - ${e.message}\n", e.cause) } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt index b54bd8c458..56a403e3e9 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt @@ -1,13 +1,13 @@ package net.corda.nodeapi.internal.serialization.kryo -import net.corda.core.internal.declaredField import net.corda.serialization.internal.ByteBufferOutputStream import org.assertj.core.api.Assertions.catchThrowable import org.junit.Assert.assertArrayEquals import org.junit.Test -import java.io.* +import java.io.InputStream +import java.io.OutputStream import java.nio.BufferOverflowException -import java.util.* +import java.util.Random import java.util.zip.DeflaterOutputStream import java.util.zip.InflaterInputStream import kotlin.test.assertEquals @@ -67,15 +67,12 @@ class KryoStreamsTest { fun `ByteBufferOutputStream works`() { val stream = ByteBufferOutputStream(3) stream.write("abc".toByteArray()) - val getBuf = stream.declaredField(ByteArrayOutputStream::class, "buf")::value - assertEquals(3, getBuf().size) repeat(2) { assertSame(BufferOverflowException::class.java, catchThrowable { stream.alsoAsByteBuffer(9) { it.put("0123456789".toByteArray()) } }.javaClass) - assertEquals(3 + 9, getBuf().size) } // This time make too much space: stream.alsoAsByteBuffer(11) { diff --git a/node/build.gradle b/node/build.gradle index 310875b833..4759f663f9 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -280,10 +280,6 @@ tasks.register('integrationTest', Test) { testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath 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 systemProperty 'net.corda.dpcrl.connect.timeout', '4000' } @@ -292,9 +288,6 @@ tasks.register('slowIntegrationTest', Test) { testClassesDirs = sourceSets.slowIntegrationTest.output.classesDirs classpath = sourceSets.slowIntegrationTest.runtimeClasspath maxParallelForks = 1 - - jvmArgs test_add_opens - jvmArgs test_add_exports } // quasar exclusions upon agent code instrumentation at run-time @@ -332,9 +325,6 @@ quasar { jar { 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) { diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index fe62ce33d0..eadcc9bf70 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -57,15 +57,15 @@ tasks.register('buildCordaJAR', FatCapsule) { with jar 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 { applicationVersion = corda_release_version applicationId = "net.corda.node.Corda" // 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 quasarClassLoaderExclusion = "l(net.corda.djvm.**;net.corda.core.serialization.internal.**)" + 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.core.serialization.internal.**)" def quasarOptions = "m" 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' @@ -73,7 +73,6 @@ tasks.register('buildCordaJAR', FatCapsule) { // JVM configuration: // - 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. // // If you change these flags, please also update Driver.kt diff --git a/node/capsule/src/main/java/CordaCaplet.java b/node/capsule/src/main/java/CordaCaplet.java index 9078b28110..99dd004ec9 100644 --- a/node/capsule/src/main/java/CordaCaplet.java +++ b/node/capsule/src/main/java/CordaCaplet.java @@ -2,23 +2,32 @@ // 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. -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 java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.net.URL; -import java.nio.file.DirectoryStream; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; -import java.util.jar.JarInputStream; -import java.util.jar.Manifest; +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 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; public class CordaCaplet extends Capsule { @@ -47,7 +56,7 @@ public class CordaCaplet extends Capsule { File getConfigFile(List 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); + return (config == null || config.isEmpty()) ? new File(baseDir, "node.conf") : new File(config); } String getBaseDirectory(List args) { @@ -77,7 +86,7 @@ public class CordaCaplet extends Capsule { } 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); } else { 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. @Override protected int launch(ProcessBuilder pb) throws IOException, InterruptedException { - if (isAtLeastJavaVersion11()) { - List args = pb.command(); - List myArgs = Arrays.asList( - "--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); - } + List args = pb.command(); + args.addAll(1, getNodeJvmArgs()); + pb.command(args); return super.launch(pb); } + private List 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. */ @@ -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. - augmentClasspath((List) cp, new File(baseDir, "drivers")); + augmentClasspath((List) cp, Path.of(baseDir, "drivers")); try { List jarDirs = nodeConfig.getStringList("jarDirs"); log(LOG_VERBOSE, "Configured JAR directories = " + jarDirs); for (String jarDir : jarDirs) { - augmentClasspath((List) cp, new File(jarDir)); + augmentClasspath((List) cp, Path.of(jarDir)); } } catch (ConfigException.Missing e) { // 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:+CrashOnOutOfMemoryError"); } - if (isAtLeastJavaVersion11()) { - jvmArgs.add("-Dnashorn.args=--no-deprecation-warning"); - } return (T) jvmArgs; } else if (ATTR_SYSTEM_PROPERTIES == attr) { // Add system properties, if specified, from the config. @@ -193,7 +191,7 @@ public class CordaCaplet extends Capsule { try { Map overrideSystemProps = nodeConfig.getConfig("systemProperties").entrySet().stream() .map(Property::create) - .collect(toMap(Property::getKey, Property::getValue)); + .collect(toMap(Property::key, Property::value)); log(LOG_VERBOSE, "Configured system properties = " + overrideSystemProps); for (Map.Entry entry : overrideSystemProps.entrySet()) { systemProps.put(entry.getKey(), entry.getValue().toString()); @@ -207,18 +205,15 @@ public class CordaCaplet extends Capsule { } else return super.attribute(attr); } - private void augmentClasspath(List classpath, File dir) { - try { - if (dir.exists()) { - // The following might return null if the directory is not there (we check this already) or if an I/O error occurs. - for (File file : dir.listFiles()) { - addToClasspath(classpath, file); - } - } else { - log(LOG_VERBOSE, "Directory to add in Classpath was not found " + dir.getAbsolutePath()); + private void augmentClasspath(List classpath, Path dir) { + if (Files.exists(dir)) { + try (var files = Files.list(dir)) { + files.forEach((file) -> addToClasspath(classpath, file)); + } catch (IOException e) { + log(LOG_QUIET, e); } - } catch (SecurityException | NullPointerException e) { - log(LOG_QUIET, e); + } else { + log(LOG_VERBOSE, "Directory to add in Classpath was not found " + dir.toAbsolutePath()); } } @@ -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) { try { 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"); } - private void addToClasspath(List classpath, File file) { + private void addToClasspath(List classpath, Path file) { try { - if (file.canRead()) { - if (file.isFile() && isJAR(file)) { - classpath.add(file.toPath().toAbsolutePath()); - } else if (file.isDirectory()) { // Search in nested folders as well. TODO: check for circular symlinks. + if (Files.isReadable(file)) { + if (Files.isRegularFile(file) && isJAR(file)) { + classpath.add(file.toAbsolutePath()); + } else if (Files.isDirectory(file)) { // Search in nested folders as well. TODO: check for circular symlinks. augmentClasspath(classpath, file); } } 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); } } @@ -280,30 +267,14 @@ public class CordaCaplet extends Capsule { }); } - private Boolean isJAR(File file) { - return file.getName().toLowerCase().endsWith(".jar"); + private Boolean isJAR(Path file) { + return file.toString().toLowerCase().endsWith(".jar"); } /** * Helper class so that we can parse the "systemProperties" element of node.conf. */ - private static class Property { - 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; - } - + private record Property(String key, Object value) { static Property create(Map.Entry entry) { // 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. diff --git a/node/capsule/src/main/resources/node-jvm-args.txt b/node/capsule/src/main/resources/node-jvm-args.txt new file mode 100644 index 0000000000..21d6d9f829 --- /dev/null +++ b/node/capsule/src/main/resources/node-jvm-args.txt @@ -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 diff --git a/node/src/main/kotlin/net/corda/node/SerialFilter.kt b/node/src/main/kotlin/net/corda/node/SerialFilter.kt index 9998eacd7b..0cb47d52e8 100644 --- a/node/src/main/kotlin/net/corda/node/SerialFilter.kt +++ b/node/src/main/kotlin/net/corda/node/SerialFilter.kt @@ -1,53 +1,12 @@ package net.corda.node -import net.corda.core.internal.DeclaredField -import net.corda.core.internal.staticField -import net.corda.node.internal.Node -import java.lang.reflect.Method -import java.lang.reflect.Proxy +import java.io.ObjectInputFilter +import java.io.ObjectInputFilter.Status 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 - - 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("serialFilterLock").value - serialFilterField = configClass.staticField("serialFilter") - } - internal fun install(acceptClass: (Class<*>) -> Boolean) { - val filter = Proxy.newProxyInstance(javaClass.classLoader, arrayOf(filterInterface)) { _, _, args -> - val serialClass = serialClassGetter.invoke(args[0]) as Class<*>? - 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 + ObjectInputFilter.Config.setSerialFilter { filterInfo -> + if (applyPredicate(acceptClass, filterInfo.serialClass())) Status.UNDECIDED else Status.REJECTED } } diff --git a/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandle.kt b/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandle.kt index a69e197907..aa82ef45d9 100644 --- a/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandle.kt +++ b/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandle.kt @@ -36,6 +36,7 @@ import java.io.DataInputStream import java.io.DataOutputStream import java.io.IOException import java.lang.ProcessBuilder.Redirect +import java.lang.management.ManagementFactory import java.net.ServerSocket import java.net.Socket import java.nio.file.Files @@ -179,15 +180,18 @@ class ExternalVerifierHandle(private val serviceHub: ServiceHubInternal) : AutoC val fromVerifier: DataInputStream init { - val logsDirectory = (serviceHub.configuration.baseDirectory / "logs").createDirectories() - val command = listOf( - "${Path(System.getProperty("java.home"), "bin", "java")}", + val inheritedJvmArgs = ManagementFactory.getRuntimeMXBean().inputArguments.filter { "--add-opens" in it } + val command = ArrayList() + command += "${Path(System.getProperty("java.home"), "bin", "java")}" + command += inheritedJvmArgs + command += listOf( "-jar", "$verifierJar", "${server.localPort}", - System.getProperty("log4j2.level")?.lowercase() ?: "info" // TODO + System.getProperty("log4j2.level")?.lowercase() ?: "info" ) log.debug { "Verifier command: $command" } + val logsDirectory = (serviceHub.configuration.baseDirectory / "logs").createDirectories() verifierProcess = ProcessBuilder(command) .redirectOutput(Redirect.appendTo((logsDirectory / "verifier-stdout.log").toFile())) .redirectError(Redirect.appendTo((logsDirectory / "verifier-stderr.log").toFile())) diff --git a/samples/attachment-demo/build.gradle b/samples/attachment-demo/build.gradle index 0a319e45c0..e3106311e9 100644 --- a/samples/attachment-demo/build.gradle +++ b/samples/attachment-demo/build.gradle @@ -78,9 +78,6 @@ dependencies { task integrationTest(type: Test, dependsOn: []) { testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath - - jvmArgs test_add_opens - jvmArgs test_add_exports } def nodeTask = tasks.getByPath(':node:capsule:assemble') @@ -147,9 +144,6 @@ task runSender(type: JavaExec, dependsOn: jar) { classpath = sourceSets.main.runtimeClasspath main = 'net.corda.attachmentdemo.AttachmentDemoKt' - jvmArgs test_add_opens - jvmArgs test_add_exports - args '--role' args 'SENDER' } @@ -158,9 +152,6 @@ task runRecipient(type: JavaExec, dependsOn: jar) { classpath = sourceSets.main.runtimeClasspath main = 'net.corda.attachmentdemo.AttachmentDemoKt' - jvmArgs test_add_opens - jvmArgs test_add_exports - args '--role' args 'RECIPIENT' } diff --git a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt index b60e813471..b2e0072570 100644 --- a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt +++ b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt @@ -5,28 +5,23 @@ import net.corda.core.utilities.getOrThrow import net.corda.node.services.Permissions.Companion.all import net.corda.testing.core.DUMMY_BANK_A_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.driver import net.corda.testing.driver.internal.incrementalPortAllocation -import net.corda.testing.node.NotarySpec import net.corda.testing.node.User -import net.corda.testing.node.internal.DummyClusterSpec import net.corda.testing.node.internal.findCordapp import org.junit.Test import java.util.concurrent.CompletableFuture.supplyAsync 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) fun `attachment demo using a 10MB zip file`() { val numOfExpectedBytes = 10_000_000 driver(DriverParameters( portAllocation = incrementalPortAllocation(), startNodesInProcess = true, - cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows")), - notarySpecs = listOf(NotarySpec(name = DUMMY_NOTARY_NAME, cluster = DummyClusterSpec(clusterSize = 1)))) - ) { + cordappsForAllNodes = listOf(findCordapp("net.corda.attachmentdemo.contracts"), findCordapp("net.corda.attachmentdemo.workflows")) + )) { val demoUser = listOf(User("demo", "demo", setOf(all()))) val (nodeA, nodeB) = listOf( startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = demoUser, maximumHeapSize = "1g"), diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt index 2851a3c654..e8243b84c7 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt @@ -7,7 +7,7 @@ import net.corda.client.rpc.CordaRPCClient import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name 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.startTrackedFlow import net.corda.core.utilities.NetworkHostAndPort @@ -16,9 +16,14 @@ import java.io.InputStream import java.net.HttpURLConnection import java.net.URL import java.util.jar.JarInputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream import javax.servlet.http.HttpServletResponse.SC_OK import javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION 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 internal enum class Role { @@ -57,11 +62,16 @@ fun main(args: Array) { } } -/** 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 fun sender(rpc: CordaRPCOps, numOfClearBytes: Int = 1024) { // default size 1K. - val (inputStream, hash) = InputStreamAndHash.createInMemoryTestZip(numOfClearBytes, 0) - sender(rpc, inputStream, hash) + val attachmentFile = createTempFile("attachment-demo").apply { toFile().deleteOnExit() } + 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) { diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle index 4917688ab5..92da2420ef 100644 --- a/samples/bank-of-corda-demo/build.gradle +++ b/samples/bank-of-corda-demo/build.gradle @@ -117,36 +117,24 @@ tasks.register('runRPCCashIssue', JavaExec) { classpath = sourceSets.main.runtimeClasspath mainClass = 'net.corda.bank.IssueCash' - jvmArgs test_add_opens - jvmArgs test_add_exports - args '--role' args 'ISSUE_CASH_RPC' args '--quantity' args 20000 args '--currency' args 'USD' - - jvmArgs test_add_opens - jvmArgs test_add_exports } tasks.register('runWebCashIssue', JavaExec) { classpath = sourceSets.main.runtimeClasspath mainClass = 'net.corda.bank.IssueCash' - jvmArgs test_add_opens - jvmArgs test_add_exports - args '--role' args 'ISSUE_CASH_WEB' args '--quantity' args 30000 args '--currency' args 'GBP' - - jvmArgs test_add_opens - jvmArgs test_add_exports } jar { diff --git a/samples/simm-valuation-demo/build.gradle b/samples/simm-valuation-demo/build.gradle index 42099153ff..887c4de814 100644 --- a/samples/simm-valuation-demo/build.gradle +++ b/samples/simm-valuation-demo/build.gradle @@ -170,9 +170,6 @@ task deployNodes(type: net.corda.plugins.Cordform) { task integrationTest(type: Test, dependsOn: []) { testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath - - jvmArgs test_add_opens - jvmArgs test_add_exports } cordapp { diff --git a/samples/trader-demo/build.gradle b/samples/trader-demo/build.gradle index c99e19e9c1..9671e605f2 100644 --- a/samples/trader-demo/build.gradle +++ b/samples/trader-demo/build.gradle @@ -73,9 +73,6 @@ dependencies { task integrationTest(type: Test, dependsOn: []) { testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath - - jvmArgs test_add_opens - jvmArgs test_add_exports } configurations.cordaCordapp.canBeResolved = true @@ -149,16 +146,6 @@ task deployNodes(type: net.corda.plugins.Cordform) { jar { 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 { diff --git a/serialization-tests/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt b/serialization-tests/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt index fcb5f91a0f..f69276b399 100644 --- a/serialization-tests/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt +++ b/serialization-tests/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt @@ -54,7 +54,6 @@ import org.apache.qpid.proton.codec.DecoderImpl import org.apache.qpid.proton.codec.EncoderImpl import org.assertj.core.api.Assertions.assertThat 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.catchThrowable 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.whenever import java.io.IOException +import java.io.InputStream import java.io.NotSerializableException import java.math.BigDecimal 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 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 { val pub: Int @@ -340,25 +340,25 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi @Test(timeout=300_000) fun `test float`() { - val obj = testFloat(10.0F) + val obj = TestFloat(10.0F) serdes(obj) } @Test(timeout=300_000) fun `test double`() { - val obj = testDouble(10.0) + val obj = TestDouble(10.0) serdes(obj) } @Test(timeout=300_000) fun `test short`() { - val obj = testShort(1) + val obj = TestShort(1) serdes(obj) } @Test(timeout=300_000) fun `test bool`() { - val obj = testBoolean(true) + val obj = TestBoolean(true) serdes(obj) } @@ -377,7 +377,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi @Test(timeout=300_000) fun `test dislike of HashMap`() { val obj = WrapHashMap(HashMap()) - assertThatIllegalArgumentException().isThrownBy { + assertThatExceptionOfType(NotSerializableException::class.java).isThrownBy { serdes(obj) } } @@ -1303,7 +1303,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi ) factory2.register(net.corda.serialization.internal.amqp.custom.InputStreamSerializer) 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 obj3 = bytes.inputStream() // Can't use original since the stream pointer has moved. assertEquals(obj3.available(), obj2.available()) diff --git a/serialization/build.gradle b/serialization/build.gradle index 5eae716e21..757c020d90 100644 --- a/serialization/build.gradle +++ b/serialization/build.gradle @@ -57,10 +57,6 @@ artifacts { jar { archiveBaseName = 'corda-serialization' archiveClassifier = '' - - manifest { - attributes('Add-Opens': 'java.base/java.time java.base/java.io') - } } publishing { diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt index 8068cdf1a3..13774be072 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/ByteBufferStreams.kt @@ -43,14 +43,8 @@ class ByteBufferInputStream(val byteBuffer: ByteBuffer) : InputStream() { } class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) { - companion object { - private val ensureCapacity = ByteArrayOutputStream::class.java.getDeclaredMethod("ensureCapacity", Int::class.java).apply { - isAccessible = true - } - } - fun alsoAsByteBuffer(remaining: Int, task: (ByteBuffer) -> T): T { - ensureCapacity.invoke(this, count + remaining) + ensureCapacity(count + remaining) val buffer = ByteBuffer.wrap(buf, count, remaining) val result = task(buffer) count = buffer.position() @@ -60,4 +54,10 @@ class ByteBufferOutputStream(size: Int) : ByteArrayOutputStream(size) { fun copyTo(stream: OutputStream) { stream.write(buf, 0, count) } + + private fun ensureCapacity(minCapacity: Int) { + if (minCapacity > buf.size) { + buf = buf.copyOf(minCapacity) + } + } } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationUtils.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationUtils.kt new file mode 100644 index 0000000000..3e7305e136 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/SerializationUtils.kt @@ -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) } +} diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPExceptions.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPExceptions.kt index e4655d8f34..d1a8a37c93 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPExceptions.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPExceptions.kt @@ -1,19 +1,11 @@ package net.corda.serialization.internal.amqp import net.corda.core.internal.VisibleForTesting +import net.corda.serialization.internal.NotSerializableException import org.slf4j.Logger import java.io.NotSerializableException 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. * 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 ifThrowsAppend(strToAppendFn: () -> String, block: () -> T): T { try { return block() - } catch (th: Throwable) { - when (th) { - is AMQPNotSerializableException -> th.classHierarchy.add(strToAppendFn()) - // Do not overwrite the message of these exceptions as it may be used. - is ClassNotFoundException -> {} - is NoClassDefFoundError -> {} - else -> th.setMessage("${strToAppendFn()} -> ${th.message}") - } - throw th + } catch (e: AMQPNotSerializableException) { + e.classHierarchy += strToAppendFn() + throw e + } catch (e: Exception) { + // Avoid creating heavily nested NotSerializableExceptions + val cause = if (e.message?.contains(" -> ") == true) { e.cause ?: e } else { e } + throw NotSerializableException("${strToAppendFn()} -> ${e.message}", cause) } } @@ -77,8 +67,3 @@ open class AMQPNotSerializableException( 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") \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt index 6ee023d1a1..62a37410b3 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt @@ -11,6 +11,7 @@ import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace import net.corda.serialization.internal.ByteBufferInputStream import net.corda.serialization.internal.CordaSerializationEncoding +import net.corda.serialization.internal.NotSerializableException import net.corda.serialization.internal.NullEncodingWhitelist import net.corda.serialization.internal.SectionId import net.corda.serialization.internal.encodingNotPermittedFormat @@ -120,11 +121,11 @@ class DeserializationInput constructor( return generator() } catch (amqp : AMQPNotSerializableException) { amqp.log("Deserialize", logger) - throw NotSerializableException(amqp.mitigation) + throw NotSerializableException(amqp.mitigation, amqp) } catch (nse: NotSerializableException) { throw nse } 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 { objectHistory.clear() } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectBuilder.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectBuilder.kt index ffc2fae5b5..7c08c103b0 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectBuilder.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectBuilder.kt @@ -1,5 +1,6 @@ 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.LocalPropertyInformation import net.corda.serialization.internal.model.LocalTypeInformation @@ -32,17 +33,12 @@ private class ConstructorCaller(private val javaConstructor: Constructor) : try { javaConstructor.newInstance(*parameters) } catch (e: InvocationTargetException) { - @Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9) throw NotSerializableException( - "Constructor for ${javaConstructor.declaringClass} (isAccessible=${javaConstructor.isAccessible}) " + - "failed when called with parameters ${parameters.toList()}: ${e.cause!!.message}" + "Constructor for ${javaConstructor.declaringClass.name} failed when called with parameters ${parameters.asList()}: ${e.cause?.message}", + e.cause ) } catch (e: IllegalAccessException) { - @Suppress("DEPRECATION") // JDK11: isAccessible() should be replaced with canAccess() (since 9) - throw NotSerializableException( - "Constructor for ${javaConstructor.declaringClass} (isAccessible=${javaConstructor.isAccessible}) " + - "not accessible: ${e.message}" - ) + throw NotSerializableException("Constructor for ${javaConstructor.declaringClass.name} not accessible: ${e.message}") } } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/CertPathSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/CertPathSerializer.kt index b234447fb2..f7234c4c70 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/CertPathSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/CertPathSerializer.kt @@ -2,9 +2,9 @@ package net.corda.serialization.internal.amqp.custom import net.corda.core.serialization.DESERIALIZATION_CACHE_PROPERTY 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.SerializerFactory -import java.io.NotSerializableException import java.security.cert.CertPath import java.security.cert.CertificateException import java.security.cert.CertificateFactory @@ -23,9 +23,7 @@ class CertPathSerializer( val cf = CertificateFactory.getInstance(proxy.type) return cf.generateCertPath(proxy.encoded.inputStream()) } catch (ce: CertificateException) { - val nse = NotSerializableException("java.security.cert.CertPath: $type") - nse.initCause(ce) - throw nse + throw NotSerializableException("java.security.cert.CertPath: $type", ce) } } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InputStreamSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InputStreamSerializer.kt index 0e2d9ab1f8..6a9cbfcddd 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InputStreamSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InputStreamSerializer.kt @@ -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]. */ -object InputStreamSerializer - : CustomSerializer.Implements( - InputStream::class.java -) { - override val revealSubclassesInSchema: Boolean = true - +object InputStreamSerializer : CustomSerializer.Implements(InputStream::class.java) { override val schemaForDocumentation = Schema( listOf( RestrictedType( diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ZonedDateTimeSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ZonedDateTimeSerializer.kt index 04ce52a65e..ac53979a0b 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ZonedDateTimeSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ZonedDateTimeSerializer.kt @@ -2,7 +2,6 @@ package net.corda.serialization.internal.amqp.custom import net.corda.serialization.internal.amqp.CustomSerializer import net.corda.serialization.internal.amqp.SerializerFactory -import java.lang.reflect.Method import java.time.LocalDateTime import java.time.ZoneId import java.time.ZoneOffset @@ -18,21 +17,6 @@ class ZonedDateTimeSerializer( ZonedDateTimeProxy::class.java, 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> = listOf( LocalDateTimeSerializer(factory), ZoneIdSerializer(factory) @@ -40,12 +24,7 @@ class ZonedDateTimeSerializer( override fun toProxy(obj: ZonedDateTime): ZonedDateTimeProxy = ZonedDateTimeProxy(obj.toLocalDateTime(), obj.offset, obj.zone) - override fun fromProxy(proxy: ZonedDateTimeProxy): ZonedDateTime = ofLenient.invoke( - null, - proxy.dateTime, - proxy.offset, - proxy.zone - ) as ZonedDateTime + override fun fromProxy(proxy: ZonedDateTimeProxy): ZonedDateTime = ZonedDateTime.ofLocal(proxy.dateTime, proxy.zone, proxy.offset) data class ZonedDateTimeProxy(val dateTime: LocalDateTime, val offset: ZoneOffset, val zone: ZoneId) -} \ No newline at end of file +} diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt index c868193354..cb69d16be1 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/LocalTypeInformationBuilder.kt @@ -2,18 +2,25 @@ package net.corda.serialization.internal.model import net.corda.core.internal.isAbstractClass import net.corda.core.internal.isConcreteClass +import net.corda.core.internal.isJdkClass import net.corda.core.internal.kotlinObjectInstance import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization +import net.corda.core.utilities.loggerFor 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.AnArray import net.corda.serialization.internal.model.LocalTypeInformation.AnEnum import net.corda.serialization.internal.model.LocalTypeInformation.AnInterface 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.Cycle 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.Unknown import java.io.NotSerializableException +import java.lang.reflect.InaccessibleObjectException import java.lang.reflect.Method import java.lang.reflect.ParameterizedType import java.lang.reflect.Type -import kotlin.collections.LinkedHashMap import kotlin.reflect.KFunction +import kotlin.reflect.KVisibility import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.memberProperties import kotlin.reflect.full.primaryConstructor @@ -298,7 +306,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, private fun propertiesSatisfyConstructor(constructorInformation: LocalConstructorInformation, properties: Map): Boolean { if (!constructorInformation.hasParameters) return true - val indicesAddressedByProperties = properties.values.asSequence().mapNotNullTo(LinkedHashSet()) { + val indicesAddressedByProperties = properties.values.mapNotNullTo(LinkedHashSet()) { when (it) { is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex @@ -317,7 +325,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, ): List { if (!constructorInformation.hasParameters) return emptyList() - val indicesAddressedByProperties = properties.values.asSequence().mapNotNullTo(LinkedHashSet()) { + val indicesAddressedByProperties = properties.values.mapNotNullTo(LinkedHashSet()) { when (it) { is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex @@ -520,8 +528,7 @@ private fun constructorForDeserialization(type: Type): KFunction? { val defaultCtor = kotlinCtors.firstOrNull { it.parameters.isEmpty() } val nonDefaultCtors = kotlinCtors.filter { it != defaultCtor } - val preferredCandidate = clazz.kotlin.primaryConstructor ?: - when(nonDefaultCtors.size) { + val preferredCandidate = clazz.kotlin.primaryConstructor ?: when (nonDefaultCtors.size) { 1 -> nonDefaultCtors.first() 0 -> defaultCtor else -> null @@ -531,6 +538,19 @@ private fun constructorForDeserialization(type: Type): KFunction? { preferredCandidate.apply { isAccessible = true } } catch (e: SecurityException) { 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()) { + if (isTraceEnabled) { + trace("Ignoring private JDK constructor", e) + } + } + null } } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeMapTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeMapTests.kt index b7c684a046..1893271710 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeMapTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeMapTests.kt @@ -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.deserialize 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.junit.Test import java.io.NotSerializableException @@ -86,8 +86,9 @@ class DeserializeMapTests { val c = C(v) // expected to throw - Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) } - .isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Unable to serialise deprecated type class java.util.Dictionary.") + assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) } + .isInstanceOf(NotSerializableException::class.java) + .hasMessageContaining("Unable to serialise deprecated type class java.util.Dictionary.") } @Test(timeout=300_000) @@ -100,7 +101,7 @@ class DeserializeMapTests { val c = C(v) // 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") } @@ -111,7 +112,7 @@ class DeserializeMapTests { val c = C(HashMap(mapOf("A" to 1, "B" to 2))) // 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.") } @@ -121,7 +122,7 @@ class DeserializeMapTests { 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.") } diff --git a/settings.gradle b/settings.gradle index 5e16f4041c..67e26a11be 100644 --- a/settings.gradle +++ b/settings.gradle @@ -55,7 +55,6 @@ include 'client:jfx' include 'client:mock' include 'client:rpc' include 'docker' -include 'testing:client-rpc' include 'testing:testserver' include 'testing:testserver:testcapsule:' include 'experimental' diff --git a/testing/client-rpc/build.gradle b/testing/client-rpc/build.gradle deleted file mode 100644 index 6adfbaf4b5..0000000000 --- a/testing/client-rpc/build.gradle +++ /dev/null @@ -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 -} diff --git a/testing/node-driver/build.gradle b/testing/node-driver/build.gradle index e8a122fff8..7d1f909fe5 100644 --- a/testing/node-driver/build.gradle +++ b/testing/node-driver/build.gradle @@ -102,17 +102,20 @@ dependencies { compileJava { doFirst { 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) { testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath - - jvmArgs test_add_opens - jvmArgs test_add_exports } jar { diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/MockNetworkIntegrationTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/MockNetworkIntegrationTests.kt index 00ccdc6f97..e59e699c58 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/MockNetworkIntegrationTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/MockNetworkIntegrationTests.kt @@ -1,10 +1,14 @@ package net.corda.testing.node +import net.corda.core.internal.deleteRecursively import net.corda.testing.common.internal.ProjectStructure.projectRootDir 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 kotlin.io.path.Path +import kotlin.io.path.createDirectories import kotlin.io.path.div -import kotlin.test.assertEquals class MockNetworkIntegrationTests { companion object { @@ -22,16 +26,16 @@ class MockNetworkIntegrationTests { fun `does not leak non-daemon threads`() { val quasar = projectRootDir / "lib" / "quasar.jar" 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(emptyList(), - extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + moduleOpens).waitFor()) + val workingDirectory = Path("build", "MockNetworkIntegrationTests").apply { + deleteRecursively() + createDirectories() + } + val process = startJavaProcess( + emptyList(), + workingDirectory = workingDirectory, + extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs + ) + assertThat(process.waitFor()).isZero() } } diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/CordaCliWrapperErrorHandlingTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/CordaCliWrapperErrorHandlingTests.kt index 70cd653ffe..bf483cca3d 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/CordaCliWrapperErrorHandlingTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/CordaCliWrapperErrorHandlingTests.kt @@ -9,7 +9,6 @@ import java.io.BufferedReader import java.io.InputStreamReader import java.util.stream.Collectors - @RunWith(value = Parameterized::class) class CordaCliWrapperErrorHandlingTests(val arguments: List, val outputRegexPattern: String) { @@ -31,10 +30,7 @@ class CordaCliWrapperErrorHandlingTests(val arguments: List, val outputR @Test(timeout=300_000) fun `Run CordaCliWrapper sample app with arguments and check error output matches regExp`() { - val process = ProcessUtilities.startJavaProcess( - className = className, - arguments = arguments, - inheritIO = false) + val process = ProcessUtilities.startJavaProcess(className = className, arguments = arguments) process.waitFor() diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/InternalMockNetworkIntegrationTests.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/InternalMockNetworkIntegrationTests.kt index a848e68814..4d93f55d6c 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/InternalMockNetworkIntegrationTests.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/internal/InternalMockNetworkIntegrationTests.kt @@ -1,10 +1,13 @@ package net.corda.testing.node.internal +import net.corda.core.internal.deleteRecursively import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess +import org.assertj.core.api.Assertions.assertThat import org.junit.Test +import kotlin.io.path.Path +import kotlin.io.path.createDirectories import kotlin.io.path.div -import kotlin.test.assertEquals class InternalMockNetworkIntegrationTests { companion object { @@ -22,17 +25,16 @@ class InternalMockNetworkIntegrationTests { fun `does not leak non-daemon threads`() { val quasar = projectRootDir / "lib" / "quasar.jar" 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(emptyList(), - extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + moduleOpens - ).waitFor()) + val workingDirectory = Path("build", "InternalMockNetworkIntegrationTests").apply { + deleteRecursively() + createDirectories() + } + val process = startJavaProcess( + emptyList(), + workingDirectory = workingDirectory, + extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs + ) + assertThat(process.waitFor()).isZero() } } diff --git a/testing/node-driver/src/main/java/net/corda/testing/driver/SharedMemoryIncremental.java b/testing/node-driver/src/main/java/net/corda/testing/driver/SharedMemoryIncremental.java index 88e6e726d7..8c70cbea87 100644 --- a/testing/node-driver/src/main/java/net/corda/testing/driver/SharedMemoryIncremental.java +++ b/testing/node-driver/src/main/java/net/corda/testing/driver/SharedMemoryIncremental.java @@ -1,97 +1,108 @@ package net.corda.testing.driver; -import sun.misc.Unsafe; -import sun.nio.ch.DirectBuffer; +import jdk.incubator.foreign.MemoryHandles; +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.RandomAccessFile; -import java.lang.reflect.Field; +import java.io.UncheckedIOException; +import java.lang.invoke.VarHandle; import java.net.ServerSocket; +import java.nio.ByteOrder; import java.nio.MappedByteBuffer; 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; -/** - * JDK11 upgrade: rewritten in Java to gain access to private internal JDK classes via module directives (not available to Kotlin compiler): - * import sun.misc.Unsafe; - * import sun.nio.ch.DirectBuffer; - */ +import static java.nio.file.StandardOpenOption.READ; +import static java.nio.file.StandardOpenOption.WRITE; + +// 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 { + 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; - static private final int FIRST_EPHEMERAL_PORT = 30_000; + private final int startPort; + private final int endPort; - private int startPort; - private int endPort; - - 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 final MemorySegment memorySegment; + private final VarHandle intHandle; + private final MappedByteBuffer unsafeBuffer; private SharedMemoryIncremental(int startPort, int endPort) { this.startPort = startPort; this.endPort = endPort; + Path file = Path.of(System.getProperty("user.home"), "corda-" + startPort + "-to-" + endPort + "-port-allocator.bin"); try { - mb = backingFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 16); - startingAddress = ((DirectBuffer) mb).address(); + try { + 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) { - 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); - 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 public int nextPort() { - long oldValue; - long newValue; - boolean loopSuccess; - do { - oldValue = UNSAFE.getLongVolatile(null, startingAddress); + while (true) { + int oldValue; + if (intHandle != null) { + oldValue = (int) intHandle.getVolatile(memorySegment, 0L); + } else { + oldValue = unsafeBuffer.getInt(0); + } + int newValue; if (oldValue + 1 >= endPort || oldValue < startPort) { newValue = startPort; } else { newValue = (oldValue + 1); } - boolean reserveSuccess = UNSAFE.compareAndSwapLong(null, startingAddress, oldValue, newValue); - loopSuccess = reserveSuccess && isLocalPortAvailable(newValue); - } while (!loopSuccess); - - return (int) newValue; + if (intHandle != null) { + if (!intHandle.compareAndSet(memorySegment, 0L, oldValue, newValue)) { + continue; + } + } else { + unsafeBuffer.putInt(0, newValue); + } + if (isLocalPortAvailable(newValue)) { + return newValue; + } + } } - - private boolean isLocalPortAvailable(Long portToTest) { - try (ServerSocket serverSocket = new ServerSocket(Math.toIntExact(portToTest))) { + private boolean isLocalPortAvailable(int portToTest) { + try (ServerSocket ignored = new ServerSocket(portToTest)) { + return true; } catch (IOException e) { // Don't catch anything other than IOException here in case we // accidentally create an infinite loop. For example, installing // a SecurityManager could throw AccessControlException. return false; } - return true; } - } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index ecf26a5211..b29d633680 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -49,6 +49,7 @@ import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.loggerFor import net.corda.coretesting.internal.DEV_ROOT_CA import net.corda.node.VersionInfo 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.configureDatabase 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.MockKeyManagementService 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.services.MockAttachmentStorage import java.io.ByteArrayOutputStream +import java.nio.file.FileAlreadyExistsException import java.nio.file.Paths import java.security.KeyPair import java.sql.Connection @@ -141,8 +142,8 @@ open class MockServices private constructor( val dbPath = dbDir.resolve("persistence") try { DatabaseSnapshot.copyDatabaseSnapshot(dbDir) - } catch (ex: java.nio.file.FileAlreadyExistsException) { - DriverDSLImpl.log.warn("Database already exists on disk, not attempting to pre-migrate database.") + } catch (e: FileAlreadyExistsException) { + loggerFor().warn("Database already exists on disk, not attempting to pre-migrate database.") } val props = Properties() props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource") diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 608d6293f2..6b982b920c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -45,6 +45,7 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.contextLogger import net.corda.core.utilities.debug import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.loggerFor import net.corda.core.utilities.millis import net.corda.core.utilities.toHexString import net.corda.coretesting.internal.stubs.CertificateStoreStubs @@ -845,7 +846,7 @@ class DriverDSLImpl( companion object { 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 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.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**;)" - val excludeClassloaderPattern = "l(net.corda.djvm.**;net.corda.core.serialization.internal.**)" + val excludeClassloaderPattern = "l(net.corda.core.serialization.internal.**)" val quasarOptions = "m" val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } + "-javaagent:$quasarJarPath=$quasarOptions$excludePackagePattern$excludeClassloaderPattern" @@ -1002,24 +1003,11 @@ class DriverDSLImpl( && !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( className = "net.corda.node.Corda", // cannot directly get class for this, so just use string arguments = arguments, 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, maximumHeapSize = maximumHeapSize, classPath = cp, @@ -1066,22 +1054,13 @@ class DriverDSLImpl( } 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()) 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()), jdwpPort = debugPort, - extraJvmArguments = listOf("-Dname=node-${handle.p2pAddress}-webserver") + moduleOpens + + extraJvmArguments = listOf("-Dname=node-${handle.p2pAddress}-webserver") + inheritFromParentProcess().map { "-D${it.first}=${it.second}" }, maximumHeapSize = maximumHeapSize ) @@ -1101,12 +1080,11 @@ class DriverDSLImpl( } private fun NodeHandleInternal.toWebServerConfig(): Config { - var config = ConfigFactory.empty() config += "webAddress" to webAddress.toString() config += "myLegalName" to configuration.myLegalName.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 += "baseDirectory" to configuration.baseDirectory.toAbsolutePath().toString() @@ -1276,7 +1254,7 @@ fun genericDriver( driverDsl.start() return dsl(coerce(driverDsl)) } catch (exception: Throwable) { - DriverDSLImpl.log.error("Driver shutting down because of exception", exception) + loggerFor().error("Driver shutting down because of exception", exception) throw exception } finally { driverDsl.shutdown() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt index a7ea805442..82c6f2d685 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt @@ -301,6 +301,10 @@ fun DriverDSL.assertUncompletedCheckpoints(name: CordaX500Name, expected: Long) } } +val nodeJvmArgs: List by lazy { + DriverDSLImpl::class.java.getResourceAsStream("node-jvm-args.txt")!!.use { it.bufferedReader().readLines() } +} + /** * Should only be used by Driver and MockNode. */ diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt index f2acdc688e..00fea1ce6c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt @@ -39,7 +39,6 @@ object ProcessUtilities { maximumHeapSize: String? = null, identifier: String = "", environmentVariables: Map = emptyMap(), - inheritIO: Boolean = true ): Process { val command = mutableListOf().apply { add(javaPath) @@ -50,7 +49,6 @@ object ProcessUtilities { addAll(arguments) } return ProcessBuilder(command).apply { - if (inheritIO) inheritIO() environment().putAll(environmentVariables) environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator) if (workingDirectory != null) { diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index a5d3f373ad..216db2e120 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -148,24 +148,25 @@ class NodeProcess( 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)) { process.destroy() throw SchemaCreationTimedOutError(nodeDir) } - if (process.exitValue() != 0){ + if (process.exitValue() != 0) { throw SchemaCreationFailedError(nodeDir) } } - @Suppress("SpreadOperator") - private fun startNode(nodeDir: Path, extraArgs: Array = emptyArray()): Process { + private fun startNode(nodeDir: Path, vararg extraArgs: String): Process { + val command = arrayListOf(javaPath.toString(), "-Dcapsule.log=verbose", "-jar", cordaJar.toString()) + command += extraArgs + val now = formatter.format(Instant.now()) val builder = ProcessBuilder() - .command(javaPath.toString(), "-Dcapsule.log=verbose", "-jar", cordaJar.toString(), *extraArgs) + .command(command) .directory(nodeDir.toFile()) - .redirectError(ProcessBuilder.Redirect.INHERIT) - .redirectOutput(ProcessBuilder.Redirect.INHERIT) - + .redirectError((nodeDir / "$now-stderr.log").toFile()) + .redirectOutput((nodeDir / "$now-stdout.log").toFile()) builder.environment().putAll(mapOf( "CAPSULE_CACHE_DIR" to (buildDirectory / "capsule").toString() )) diff --git a/testing/testserver/build.gradle b/testing/testserver/build.gradle index 373b43e41d..dad5d1832c 100644 --- a/testing/testserver/build.gradle +++ b/testing/testserver/build.gradle @@ -80,9 +80,6 @@ dependencies { tasks.register('integrationTest', Test) { testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath - - jvmArgs test_add_opens - jvmArgs test_add_exports } jar { diff --git a/testing/testserver/src/main/java/CordaWebserverCaplet.java b/testing/testserver/src/main/java/CordaWebserverCaplet.java index 2260946e9b..69bef1e916 100644 --- a/testing/testserver/src/main/java/CordaWebserverCaplet.java +++ b/testing/testserver/src/main/java/CordaWebserverCaplet.java @@ -2,14 +2,22 @@ // 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. -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 java.io.File; -import java.io.IOException; import java.nio.file.Path; 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; public class CordaWebserverCaplet extends Capsule { @@ -37,11 +45,6 @@ public class CordaWebserverCaplet extends Capsule { } } - File getConfigFile(List 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 args) { String baseDir = getOptionMultiple(args, Arrays.asList("--base-directory", "-b")); 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.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); } else { return null; @@ -87,23 +90,6 @@ public class CordaWebserverCaplet extends Capsule { 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 args = pb.command(); - List 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. @Override protected String getVarValue(String var) { @@ -157,9 +143,6 @@ public class CordaWebserverCaplet extends Capsule { } catch (ConfigException e) { log(LOG_QUIET, e); } - if (isAtLeastJavaVersion11()) { - jvmArgs.add("-Dnashorn.args=--no-deprecation-warning"); - } return (T) jvmArgs; } else if (ATTR_SYSTEM_PROPERTIES == attr) { // 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) { try { if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case. diff --git a/testing/testserver/testcapsule/build.gradle b/testing/testserver/testcapsule/build.gradle index f0c678fdf0..d97e8446d1 100644 --- a/testing/testserver/testcapsule/build.gradle +++ b/testing/testserver/testcapsule/build.gradle @@ -55,10 +55,6 @@ tasks.register('buildWebserverJar', FatCapsule) { // If you change these flags, please also update Driver.kt jvmArgs = ['-Xmx200m'] } - - manifest { - attributes('Add-Opens': 'java.management/com.sun.jmx.mbeanserver java.base/java.lang') - } } artifacts { diff --git a/verifier/build.gradle b/verifier/build.gradle index b583b39ece..b34f2de21f 100644 --- a/verifier/build.gradle +++ b/verifier/build.gradle @@ -16,20 +16,3 @@ dependencies { 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" - ) - } -}