diff --git a/build.gradle b/build.gradle index 129c022239..6a028e30d6 100644 --- a/build.gradle +++ b/build.gradle @@ -369,6 +369,7 @@ bintrayConfig { 'corda-rpc', 'corda-core', 'corda-core-deterministic', + 'corda-djvm', 'corda', 'corda-finance', 'corda-node', @@ -477,6 +478,6 @@ if (file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BU } wrapper { - gradleVersion = "4.8.1" + gradleVersion = "4.10" distributionType = Wrapper.DistributionType.ALL } diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt index b370fd5321..b98b79bf5a 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt @@ -12,6 +12,7 @@ import net.corda.core.utilities.days import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport +import net.corda.nodeapi.internal.PLATFORM_VERSION import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT import java.time.Duration @@ -45,7 +46,7 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor( * The default value is whatever version of Corda this RPC library was shipped as a part of. Therefore if you * use the RPC library from Corda 4, it will by default only connect to a node of version 4 or above. */ - open val minimumServerProtocolVersion: Int = 4, + open val minimumServerProtocolVersion: Int = PLATFORM_VERSION, /** * If set to true the client will track RPC call sites (default is false). If an error occurs subsequently diff --git a/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/Enclavelet.kt b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/Enclavelet.kt new file mode 100644 index 0000000000..720a2bc4ff --- /dev/null +++ b/core-deterministic/testing/common/src/main/kotlin/net/corda/deterministic/common/Enclavelet.kt @@ -0,0 +1,21 @@ +@file:JvmName("Enclavelet") +package net.corda.deterministic.common + +import net.corda.core.serialization.deserialize +import net.corda.core.transactions.LedgerTransaction + +/** + * We assume the signatures were already checked outside the sandbox: the purpose of this code + * is simply to check the sensitive, app-specific parts of a transaction. + * + * TODO: Transaction data is meant to be encrypted under an enclave-private key. + */ +@Throws(Exception::class) +fun verifyInEnclave(reqBytes: ByteArray) { + deserialize(reqBytes).verify() +} + +private fun deserialize(reqBytes: ByteArray): LedgerTransaction { + return reqBytes.deserialize() + .toLedgerTransaction() +} diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/EnclaveletTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/EnclaveletTest.kt index 88125a4072..84bab1d1b7 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/EnclaveletTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/txverify/EnclaveletTest.kt @@ -1,11 +1,8 @@ -@file:JvmName("Enclavelet") package net.corda.deterministic.txverify -import net.corda.core.serialization.deserialize -import net.corda.core.transactions.LedgerTransaction import net.corda.deterministic.bytesOfResource import net.corda.deterministic.common.LocalSerializationRule -import net.corda.deterministic.common.TransactionVerificationRequest +import net.corda.deterministic.common.verifyInEnclave import net.corda.finance.contracts.asset.Cash.Commands.* import org.assertj.core.api.Assertions.assertThat import org.junit.ClassRule @@ -30,23 +27,3 @@ class EnclaveletTest { assertThat(e).hasMessageContaining("Required ${Move::class.java.canonicalName} command") } } - -/** - * Returns either null to indicate success when the transactions are validated, or a string with the - * contents of the error. Invoked via JNI in response to an enclave RPC. The argument is a serialised - * [TransactionVerificationRequest]. - * - * Note that it is assumed the signatures were already checked outside the sandbox: the purpose of this code - * is simply to check the sensitive, app specific parts of a transaction. - * - * TODO: Transaction data is meant to be encrypted under an enclave-private key. - */ -@Throws(Exception::class) -private fun verifyInEnclave(reqBytes: ByteArray) { - deserialize(reqBytes).verify() -} - -private fun deserialize(reqBytes: ByteArray): LedgerTransaction { - return reqBytes.deserialize() - .toLedgerTransaction() -} diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index 29d261cb92..5716f2cb53 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -76,7 +76,8 @@ sealed class StateMachineUpdate { // DOCSTART 1 /** * Data class containing information about the scheduled network parameters update. The info is emitted every time node - * receives network map with [ParametersUpdate] which wasn't seen before. For more information see: [CordaRPCOps.networkParametersFeed] and [CordaRPCOps.acceptNewNetworkParameters]. + * receives network map with [ParametersUpdate] which wasn't seen before. For more information see: [CordaRPCOps.networkParametersFeed] + * and [CordaRPCOps.acceptNewNetworkParameters]. * @property hash new [NetworkParameters] hash * @property parameters new [NetworkParameters] data structure * @property description description of the update @@ -233,6 +234,9 @@ interface CordaRPCOps : RPCOps { @RPCReturnsObservables fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange> + /** Returns the network parameters the node is operating under. */ + val networkParameters: NetworkParameters + /** * Returns [DataFeed] object containing information on currently scheduled parameters update (null if none are currently scheduled) * and observable with future update events. Any update that occurs before the deadline automatically cancels the current one. @@ -434,7 +438,7 @@ fun CordaRPCOps.pendingFlowsCount(): DataFeed> { } } }.subscribe() - if (completedFlowsCount == 0) { + if (pendingFlowsCount == 0) { updates.onCompleted() } return DataFeed(pendingFlowsCount, updates) diff --git a/djvm/build.gradle b/djvm/build.gradle index 270bfc2375..b3447b63ac 100644 --- a/djvm/build.gradle +++ b/djvm/build.gradle @@ -1,18 +1,24 @@ plugins { id 'com.github.johnrengelman.shadow' version '2.0.4' } +apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'com.jfrog.artifactory' + +description 'Corda deterministic JVM sandbox' ext { // Shaded version of ASM to avoid conflict with root project. asm_version = '6.1.1' } +configurations { + testCompile.extendsFrom shadow +} + dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - compile "org.slf4j:jul-to-slf4j:$slf4j_version" - compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" - compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" + shadow "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + shadow "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + shadow "org.slf4j:slf4j-api:$slf4j_version" // ASM: byte code manipulation library compile "org.ow2.asm:asm:$asm_version" @@ -20,30 +26,29 @@ dependencies { compile "org.ow2.asm:asm-commons:$asm_version" // Classpath scanner - compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" + shadow "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" // Test utilities testCompile "junit:junit:$junit_version" testCompile "org.assertj:assertj-core:$assertj_version" + testCompile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" } jar.enabled = false shadowJar { - baseName = "djvm" - classifier = "" - dependencies { - exclude(dependency('com.jcabi:.*:.*')) - exclude(dependency('org.apache.*:.*:.*')) - exclude(dependency('org.jetbrains.*:.*:.*')) - exclude(dependency('org.slf4j:.*:.*')) - exclude(dependency('io.github.lukehutch:.*:.*')) - } + baseName 'corda-djvm' + classifier '' relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm' - artifacts { - shadow(tasks.shadowJar.archivePath) { - builtBy shadowJar - } - } } assemble.dependsOn shadowJar + +artifacts { + publish shadowJar +} + +publish { + dependenciesFrom configurations.shadow + disableDefaultJar true + name shadowJar.baseName +} diff --git a/djvm/cli/build.gradle b/djvm/cli/build.gradle index a3543bcc54..d72a4a74c0 100644 --- a/djvm/cli/build.gradle +++ b/djvm/cli/build.gradle @@ -15,12 +15,10 @@ configurations { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - compile "org.slf4j:jul-to-slf4j:$slf4j_version" compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" compile "info.picocli:picocli:$picocli_version" - compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" compile project(path: ":djvm", configuration: "shadow") // Deterministic runtime - used in whitelist generation diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/BuildCommand.kt b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/BuildCommand.kt index 298ebcb1ce..7fafd5d743 100644 --- a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/BuildCommand.kt +++ b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/BuildCommand.kt @@ -1,8 +1,5 @@ package net.corda.djvm.tools.cli -import net.corda.djvm.tools.Utilities.createCodePath -import net.corda.djvm.tools.Utilities.getFileNames -import net.corda.djvm.tools.Utilities.jarPath import picocli.CommandLine.Command import picocli.CommandLine.Parameters import java.nio.file.Path diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ClassCommand.kt b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ClassCommand.kt index e3538535d0..8f5f1cb911 100644 --- a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ClassCommand.kt +++ b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/ClassCommand.kt @@ -7,9 +7,6 @@ import net.corda.djvm.execution.* import net.corda.djvm.references.ClassModule import net.corda.djvm.source.ClassSource import net.corda.djvm.source.SourceClassLoader -import net.corda.djvm.tools.Utilities.find -import net.corda.djvm.tools.Utilities.onEmpty -import net.corda.djvm.tools.Utilities.userClassPath import net.corda.djvm.utilities.Discovery import djvm.org.objectweb.asm.ClassReader import picocli.CommandLine.Option diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/InspectionCommand.kt b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/InspectionCommand.kt index f6d779ceb2..32ce08ec6e 100644 --- a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/InspectionCommand.kt +++ b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/InspectionCommand.kt @@ -1,7 +1,6 @@ package net.corda.djvm.tools.cli import net.corda.djvm.source.ClassSource -import net.corda.djvm.tools.Utilities.createCodePath import picocli.CommandLine.Command import picocli.CommandLine.Parameters import java.nio.file.Files diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/NewCommand.kt b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/NewCommand.kt index e2df2f4d0f..8e6302de4e 100644 --- a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/NewCommand.kt +++ b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/NewCommand.kt @@ -1,9 +1,5 @@ package net.corda.djvm.tools.cli -import net.corda.djvm.tools.Utilities.baseName -import net.corda.djvm.tools.Utilities.createCodePath -import net.corda.djvm.tools.Utilities.getFiles -import net.corda.djvm.tools.Utilities.openOptions import picocli.CommandLine.* import java.nio.file.Files import java.nio.file.Path diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/TreeCommand.kt b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/TreeCommand.kt index 47b24ffa44..26b5dbc7d8 100644 --- a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/TreeCommand.kt +++ b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/TreeCommand.kt @@ -1,6 +1,5 @@ package net.corda.djvm.tools.cli -import net.corda.djvm.tools.Utilities.workingDirectory import picocli.CommandLine.Command import java.nio.file.Files diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt new file mode 100644 index 0000000000..66d5d4d918 --- /dev/null +++ b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt @@ -0,0 +1,104 @@ +@file:JvmName("Utilities") +package net.corda.djvm.tools.cli + +import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner +import java.lang.reflect.Modifier +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.StandardOpenOption + +/** + * Get the expanded file name of each path in the provided array. + */ +fun Array?.getFiles(map: (Path) -> Path = { it }) = (this ?: emptyArray()).map { + val pathString = it.toString() + val path = map(it) + when { + '/' in pathString || '\\' in pathString -> + throw Exception("Please provide a pathless file name") + pathString.endsWith(".java", true) -> path + else -> Paths.get("$path.java") + } +} + +/** + * Get the string representation of each expanded file name in the provided array. + */ +fun Array?.getFileNames(map: (Path) -> Path = { it }) = this.getFiles(map).map { + it.toString() +}.toTypedArray() + +/** + * Execute inlined action if the collection is empty. + */ +inline fun List.onEmpty(action: () -> Unit): List { + if (!this.any()) { + action() + } + return this +} + +/** + * Execute inlined action if the array is empty. + */ +inline fun Array?.onEmpty(action: () -> Unit): Array { + return (this ?: emptyArray()).toList().onEmpty(action).toTypedArray() +} + +/** + * Derive the set of [StandardOpenOption]'s to use for a file operation. + */ +fun openOptions(force: Boolean) = if (force) { + arrayOf(StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING) +} else { + arrayOf(StandardOpenOption.CREATE_NEW) +} + +/** + * Get the path of where any generated code will be placed. Create the directory if it does not exist. + */ +fun createCodePath(): Path { + return Paths.get("tmp", "net", "corda", "djvm").let { + Files.createDirectories(it) + } +} + +/** + * Return the base name of a file (i.e., its name without extension) + */ +val Path.baseName: String + get() = this.fileName.toString() + .replaceAfterLast('.', "") + .removeSuffix(".") + +/** + * The path of the executing JAR. + */ +val jarPath: String = object {}.javaClass.protectionDomain.codeSource.location.toURI().path + + +/** + * The path of the current working directory. + */ +val workingDirectory: Path = Paths.get(System.getProperty("user.dir")) + +/** + * The class path for the current execution context. + */ +val userClassPath: String = System.getProperty("java.class.path") + +/** + * Get a reference of each concrete class that implements interface or class [T]. + */ +inline fun find(scanSpec: String = "net/corda/djvm"): List> { + val references = mutableListOf>() + FastClasspathScanner(scanSpec) + .matchClassesImplementing(T::class.java) { clazz -> + if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) { + references.add(clazz) + } + } + .scan() + return references +} diff --git a/djvm/src/main/resources/log4j2.xml b/djvm/cli/src/main/resources/log4j2.xml similarity index 100% rename from djvm/src/main/resources/log4j2.xml rename to djvm/cli/src/main/resources/log4j2.xml diff --git a/djvm/src/main/kotlin/net/corda/djvm/code/Emitter.kt b/djvm/src/main/kotlin/net/corda/djvm/code/Emitter.kt index 7d953a28e1..f904d276b7 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/code/Emitter.kt +++ b/djvm/src/main/kotlin/net/corda/djvm/code/Emitter.kt @@ -20,6 +20,7 @@ interface Emitter { /** * Indication of whether or not the emitter performs instrumentation for tracing inside the sandbox. */ + @JvmDefault val isTracer: Boolean get() = false diff --git a/djvm/src/main/kotlin/net/corda/djvm/references/MemberInformation.kt b/djvm/src/main/kotlin/net/corda/djvm/references/MemberInformation.kt index f890fe7c89..09ccb21836 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/references/MemberInformation.kt +++ b/djvm/src/main/kotlin/net/corda/djvm/references/MemberInformation.kt @@ -12,5 +12,6 @@ interface MemberInformation { val className: String val memberName: String val signature: String + @JvmDefault val reference: String get() = "$className.$memberName:$signature" } diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/LoadedClass.kt b/djvm/src/main/kotlin/net/corda/djvm/rewiring/LoadedClass.kt index 8db11e85d1..fdbeed7161 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/rewiring/LoadedClass.kt +++ b/djvm/src/main/kotlin/net/corda/djvm/rewiring/LoadedClass.kt @@ -1,5 +1,7 @@ package net.corda.djvm.rewiring +import org.objectweb.asm.Type + /** * A class or interface running in a Java application, together with its raw byte code representation and all references * made from within the type. @@ -16,7 +18,7 @@ class LoadedClass( * The name of the loaded type. */ val name: String - get() = type.name.replace('.', '/') + get() = Type.getInternalName(type) override fun toString(): String { return "Class(type=$name, size=${byteCode.bytes.size}, isModified=${byteCode.isModified})" diff --git a/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassWriter.kt b/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassWriter.kt index 9704421cd5..55f7cde501 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassWriter.kt +++ b/djvm/src/main/kotlin/net/corda/djvm/rewiring/SandboxClassWriter.kt @@ -4,6 +4,7 @@ import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES import org.objectweb.asm.ClassWriter.COMPUTE_MAXS +import org.objectweb.asm.Type /** * Class writer for sandbox execution, with configurable a [classLoader] to ensure correct deduction of the used class @@ -52,7 +53,7 @@ open class SandboxClassWriter( do { clazz = clazz.superclass } while (!clazz.isAssignableFrom(class2)) - clazz.name.replace('.', '/') + Type.getInternalName(clazz) } } } diff --git a/djvm/src/main/kotlin/net/corda/djvm/source/SourceClassLoader.kt b/djvm/src/main/kotlin/net/corda/djvm/source/SourceClassLoader.kt index 47d0544bcd..60c43ada43 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/source/SourceClassLoader.kt +++ b/djvm/src/main/kotlin/net/corda/djvm/source/SourceClassLoader.kt @@ -80,7 +80,7 @@ open class SourceClassLoader( when { !Files.exists(it) -> throw FileNotFoundException("File not found; $it") Files.isDirectory(it) -> { - listOf(it.toURL()) + Files.list(it).filter { isJarFile(it) }.map { it.toURL() }.toList() + listOf(it.toURL()) + Files.list(it).filter(::isJarFile).map { it.toURL() }.toList() } Files.isReadable(it) && isJarFile(it) -> listOf(it.toURL()) else -> throw IllegalArgumentException("Expected JAR or class file, but found $it") diff --git a/djvm/src/main/kotlin/net/corda/djvm/tools/Utilities.kt b/djvm/src/main/kotlin/net/corda/djvm/tools/Utilities.kt deleted file mode 100644 index f66b14d1cd..0000000000 --- a/djvm/src/main/kotlin/net/corda/djvm/tools/Utilities.kt +++ /dev/null @@ -1,114 +0,0 @@ -package net.corda.djvm.tools - -import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner -import java.lang.reflect.Modifier -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.nio.file.StandardOpenOption - -/** - * Various utility functions. - */ -@Suppress("unused") -object Utilities { - - /** - * Get the expanded file name of each path in the provided array. - */ - fun Array?.getFiles(map: (Path) -> Path = { it }) = (this ?: emptyArray()).map { - val pathString = it.toString() - val path = map(it) - when { - '/' in pathString || '\\' in pathString -> - throw Exception("Please provide a pathless file name") - pathString.endsWith(".java", true) -> path - else -> Paths.get("$path.java") - } - } - - /** - * Get the string representation of each expanded file name in the provided array. - */ - fun Array?.getFileNames(map: (Path) -> Path = { it }) = this.getFiles(map).map { - it.toString() - }.toTypedArray() - - /** - * Execute inlined action if the collection is empty. - */ - inline fun List.onEmpty(action: () -> Unit): List { - if (!this.any()) { - action() - } - return this - } - - /** - * Execute inlined action if the array is empty. - */ - inline fun Array?.onEmpty(action: () -> Unit): Array { - return (this ?: emptyArray()).toList().onEmpty(action).toTypedArray() - } - - /** - * Derive the set of [StandardOpenOption]'s to use for a file operation. - */ - fun openOptions(force: Boolean) = if (force) { - arrayOf(StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING) - } else { - arrayOf(StandardOpenOption.CREATE_NEW) - } - - /** - * Get the path of where any generated code will be placed. Create the directory if it does not exist. - */ - fun createCodePath(): Path { - val root = Paths.get("tmp") - .resolve("net") - .resolve("corda") - .resolve("djvm") - Files.createDirectories(root) - return root - } - - /** - * Return the base name of a file (i.e., its name without extension) - */ - val Path.baseName: String - get() = this.fileName.toString() - .replaceAfterLast('.', "") - .removeSuffix(".") - - /** - * The path of the executing JAR. - */ - val jarPath: String = Utilities::class.java.protectionDomain.codeSource.location.toURI().path - - - /** - * The path of the current working directory. - */ - val workingDirectory: Path = Paths.get(System.getProperty("user.dir")) - - /** - * The class path for the current execution context. - */ - val userClassPath: String = System.getProperty("java.class.path") - - /** - * Get a reference of each concrete class that implements interface or class [T]. - */ - inline fun find(scanSpec: String = "net/corda/djvm"): List> { - val references = mutableListOf>() - FastClasspathScanner(scanSpec) - .matchClassesImplementing(T::class.java) { clazz -> - if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) { - references.add(clazz) - } - } - .scan() - return references - } - -} \ No newline at end of file diff --git a/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt b/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt index 8deadf7d0b..9092e5c044 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt +++ b/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt @@ -7,6 +7,7 @@ import java.lang.reflect.Modifier * Find and instantiate types that implement a certain interface. */ object Discovery { + const val FORBIDDEN_CLASS_MASK = (Modifier.STATIC or Modifier.ABSTRACT) /** * Get an instance of each concrete class that implements interface or class [T]. @@ -15,7 +16,7 @@ object Discovery { val instances = mutableListOf() FastClasspathScanner("net/corda/djvm") .matchClassesImplementing(T::class.java) { clazz -> - if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) { + if (clazz.modifiers and FORBIDDEN_CLASS_MASK == 0) { try { instances.add(clazz.newInstance()) } catch (exception: Throwable) { diff --git a/djvm/src/test/kotlin/foo/bar/sandbox/StrictFloat.kt b/djvm/src/test/kotlin/foo/bar/sandbox/StrictFloat.kt index 0d30565a98..82f721ab9a 100644 --- a/djvm/src/test/kotlin/foo/bar/sandbox/StrictFloat.kt +++ b/djvm/src/test/kotlin/foo/bar/sandbox/StrictFloat.kt @@ -4,7 +4,7 @@ class StrictFloat : Callable { override fun call() { val d = java.lang.Double.MIN_VALUE val x = d / 2 * 2 - assert(x.toString() == "0.0") + require(x.toString() == "0.0") } } diff --git a/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt b/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt index 333516c06a..dfe50ea07c 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt +++ b/djvm/src/test/kotlin/net/corda/djvm/TestBase.kt @@ -18,9 +18,10 @@ import net.corda.djvm.utilities.Discovery import net.corda.djvm.validation.RuleValidator import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.Type import java.lang.reflect.InvocationTargetException -open class TestBase { +abstract class TestBase { companion object { @@ -38,8 +39,7 @@ open class TestBase { /** * Get the full name of type [T]. */ - inline fun nameOf(prefix: String = "") = - "$prefix${T::class.java.name.replace('.', '/')}" + inline fun nameOf(prefix: String = "") = "$prefix${Type.getInternalName(T::class.java)}" } @@ -115,7 +115,7 @@ open class TestBase { var thrownException: Throwable? = null Thread { try { - val pinnedTestClasses = pinnedClasses.map { it.name.replace('.', '/') }.toSet() + val pinnedTestClasses = pinnedClasses.map(Type::getInternalName).toSet() val analysisConfiguration = AnalysisConfiguration( whitelist = whitelist, additionalPinnedClasses = pinnedTestClasses, diff --git a/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt b/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt index a677b4fd35..2b2b8e7290 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt +++ b/djvm/src/test/kotlin/net/corda/djvm/execution/SandboxExecutorTest.kt @@ -63,7 +63,7 @@ class SandboxExecutorTest : TestBase() { val obj = Object() val hash1 = obj.hashCode() val hash2 = obj.hashCode() - assert(hash1 == hash2) + require(hash1 == hash2) return Object().hashCode() } } diff --git a/djvm/src/test/kotlin/net/corda/djvm/references/MemberModuleTest.kt b/djvm/src/test/kotlin/net/corda/djvm/references/MemberModuleTest.kt index 616a7ca7b6..71cd99cbf7 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/references/MemberModuleTest.kt +++ b/djvm/src/test/kotlin/net/corda/djvm/references/MemberModuleTest.kt @@ -4,6 +4,7 @@ import net.corda.djvm.annotations.NonDeterministic import org.assertj.core.api.Assertions.assertThat import org.jetbrains.annotations.NotNull import org.junit.Test +import org.objectweb.asm.Type class MemberModuleTest { @@ -132,7 +133,7 @@ class MemberModuleTest { } private val java.lang.Class<*>.descriptor: String - get() = "L${name.replace('.', '/')};" + get() = Type.getDescriptor(this) private fun member(member: String) = MemberReference("", member, "") diff --git a/djvm/src/test/kotlin/net/corda/djvm/rules/ReferenceExtractorTest.kt b/djvm/src/test/kotlin/net/corda/djvm/rules/ReferenceExtractorTest.kt index 3ea655c24f..bf4485caf5 100644 --- a/djvm/src/test/kotlin/net/corda/djvm/rules/ReferenceExtractorTest.kt +++ b/djvm/src/test/kotlin/net/corda/djvm/rules/ReferenceExtractorTest.kt @@ -4,6 +4,7 @@ import foo.bar.sandbox.Callable import net.corda.djvm.TestBase import net.corda.djvm.assertions.AssertionExtensions.assertThat import org.junit.Test +import org.objectweb.asm.Type import java.util.* class ReferenceExtractorTest : TestBase() { @@ -32,7 +33,7 @@ class ReferenceExtractorTest : TestBase() { @Test fun `can find field references`() = validate { context -> assertThat(context.references) - .hasMember(B::class.java.name.replace('.', '/'), "foo", "Ljava/lang/String;") + .hasMember(Type.getInternalName(B::class.java), "foo", "Ljava/lang/String;") } class B { @@ -47,7 +48,7 @@ class ReferenceExtractorTest : TestBase() { @Test fun `can find class references`() = validate { context -> assertThat(context.references) - .hasClass(A::class.java.name.replace('.', '/')) + .hasClass(Type.getInternalName(A::class.java)) } class C { diff --git a/djvm/src/test/resources/log4j2-test.xml b/djvm/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..b12cea5b2d --- /dev/null +++ b/djvm/src/test/resources/log4j2-test.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/djvm/src/test/resources/log4j2.xml b/djvm/src/test/resources/log4j2.xml deleted file mode 100644 index 93e84b6252..0000000000 --- a/djvm/src/test/resources/log4j2.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index f60ce1aa1c..ce151efb88 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,6 +6,8 @@ release, see :doc:`upgrade-notes`. Unreleased ---------- +* Getter added to ``CordaRPCOps`` for the node's network parameters. + * The RPC client library now checks at startup whether the server is of the client libraries major version or higher. Therefore to connect to a Corda 4 node you must use version 4 or lower of the library. This behaviour can be overridden by specifying a lower number in the ``CordaRPCClientConfiguration`` class. diff --git a/docs/source/corda-api.rst b/docs/source/corda-api.rst index 9e79e9d8a7..84c243173b 100644 --- a/docs/source/corda-api.rst +++ b/docs/source/corda-api.rst @@ -26,21 +26,8 @@ Before reading this page, you should be familiar with the :doc:`key concepts of Internal APIs and stability guarantees -------------------------------------- -.. warning:: For Corda 1.0 we do not currently provide a stable wire protocol or support for database upgrades. - Additionally, the JSON format produced by the client-jackson module may change in future. - Therefore, you should not expect to be able to migrate persisted data from 1.0 to future versions. - - Additionally, it may be necessary to recompile applications against future versions of the API until we begin offering - ABI stability as well. We plan to do this soon after the release of Corda 1.0. - - Finally, please note that the 1.0 release has not yet been security audited. You should not run it in situations - where security is required. - -Corda artifacts can be required from Java 9 Jigsaw modules. -From within a ``module-info.java``, you can reference one of the modules e.g., ``requires net.corda.core;``. - -.. warning:: while Corda artifacts can be required from ``module-info.java`` files, they are still not proper Jigsaw modules, - because they rely on the automatic module mechanism and declare no module descriptors themselves. We plan to integrate Jigsaw more thoroughly in the future. +Corda makes certain commitments about what parts of the API will preserve backwards compatibility as they change and +which will not. Over time, more of the API will fall under the stability guarantees. Corda stable modules -------------------- diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 9c74f64c5e..e62f7103b3 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -115,6 +115,9 @@ absolute path to the node's base directory. note that the host is the included as the advertised entry in the network map. As a result the value listed here must be externally accessible when running nodes across a cluster of machines. If the provided host is unreachable, the node will try to auto-discover its public one. + +:additionalP2PAddresses: An array of additional host:port values, which will be included in the advertised NodeInfo in the network map in addition to the ``p2pAddress``. + Nodes can use this configuration option to advertise HA endpoints and aliases to external parties. If not specified the default value is an empty list. :flowTimeout: When a flow implementing the ``TimedFlow`` interface does not complete in time, it is restarted from the initial checkpoint. Currently only used for notarisation requests: if a notary replica dies while processing a notarisation request, diff --git a/docs/source/design/targetversion/design.md b/docs/source/design/targetversion/design.md new file mode 100644 index 0000000000..a0de10d1db --- /dev/null +++ b/docs/source/design/targetversion/design.md @@ -0,0 +1,90 @@ +# CorDapp Minimum and Target Platform Version + +## Overview + +We want to give CorDapps the ability to specify which versions of the platform they support. This will make it easier for CorDapp developers to support multiple platform versions, and enable CorDapp developers to tweak behaviour and opt in to changes that might be breaking (e.g. sandboxing). Corda developers gain the ability to introduce changes to the implementation of the API that would otherwise break existing CorDapps. + +This document proposes that CorDapps will have metadata associated with them specifying a minimum platform version and a target platform Version. The minimum platform version of a CorDapp would indicate that a Corda node would have to be running at least this version of the Corda platform in order to be able to run this CorDapp. The target platform version of a CorDapp would indicate that it was tested for this version of the Corda platform. + +## Background + +> Introduce target version and min platform version as app attributes +> +> This is probably as simple as a couple of keys in a MANIFEST.MF file. +> We should document what it means, make sure API implementations can always access the target version of the calling CorDapp (i.e. by examining the flow, doing a stack walk or using Reflection.getCallerClass()) and do a simple test of an API that acts differently depending on the target version of the app. +> We should also implement checking at CorDapp load time that min platform version <= current platform version. + +([from CORDA-470](https://r3-cev.atlassian.net/browse/CORDA-470)) + +### Definitions + +* *Platform version (Corda)* An integer representing the API version of the Corda platform + +> It starts at 1 and will increment by exactly 1 for each release which changes any of the publicly exposed APIs in the entire platform. This includes public APIs on the node itself, the RPC system, messaging, serialisation, etc. API backwards compatibility will always be maintained, with the use of deprecation to migrate away from old APIs. In rare situations APIs may have to be removed, for example due to security issues. There is no relationship between the Platform Version and the release version - a change in the major, minor or patch values may or may not increase the Platform Version. + +([from the docs](https://docs.corda.net/head/versioning.html#versioning)). + +* *Platform version (Node)* The value of the Corda platform version that a node is running and advertising to the network. + +* *Minimum platform version (Network)* The minimum platform version that the nodes must run in order to be able to join the network. Set by the network zone operator. The minimum platform version is distributed with the network parameters as `minimumPlatformVersion`. + ([see docs:](https://docs.corda.net/network-map.html#network-parameters)) + +* *Target platform version (CorDapp)* Introduced in this document. Indicates that a CorDapp was tested with this version of the Corda Platform and should be run at this API level if possible. + +* *Minimum platform version (CorDapp)* Introduced in this document. Indicates the minimum version of the Corda platform that a Corda Node has to run in order to be able to run a CorDapp. + + +## Goals + +Define the semantics of target platform version and minimum platform version attributes for CorDapps, and the minimum platform version for the Corda network. Describe how target and platform versions would be specified by CorDapp developers. Define how these values can be accessed by the node and the CorDapp itself. + +## Non-goals + +In the future it might make sense to integrate the minimum and target versions into a Corda gradle plugin. Such a plugin is out of scope of this document. + +## Timeline + +This is intended as a long-term solution. The first iteration of the implementation will be part of platform version 4 and contain the minimum and target platform version. + +## Requirements + +* The CorDapp's minimum and target platform version must be accessible to nodes at CorDapp load time. + +* At CorDapp load time there should be a check that the node's platform version is greater or equal to the CorDapp's Minimum Platform version. + +* API implementations must be able to access the target version of the calling CorDapp. + +* The node's platform version must be accessible to CorDapps. + +* The CorDapp's target platform version must be accessible to the node when running CorDapps. + +## Design + +### Testing + +When a new platform version is released, CorDapp developers can increase their CorDapp's target version and re-test their app. If the tests are successful, they can then release their CorDapp with the increased target version. This way they would opt-in to potentially breaking changes that were introduced in that version. If they choose to keep their current target version, their CorDapp will continue to work. + +### Implications for platform developers + +When new features or changes are introduced that require all nodes on the network to understand them (e.g. changes in the wire transaction format), they must be version-gated on the network level. This means that the new behaviour should only take effect if the minimum platform version of the network is equal to or greater than the version in which these changes were introduced. Failing that, the old behaviour must be used instead. + +Changes that risk breaking apps must be gated on targetVersion>=X where X is the version where the change was made, and the old behaviour must be preserved if that condition isn't met. + +## Technical Design + +The minimum- and target platform version will be written to the manifest of the CorDapp's JAR, in fields called `Min-Platform-Version` and `Target-Platform-Version`. +The node's CorDapp loader reads these values from the manifest when loading the CorDapp. If the CorDapp's minimum platform version is greater than the node's platform version, the node will not load the CorDapp and log a warning. The CorDapp loader sets the minimum and target version in `net.corda.core.cordapp.Cordapp`, which can be obtained via the `CorDappContext` from the service hub. + +To make APIs caller-sensitive in cases where the service hub is not available a different approach has to be used. It would possible to do a stack walk, and parse the manifest of each class on the stack to determine if it belongs to a CorDapp, and if yes, what its target version is. Alternatively, the mapping of classes to `Cordapp`s obtained by the CorDapp loader could be stored in a global singleton. This singleton would expose a lambda returning the current CorDapp's version information (e.g. `() -> Cordapp.Info`). + +Let's assume that we want to change `TimeWindow.Between` to make it inclusive, i.e. change `contains(instant: Instant) = instant >= fromTime && instant < untilTime` to `contains(instant: Instant) = instant >= fromTime && instant <= untilTime`. However, doing so will break existing CorDapps. We could then version-guard the change such that the new behaviour is only used if the target version of the CorDapp calling `contains` is equal to or greater than the platform version that contains this change. It would look similar to this: + + ``` + fun contains(instant: Instant) { + if (CorDappVersionResolver.resolve().targetVersion > 42) { + return instant >= fromTime && instant <= untilTime + } else { + return instant >= fromTime && instant < untilTime + } + ``` +Version-gating API changes when the service hub is available would look similar to the above example, in that case the service hub's CorDapp provider would be used to determine if this code is being called from a CorDapp and to obtain its target version information. diff --git a/docs/source/running-a-node.rst b/docs/source/running-a-node.rst index ede89bafe2..48a80b8eff 100644 --- a/docs/source/running-a-node.rst +++ b/docs/source/running-a-node.rst @@ -47,22 +47,74 @@ Command-line options ~~~~~~~~~~~~~~~~~~~~ The node can optionally be started with the following command-line options: -* ``--base-directory``: The node working directory where all the files are kept (default: ``.``) -* ``--bootstrap-raft-cluster``: Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer - addresses), acting as a seed for other nodes to join the cluster -* ``--config-file``: The path to the config file (default: ``node.conf``) -* ``--help`` -* ``--initial-registration``: Start initial node registration with Corda network to obtain certificate from the permissioning - server -* ``--just-generate-node-info``: Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then - quit -* ``--log-to-console``: If set, prints logging to the console as well as to a file -* ``--logging-level <[ERROR,WARN,INFO, DEBUG,TRACE]>``: Enable logging at this level and higher (default: INFO) -* ``--network-root-truststore``: Network root trust store obtained from network operator -* ``--network-root-truststore-password``: Network root trust store password obtained from network operator -* ``--no-local-shell``: Do not start the embedded shell locally -* ``--sshd``: Enables SSHD server for node administration -* ``--version``: Print the version and exit +* ``--base-directory``, ``-b``: The node working directory where all the files are kept (default: ``.``). +* ``--bootstrap-raft-cluster``: Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer + addresses), acting as a seed for other nodes to join the cluster. +* ``--clear-network-map-cache``, ``-c``: Clears local copy of network map, on node startup it will be restored from server or file system. +* ``--config-file``, ``-f``: The path to the config file. Defaults to ``node.conf``. +* ``--dev-mode``, ``-d``: Runs the node in developer mode. Unsafe in production. Defaults to true on MacOS and desktop versions of Windows. False otherwise. +* ``--help``, ``-h``: Displays the help message and exits. +* ``--initial-registration``: Start initial node registration with Corda network to obtain certificate from the permissioning + server. +* ``--install-shell-extensions``: Installs an alias and auto-completion for users of ``bash`` or ``zsh``. See below for more information. +* ``--just-generate-node-info``: Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then + quit. +* ``--just-generate-rpc-ssl-settings``: Generate the ssl keystore and truststore for a secure RPC connection. +* ``--log-to-console``, ``--verbose``, ``-v``: If set, prints logging to the console as well as to a file. +* ``--logging-level <[ERROR,WARN,INFO,DEBUG,TRACE]>``: Enable logging at this level and higher. Defaults to INFO. +* ``--network-root-truststore``, ``-t``: Network root trust store obtained from network operator. +* ``--network-root-truststore-password``, ``-p``: Network root trust store password obtained from network operator. +* ``--no-local-shell``, ``-n``: Do not start the embedded shell locally. +* ``--on-unknown-config-keys <[FAIL,WARN,INFO]>``: How to behave on unknown node configuration. Defaults to FAIL. +* ``--sshd``: Enables SSH server for node administration. +* ``--sshd-port``: Sets the port for the SSH server. If not supplied and SSH server is enabled, the port defaults to 2222. +* ``--version``, ``-V``: Prints the version and exits. + +.. _installing-shell-extensions: + +Installing shell extensions +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Users of ``bash`` or ``zsh`` can install an alias and command line completion for Corda. Run: + +.. code-block:: shell + + java -jar corda.jar --install-shell-extensions + +Then, either restart your shell, or for ``bash`` users run: + +.. code-block:: shell + + . ~/.bashrc + +Or, for ``zsh`` run: + +.. code-block:: shell + + . ~/.zshrc + +You will now be able to run a Corda node from anywhere by running the following: + +.. code-block:: shell + + corda --