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

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

View File

@ -121,23 +121,6 @@ buildscript {
ext.fontawesomefx_commons_version = constants.getProperty("fontawesomefxCommonsVersion")
ext.fontawesomefx_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()
@ -332,13 +310,12 @@ allprojects {
}
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'

View File

@ -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 {

View File

@ -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<StandaloneCashRpcClient>(
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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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'

View File

@ -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 <T, R : Any> Stream<T>.mapNotNull(crossinline transform: (T) -> R?):
/** Similar to [Collectors.toSet] except the Set is guaranteed to be ordered. */
fun <T> Stream<T>.toSet(): Set<T> = collect(toCollection { LinkedHashSet<T>() })
val Class<*>.isJdkClass: Boolean get() = module.name?.startsWith("java.") == true
fun <T> Class<T>.castIfPossible(obj: Any): T? = if (isInstance(obj)) cast(obj) else null
/** Returns a [DeclaredField] wrapper around the declared (possibly non-public) static field of the receiver [Class]. */

View File

@ -88,7 +88,7 @@ inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption =
inline fun <reified T : Any> Path.readObject(): T = readBytes().deserialize()
/** 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

View File

@ -93,26 +93,16 @@ class AttachmentsClassLoader(attachments: List<Attachment>,
* or use a decorator and reflection to bypass the single-call-per-JVM restriction otherwise.
*/
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<Attachment>,
URL.setURLStreamHandlerFactory { protocol ->
// route between our own and the pre-existing factory
AttachmentURLStreamHandlerFactory.createURLStreamHandler(protocol)
?: existingFactory.createURLStreamHandler(protocol)
?: existingFactory?.createURLStreamHandler(protocol)
}
}
}

View File

@ -2,7 +2,6 @@ package net.corda.core.utilities
import net.corda.core.contracts.StateRef
import net.corda.core.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<ByteArray>(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))
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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 <T : Enum<T>> enumBridge(clazz: Class<T>, 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<String, Any> {
val values = HashMap<String, Any>()
private fun Any.toConfigMap(): Map<String, Any?> {
val values = LinkedHashMap<String, Any?>()
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<Any?> {
val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
return when (elementType) {
// For the types already supported by Config we can use the Iterable as is
String::class.java -> this
Integer::class.java -> this
java.lang.Long::class.java -> this
java.lang.Double::class.java -> this
java.lang.Boolean::class.java -> this
LocalDate::class.java -> map(Any?::toString)
Instant::class.java -> map(Any?::toString)
NetworkHostAndPort::class.java -> map(Any?::toString)
Path::class.java -> map(Any?::toString)
URL::class.java -> map(Any?::toString)
X500Principal::class.java -> map(Any?::toString)
UUID::class.java -> map(Any?::toString)
CordaX500Name::class.java -> map(Any?::toString)
Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() }
else -> if (elementType.isEnum) {
map { (it as Enum<*>).name }
} else {
map { it?.toConfigMap() }
}
}
}
private fun Iterable<*>.toConfigIterable(): Iterable<Any?> = map { element -> element?.let(::convertValue) }
// The typesafe .getBoolean function is case sensitive, this is a case insensitive version
fun Config.getBooleanCaseInsensitive(path: String): Boolean {
try {
return getBoolean(path)
} catch (e: Exception) {
val stringVal = getString(path).toLowerCase()
val stringVal = getString(path).lowercase()
if (stringVal == "true" || stringVal == "false") {
return stringVal.toBoolean()
}

View File

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

View File

@ -9,25 +9,18 @@ import javax.net.ssl.ManagerFactoryParameters
import javax.net.ssl.X509ExtendedKeyManager
import javax.net.ssl.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<KeyManager> {
val engineGetKeyManagersMethod = KeyManagerFactorySpi::class.java.getDeclaredMethod("engineGetKeyManagers")
engineGetKeyManagersMethod.isAccessible = true
@Suppress("UNCHECKED_CAST")
val keyManagers = engineGetKeyManagersMethod.invoke(factorySpi) as Array<KeyManager>
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<out X509Certificate>? {
val keyManager = keyManagers.firstOrNull()
val alias = if (keyManager is AliasProvidingKeyMangerWrapper) keyManager.lastAlias else null

View File

@ -7,34 +7,24 @@ import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.TrustManagerFactorySpi
import javax.net.ssl.X509ExtendedTrustManager
class LoggingTrustManagerFactorySpiWrapper(private val factorySpi: TrustManagerFactorySpi) : TrustManagerFactorySpi() {
class LoggingTrustManagerFactorySpiWrapper(private val trustManagerFactory: TrustManagerFactory) : TrustManagerFactorySpi() {
override fun engineGetTrustManagers(): Array<TrustManager> {
val engineGetTrustManagersMethod = TrustManagerFactorySpi::class.java.getDeclaredMethod("engineGetTrustManagers")
engineGetTrustManagersMethod.isAccessible = true
@Suppress("UNCHECKED_CAST")
val trustManagers = engineGetTrustManagersMethod.invoke(factorySpi) as Array<TrustManager>
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)
}
}
}
class LoggingTrustManagerFactoryWrapper(factory: TrustManagerFactory) : TrustManagerFactory(
LoggingTrustManagerFactorySpiWrapper(factory),
factory.provider,
factory.algorithm
)

View File

@ -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)
}
}

View File

@ -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<ByteArray>(ByteArrayOutputStream::class, "buf")::value
assertEquals(3, getBuf().size)
repeat(2) {
assertSame<Any>(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) {

View File

@ -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) {

View File

@ -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

View File

@ -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<String> args, String baseDir) {
String config = getOptionMultiple(args, Arrays.asList("--config-file", "-f"));
return (config == null || config.equals("")) ? new File(baseDir, "node.conf") : new File(config);
return (config == null || config.isEmpty()) ? new File(baseDir, "node.conf") : new File(config);
}
String getBaseDirectory(List<String> 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<String> args = pb.command();
List<String> 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);
args.addAll(1, getNodeJvmArgs());
pb.command(args);
}
return super.launch(pb);
}
private List<String> getNodeJvmArgs() throws IOException {
try (InputStream resource = requireNonNull(getClass().getResourceAsStream("/node-jvm-args.txt"))) {
return new BufferedReader(new InputStreamReader(resource)).lines().collect(toList());
}
}
/**
* Overriding the Caplet classpath generation via the intended interface in Capsule.
*/
@ -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<Path>) cp, new File(baseDir, "drivers"));
augmentClasspath((List<Path>) cp, Path.of(baseDir, "drivers"));
try {
List<String> jarDirs = nodeConfig.getStringList("jarDirs");
log(LOG_VERBOSE, "Configured JAR directories = " + jarDirs);
for (String jarDir : jarDirs) {
augmentClasspath((List<Path>) cp, new File(jarDir));
augmentClasspath((List<Path>) 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<String, ?> 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<String, ?> 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<Path> 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);
private void augmentClasspath(List<Path> 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);
}
} else {
log(LOG_VERBOSE, "Directory to add in Classpath was not found " + dir.getAbsolutePath());
}
} catch (SecurityException | NullPointerException e) {
log(LOG_QUIET, e);
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<Path> classpath, File file) {
private void addToClasspath(List<Path> 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<String, ConfigValue> 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.

View File

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

View File

@ -1,53 +1,12 @@
package net.corda.node
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<Any>
init {
// ObjectInputFilter and friends are in java.io in Java 9 but sun.misc in backports:
fun getFilterInterface(packageName: String): Class<*>? {
return try {
Class.forName("$packageName.ObjectInputFilter")
} catch (e: ClassNotFoundException) {
null
}
}
// JDK 8u121 is the earliest JDK8 JVM that supports this functionality.
filterInterface = getFilterInterface("java.io")
?: getFilterInterface("sun.misc")
?: Node.failStartUp("Corda forbids Java deserialisation. Please upgrade to at least JDK 8u121.")
serialClassGetter = Class.forName("${filterInterface.name}\$FilterInfo").getMethod("serialClass")
val statusEnum = Class.forName("${filterInterface.name}\$Status")
undecided = statusEnum.getField("UNDECIDED").get(null)
rejected = statusEnum.getField("REJECTED").get(null)
val configClass = Class.forName("${filterInterface.name}\$Config")
serialFilterLock = configClass.staticField<Any>("serialFilterLock").value
serialFilterField = configClass.staticField("serialFilter")
}
internal fun install(acceptClass: (Class<*>) -> Boolean) {
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
}
}

View File

@ -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<String>()
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()))

View File

@ -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'
}

View File

@ -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"),

View File

@ -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<String>) {
}
}
/** An in memory test zip attachment of at least numOfClearBytes size, will be used. */
/** A temp zip file attachment of at least numOfClearBytes size, will be used. */
// DOCSTART 2
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) {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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())

View File

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

View File

@ -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 <T> 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)
}
}
}

View File

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

View File

@ -1,19 +1,11 @@
package net.corda.serialization.internal.amqp
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 <T> 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")

View File

@ -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()
}

View File

@ -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<Any>) :
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}")
}
}

View File

@ -2,9 +2,9 @@ package net.corda.serialization.internal.amqp.custom
import net.corda.core.serialization.DESERIALIZATION_CACHE_PROPERTY
import net.corda.core.serialization.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)
}
}

View File

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

View File

@ -2,7 +2,6 @@ package net.corda.serialization.internal.amqp.custom
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.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<CustomSerializer<out Any>> = 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)
}

View File

@ -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<PropertyName, LocalPropertyInformation>): 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<LocalConstructorParameterInformation> {
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<Any>? {
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<Any>? {
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<LocalTypeInformationBuilder>()) {
if (isTraceEnabled) {
trace("Ignoring private JDK constructor", e)
}
}
null
}
}

View File

@ -3,7 +3,7 @@ package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.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.")
}

View File

@ -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'

View File

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

View File

@ -102,17 +102,20 @@ dependencies {
compileJava {
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 {

View File

@ -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<MockNetworkIntegrationTests>(emptyList(),
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + moduleOpens).waitFor())
val workingDirectory = Path("build", "MockNetworkIntegrationTests").apply {
deleteRecursively()
createDirectories()
}
val process = startJavaProcess<MockNetworkIntegrationTests>(
emptyList(),
workingDirectory = workingDirectory,
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs
)
assertThat(process.waitFor()).isZero()
}
}

View File

@ -9,7 +9,6 @@ import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.stream.Collectors
@RunWith(value = Parameterized::class)
class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputRegexPattern: String) {
@ -31,10 +30,7 @@ class CordaCliWrapperErrorHandlingTests(val arguments: List<String>, val outputR
@Test(timeout=300_000)
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()

View File

@ -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<InternalMockNetworkIntegrationTests>(emptyList(),
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + moduleOpens
).waitFor())
val workingDirectory = Path("build", "InternalMockNetworkIntegrationTests").apply {
deleteRecursively()
createDirectories()
}
val process = startJavaProcess<InternalMockNetworkIntegrationTests>(
emptyList(),
workingDirectory = workingDirectory,
extraJvmArguments = listOf("-javaagent:$quasar=$quasarOptions") + nodeJvmArgs
)
assertThat(process.waitFor()).isZero()
}
}

View File

@ -1,97 +1,108 @@
package net.corda.testing.driver;
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;
}
}

View File

@ -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<MockServices>().warn("Database already exists on disk, not attempting to pre-migrate database.")
}
val props = Properties()
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")

View File

@ -45,6 +45,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.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 <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
driverDsl.start()
return dsl(coerce(driverDsl))
} catch (exception: Throwable) {
DriverDSLImpl.log.error("Driver shutting down because of exception", exception)
loggerFor<DriverDSL>().error("Driver shutting down because of exception", exception)
throw exception
} finally {
driverDsl.shutdown()

View File

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

View File

@ -39,7 +39,6 @@ object ProcessUtilities {
maximumHeapSize: String? = null,
identifier: String = "",
environmentVariables: Map<String,String> = emptyMap(),
inheritIO: Boolean = true
): Process {
val command = mutableListOf<String>().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) {

View File

@ -148,7 +148,7 @@ 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)
@ -158,14 +158,15 @@ class NodeProcess(
}
}
@Suppress("SpreadOperator")
private fun startNode(nodeDir: Path, extraArgs: Array<String> = 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()
))

View File

@ -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 {

View File

@ -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<String> args, String baseDir) {
String config = getOptionMultiple(args, Arrays.asList("--config-file", "-f"));
return (config == null || config.equals("")) ? new File(baseDir, "node.conf") : new File(config);
}
String getBaseDirectory(List<String> args) {
String 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<String> args = pb.command();
List<String> myArgs = Arrays.asList(
"--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.time=ALL-UNNAMED",
"--add-opens=java.base/java.io=ALL-UNNAMED",
"--add-opens=java.base/java.nio=ALL-UNNAMED");
args.addAll(1, myArgs);
pb.command(args);
}
return super.launch(pb);
}
// Add working directory variable to capsules string replacement variables.
@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.

View File

@ -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 {

View File

@ -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"
)
}
}