diff --git a/.ci/api-current.txt b/.ci/api-current.txt index ec11b37730..a1a4e27e36 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -4359,7 +4359,7 @@ public interface net.corda.core.schemas.QueryableState extends net.corda.core.co @NotNull public abstract Iterable supportedSchemas() ## -public interface net.corda.core.schemas.StatePersistable extends java.io.Serializable +public interface net.corda.core.schemas.StatePersistable ## public interface net.corda.core.serialization.ClassWhitelist public abstract boolean hasListed(Class) diff --git a/build.gradle b/build.gradle index b853040c16..fb7c98782e 100644 --- a/build.gradle +++ b/build.gradle @@ -157,10 +157,6 @@ allprojects { apply plugin: 'org.owasp.dependencycheck' apply plugin: 'kotlin-allopen' - // This line works around a serious performance regression in the Kotlin 1.2.50 gradle plugin, it's fixed in 1.2.51 - // TODO: Remove when we upgrade past Kotlin 1.2.51 - inspectClassesForKotlinIC.enabled = false - allOpen { annotations( "javax.persistence.Entity", diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index cfbdd7722c..b47259bd1c 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -32,9 +32,6 @@ buildscript { } } -apply plugin: 'maven' -apply plugin: 'java' - repositories { mavenLocal() mavenCentral() @@ -61,12 +58,13 @@ allprojects { } } -dependencies { - // Add the top-level projects ONLY to the host project. - runtime project.childProjects.values().collect { - project(it.path) - } +configurations { + runtime } -// Don't create an empty jar. The plugins are now in child projects. -jar.enabled = false +dependencies { + // Add the top-level projects ONLY to the host project. + runtime project.childProjects.collect { n, p -> + project(p.path) + } +} diff --git a/buildSrc/jarfilter/README.md b/buildSrc/jarfilter/README.md index a7a0546816..0f627cc415 100644 --- a/buildSrc/jarfilter/README.md +++ b/buildSrc/jarfilter/README.md @@ -59,6 +59,72 @@ task jarFilter(type: JarFilterTask) { You can specify as many annotations for each role as you like. The only constraint is that a given annotation cannot be assigned to more than one role. +#### Removing unwanted default parameter values +It is possible to assign non-deterministic expressions as default values for Kotlin constructors and functions. For +example: +```kotlin +data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID()) +``` + +The Kotlin compiler will generate _two_ constructors in this case: +``` +UniqueIdentifier(String?, UUID) +UniqueIdentifier(String?, UUID, Int, DefaultConstructorMarker) +``` + +The first constructor is the primary constructor that we would expect (and which we'd like to keep), whereas the +second is a public synthetic constructor that Kotlin applications invoke to handle the different combinations of +default parameter values. Unfortunately, this synthetic constructor is therefore also part of the Kotlin ABI and +so we _cannot_ rewrite the class like this to remove the default values: +```kotlin +// THIS REFACTOR WOULD BREAK THE KOTLIN ABI! +data class UniqueIdentifier(val externalId: String?, val id: UUID) { + constructor(externalId: String?) : this(externalId, UUID.randomUUID()) + constructor() : this(null) +} +``` + +The refactored class would have the following constructors, and would require client applications to be recompiled: +``` +UniqueIdentifier(String?, UUID) +UniqueIdentifier(String?) +UniqueIdentifier() +``` + +We therefore need to keep the default constructor parameters in order to preserve the ABI for the unfiltered code, +which in turn means that `JarFilter` will need to delete only the synthetic constructor and leave the primary +constructor intact. However, Kotlin does not currently allow us to annotate _specific_ constructors - see +[KT-22524](https://youtrack.jetbrains.com/issue/KT-22524). Until it does, `JarFilter` will perform an initial +"sanitising" pass over the JAR file to remove any unwanted annotations from the primary constructors. These unwanted +annotations are configured in the `JarFilter` task definition: +```gradle +task jarFilter(type: JarFilterTask) { + ... + annotations { + ... + forSanitise = [ + "org.testing.DeleteMe" + ] + } +} +``` + +This allows us to annotate the `UniqueIdentifier` class like this: +```kotlin +data class UniqueIdentifier @DeleteMe constructor(val externalId: String? = null, val id: UUID = UUID.randomUUID()) +``` + +to generate these constructors: +``` +UniqueIdentifier(String?, UUID) +@DeleteMe UniqueIdentifier(String?, UUID, Int, DefaultConstructorMarker) +``` + +We currently **do not** sanitise annotations from functions with default parameter values, although (in theory) these +may also be non-deterministic. We will need to extend the sanitation pass to include such functions if/when the need +arises. At the moment, deleting such functions _entirely_ is enough, whereas also deleting a primary constructor means +that we can no longer create instances of that class either. + ### The `MetaFixer` task The `MetaFixer` task updates the `@kotlin.Metadata` annotations by removing references to any functions, constructors, properties or nested classes that no longer exist in the byte-code. This is primarily to diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/FilterTransformer.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/FilterTransformer.kt index 06ce0c2311..7c33ac4dd7 100644 --- a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/FilterTransformer.kt +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/FilterTransformer.kt @@ -26,7 +26,7 @@ class FilterTransformer private constructor ( private val unwantedFields: MutableSet, private val deletedMethods: MutableSet, private val stubbedMethods: MutableSet -) : KotlinAwareVisitor(ASM6, visitor, logger, kotlinMetadata), Repeatable { +) : KotlinAfterProcessor(ASM6, visitor, logger, kotlinMetadata), Repeatable { constructor( visitor: ClassVisitor, logger: Logger, @@ -47,8 +47,8 @@ class FilterTransformer private constructor ( stubbedMethods = mutableSetOf() ) - private var _className: String = "(unknown)" - val className: String get() = _className + var className: String = "(unknown)" + private set val isUnwantedClass: Boolean get() = isUnwantedClass(className) override val hasUnwantedElements: Boolean @@ -76,7 +76,7 @@ class FilterTransformer private constructor ( ) override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array?) { - _className = clsName + className = clsName logger.info("Class {}", clsName) super.visit(version, access, clsName, signature, superName, interfaces) } @@ -172,7 +172,7 @@ class FilterTransformer private constructor ( /** * Removes the deleted methods and fields from the Kotlin Class metadata. */ - override fun transformClassMetadata(d1: List, d2: List): List { + override fun processClassMetadata(d1: List, d2: List): List { val partitioned = deletedMethods.groupBy(MethodElement::isConstructor) val prefix = "$className$" return ClassMetadataTransformer( @@ -191,7 +191,7 @@ class FilterTransformer private constructor ( /** * Removes the deleted methods and fields from the Kotlin Package metadata. */ - override fun transformPackageMetadata(d1: List, d2: List): List { + override fun processPackageMetadata(d1: List, d2: List): List { return PackageMetadataTransformer( logger = logger, deletedFields = unwantedFields, diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/JarFilterTask.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/JarFilterTask.kt index a370b7884a..1d0c99f4cc 100644 --- a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/JarFilterTask.kt +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/JarFilterTask.kt @@ -46,6 +46,9 @@ open class JarFilterTask : DefaultTask() { @get:Input protected var forRemove: Set = emptySet() + @get:Input + protected var forSanitise: Set = emptySet() + fun annotations(assign: Closure>) { assign.call() } @@ -90,6 +93,9 @@ open class JarFilterTask : DefaultTask() { if (forRemove.isNotEmpty()) { logger.info("- Annotations '{}' will be removed entirely", forRemove.joinToString()) } + if (forSanitise.isNotEmpty()) { + logger.info("- Annotations '{}' will be removed from primary constructors", forSanitise.joinToString()) + } checkDistinctAnnotations() try { jars.forEach { jar -> @@ -136,6 +142,11 @@ open class JarFilterTask : DefaultTask() { private val source: Path = inFile.toPath() private val target: Path = toFiltered(inFile).toPath() + private val descriptorsForRemove = toDescriptors(forRemove) + private val descriptorsForDelete = toDescriptors(forDelete) + private val descriptorsForStub = toDescriptors(forStub) + private val descriptorsForSanitising = toDescriptors(forSanitise) + init { Files.deleteIfExists(target) } @@ -145,10 +156,14 @@ open class JarFilterTask : DefaultTask() { var input = source try { + if (descriptorsForSanitising.isNotEmpty() && SanitisingPass(input).use { it.run() }) { + input = target.moveToInput() + } + var passes = 1 while (true) { verbose("Pass {}", passes) - val isModified = Pass(input).use { it.run() } + val isModified = FilterPass(input).use { it.run() } if (!isModified) { logger.info("No changes after latest pass - exiting.") @@ -157,9 +172,7 @@ open class JarFilterTask : DefaultTask() { break } - input = Files.move( - target, Files.createTempFile(target.parent, "filter-", ".tmp"), REPLACE_EXISTING) - verbose("New input JAR: {}", input) + input = target.moveToInput() } } catch (e: Exception) { logger.error("Error filtering '{}' elements from {}", ArrayList(forRemove).apply { addAll(forDelete); addAll(forStub) }, input) @@ -167,14 +180,20 @@ open class JarFilterTask : DefaultTask() { } } - private inner class Pass(input: Path): Closeable { + private fun Path.moveToInput(): Path { + return Files.move(this, Files.createTempFile(parent, "filter-", ".tmp"), REPLACE_EXISTING).also { + verbose("New input JAR: {}", it) + } + } + + private abstract inner class Pass(input: Path): Closeable { /** * Use [ZipFile] instead of [java.util.jar.JarInputStream] because * JarInputStream consumes MANIFEST.MF when it's the first or second entry. */ - private val inJar = ZipFile(input.toFile()) - private val outJar = ZipOutputStream(Files.newOutputStream(target)) - private var isModified = false + protected val inJar = ZipFile(input.toFile()) + protected val outJar = ZipOutputStream(Files.newOutputStream(target)) + protected var isModified = false @Throws(IOException::class) override fun close() { @@ -183,6 +202,8 @@ open class JarFilterTask : DefaultTask() { } } + abstract fun transform(inBytes: ByteArray): ByteArray + fun run(): Boolean { outJar.setLevel(BEST_COMPRESSION) outJar.setComment(inJar.comment) @@ -207,16 +228,29 @@ open class JarFilterTask : DefaultTask() { } return isModified } + } - private fun transform(inBytes: ByteArray): ByteArray { + private inner class SanitisingPass(input: Path) : Pass(input) { + override fun transform(inBytes: ByteArray): ByteArray { + return ClassWriter(0).let { writer -> + val transformer = SanitisingTransformer(writer, logger, descriptorsForSanitising) + ClassReader(inBytes).accept(transformer, 0) + isModified = isModified or transformer.isModified + writer.toByteArray() + } + } + } + + private inner class FilterPass(input: Path) : Pass(input) { + override fun transform(inBytes: ByteArray): ByteArray { var reader = ClassReader(inBytes) var writer = ClassWriter(COMPUTE_MAXS) var transformer = FilterTransformer( visitor = writer, logger = logger, - removeAnnotations = toDescriptors(forRemove), - deleteAnnotations = toDescriptors(forDelete), - stubAnnotations = toDescriptors(forStub), + removeAnnotations = descriptorsForRemove, + deleteAnnotations = descriptorsForDelete, + stubAnnotations = descriptorsForStub, unwantedClasses = unwantedClasses ) diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/KotlinAwareVisitor.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/KotlinAwareVisitor.kt index ae078c69b4..7c50110626 100644 --- a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/KotlinAwareVisitor.kt +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/KotlinAwareVisitor.kt @@ -1,13 +1,13 @@ package net.corda.gradle.jarfilter +import org.gradle.api.logging.LogLevel import org.gradle.api.logging.Logger import org.jetbrains.kotlin.load.java.JvmAnnotationNames.* import org.objectweb.asm.AnnotationVisitor import org.objectweb.asm.ClassVisitor /** - * Kotlin support: Loads the ProtoBuf data from the [kotlin.Metadata] annotation, - * or writes new ProtoBuf data that was created during a previous pass. + * Kotlin support: Loads the ProtoBuf data from the [kotlin.Metadata] annotation. */ abstract class KotlinAwareVisitor( api: Int, @@ -27,23 +27,24 @@ abstract class KotlinAwareVisitor( private var classKind: Int = 0 open val hasUnwantedElements: Boolean get() = kotlinMetadata.isNotEmpty() + protected open val level: LogLevel = LogLevel.INFO - protected abstract fun transformClassMetadata(d1: List, d2: List): List - protected abstract fun transformPackageMetadata(d1: List, d2: List): List + protected abstract fun processClassMetadata(d1: List, d2: List): List + protected abstract fun processPackageMetadata(d1: List, d2: List): List + protected abstract fun processKotlinAnnotation() override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? { val av = super.visitAnnotation(descriptor, visible) ?: return null return if (descriptor == METADATA_DESC) KotlinMetadataAdaptor(av) else av } - override fun visitEnd() { - super.visitEnd() + protected fun processMetadata() { if (kotlinMetadata.isNotEmpty()) { - logger.info("- Examining Kotlin @Metadata[k={}]", classKind) + logger.log(level, "- Examining Kotlin @Metadata[k={}]", classKind) val d1 = kotlinMetadata.remove(METADATA_DATA_FIELD_NAME) val d2 = kotlinMetadata.remove(METADATA_STRINGS_FIELD_NAME) if (d1 != null && d1.isNotEmpty() && d2 != null) { - transformMetadata(d1, d2).apply { + processMetadata(d1, d2).apply { if (isNotEmpty()) { kotlinMetadata[METADATA_DATA_FIELD_NAME] = this kotlinMetadata[METADATA_STRINGS_FIELD_NAME] = d2 @@ -53,12 +54,12 @@ abstract class KotlinAwareVisitor( } } - private fun transformMetadata(d1: List, d2: List): List { + private fun processMetadata(d1: List, d2: List): List { return when (classKind) { - KOTLIN_CLASS -> transformClassMetadata(d1, d2) - KOTLIN_FILE, KOTLIN_MULTIFILE_PART -> transformPackageMetadata(d1, d2) + KOTLIN_CLASS -> processClassMetadata(d1, d2) + KOTLIN_FILE, KOTLIN_MULTIFILE_PART -> processPackageMetadata(d1, d2) KOTLIN_SYNTHETIC -> { - logger.info("-- synthetic class ignored") + logger.log(level,"-- synthetic class ignored") emptyList() } else -> { @@ -66,7 +67,7 @@ abstract class KotlinAwareVisitor( * For class-kind=4 (i.e. "multi-file"), we currently * expect d1=[list of multi-file-part classes], d2=null. */ - logger.info("-- unsupported class-kind {}", classKind) + logger.log(level,"-- unsupported class-kind {}", classKind) emptyList() } } @@ -91,19 +92,68 @@ abstract class KotlinAwareVisitor( return null } - private inner class ArrayAccumulator(av: AnnotationVisitor, private val name: String) : AnnotationVisitor(api, av) { - private val data: MutableList = mutableListOf() - - override fun visit(name: String?, value: Any?) { - super.visit(name, value) - data.add(value as String) - } - - override fun visitEnd() { - super.visitEnd() - kotlinMetadata[name] = data - logger.debug("-- read @Metadata.{}[{}]", name, data.size) - } + override fun visitEnd() { + super.visitEnd() + processKotlinAnnotation() } } -} \ No newline at end of file + + private inner class ArrayAccumulator(av: AnnotationVisitor, private val name: String) : AnnotationVisitor(api, av) { + private val data: MutableList = mutableListOf() + + override fun visit(name: String?, value: Any?) { + super.visit(name, value) + data.add(value as String) + } + + override fun visitEnd() { + super.visitEnd() + kotlinMetadata[name] = data + logger.debug("-- read @Metadata.{}[{}]", name, data.size) + } + } +} + +/** + * Loads the ProtoBuf data from the [kotlin.Metadata] annotation, or + * writes new ProtoBuf data that was created during a previous pass. + */ +abstract class KotlinAfterProcessor( + api: Int, + visitor: ClassVisitor, + logger: Logger, + kotlinMetadata: MutableMap> +) : KotlinAwareVisitor(api, visitor, logger, kotlinMetadata) { + + /** + * Process the metadata once we have finished visiting the class. + * This will allow us to rewrite the [kotlin.Metadata] annotation + * in the next visit. + */ + override fun visitEnd() { + super.visitEnd() + processMetadata() + } + + /** + * Do nothing immediately after we have parsed [kotlin.Metadata]. + */ + final override fun processKotlinAnnotation() {} +} + +/** + * Loads the ProtoBuf data from the [kotlin.Metadata] annotation + * and then processes it before visiting the rest of the class. + */ +abstract class KotlinBeforeProcessor( + api: Int, + visitor: ClassVisitor, + logger: Logger, + kotlinMetadata: MutableMap> +) : KotlinAwareVisitor(api, visitor, logger, kotlinMetadata) { + + /** + * Process the ProtoBuf data as soon as we have parsed [kotlin.Metadata]. + */ + final override fun processKotlinAnnotation() = processMetadata() +} diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerVisitor.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerVisitor.kt index a122cf4308..bce68310bc 100644 --- a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerVisitor.kt +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/MetaFixerVisitor.kt @@ -17,7 +17,7 @@ class MetaFixerVisitor private constructor( private val fields: MutableSet, private val methods: MutableSet, private val nestedClasses: MutableSet -) : KotlinAwareVisitor(ASM6, visitor, logger, kotlinMetadata), Repeatable { +) : KotlinAfterProcessor(ASM6, visitor, logger, kotlinMetadata), Repeatable { constructor(visitor: ClassVisitor, logger: Logger, classNames: Set) : this(visitor, logger, mutableMapOf(), classNames, mutableSetOf(), mutableSetOf(), mutableSetOf()) @@ -52,7 +52,7 @@ class MetaFixerVisitor private constructor( return super.visitInnerClass(clsName, outerName, innerName, access) } - override fun transformClassMetadata(d1: List, d2: List): List { + override fun processClassMetadata(d1: List, d2: List): List { return ClassMetaFixerTransformer( logger = logger, actualFields = fields, @@ -64,7 +64,7 @@ class MetaFixerVisitor private constructor( .transform() } - override fun transformPackageMetadata(d1: List, d2: List): List { + override fun processPackageMetadata(d1: List, d2: List): List { return PackageMetaFixerTransformer( logger = logger, actualFields = fields, diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/SanitisingTransformer.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/SanitisingTransformer.kt new file mode 100644 index 0000000000..e4dcdf822a --- /dev/null +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/SanitisingTransformer.kt @@ -0,0 +1,81 @@ +package net.corda.gradle.jarfilter + +import org.gradle.api.logging.LogLevel +import org.gradle.api.logging.Logger +import org.jetbrains.kotlin.metadata.ProtoBuf +import org.jetbrains.kotlin.metadata.deserialization.Flags.* +import org.jetbrains.kotlin.metadata.deserialization.TypeTable +import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf.* +import org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmNameResolver +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.EXTENSION_REGISTRY +import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil.getJvmConstructorSignature +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes.* +import java.io.ByteArrayInputStream + +/** + * This is (hopefully?!) a temporary solution for classes with [JvmOverloads] constructors. + * We need to be able to annotate ONLY the secondary constructors for such classes, but Kotlin + * will apply any annotation to all constructors equally. Nor can we replace the overloaded + * constructor with individual constructors because this will break ABI compatibility. (Kotlin + * generates a synthetic public constructor to handle default parameter values.) + * + * This transformer identifies a class's primary constructor and removes all of its unwanted annotations. + * It will become superfluous when Kotlin allows us to target only the secondary constructors with our + * filtering annotations in the first place. + */ +class SanitisingTransformer(visitor: ClassVisitor, logger: Logger, private val unwantedAnnotations: Set) + : KotlinBeforeProcessor(ASM6, visitor, logger, mutableMapOf()) { + + var isModified: Boolean = false + private set + override val level: LogLevel = LogLevel.DEBUG + + private var className: String = "(unknown)" + private var primaryConstructor: MethodElement? = null + + override fun processPackageMetadata(d1: List, d2: List): List = emptyList() + + override fun processClassMetadata(d1: List, d2: List): List { + val input = ByteArrayInputStream(BitEncoding.decodeBytes(d1.toTypedArray())) + val stringTableTypes = StringTableTypes.parseDelimitedFrom(input, EXTENSION_REGISTRY) + val nameResolver = JvmNameResolver(stringTableTypes, d2.toTypedArray()) + val message = ProtoBuf.Class.parseFrom(input, EXTENSION_REGISTRY) + val typeTable = TypeTable(message.typeTable) + + for (constructor in message.constructorList) { + if (!IS_SECONDARY.get(constructor.flags)) { + val signature = getJvmConstructorSignature(constructor, nameResolver, typeTable) ?: break + primaryConstructor = MethodElement("", signature.drop("".length)) + logger.log(level, "Class {} has primary constructor {}", className, signature) + break + } + } + return emptyList() + } + + override fun visit(version: Int, access: Int, clsName: String, signature: String?, superName: String?, interfaces: Array?) { + className = clsName + super.visit(version, access, clsName, signature, superName, interfaces) + } + + override fun visitMethod(access: Int, methodName: String, descriptor: String, signature: String?, exceptions: Array?): MethodVisitor? { + val method = MethodElement(methodName, descriptor, access) + val mv = super.visitMethod(access, methodName, descriptor, signature, exceptions) ?: return null + return if (method == primaryConstructor) SanitisingMethodAdapter(mv, method) else mv + } + + private inner class SanitisingMethodAdapter(mv: MethodVisitor, private val method: MethodElement) : MethodVisitor(api, mv) { + override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? { + if (unwantedAnnotations.contains(descriptor)) { + logger.info("Sanitising annotation {} from method {}.{}{}", descriptor, className, method.name, method.descriptor) + isModified = true + return null + } + return super.visitAnnotation(descriptor, visible) + } + } +} diff --git a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Utils.kt b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Utils.kt index cbbc026c99..9b52c5419f 100644 --- a/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Utils.kt +++ b/buildSrc/jarfilter/src/main/kotlin/net/corda/gradle/jarfilter/Utils.kt @@ -73,17 +73,20 @@ internal val String.descriptor: String get() = "L$toPathFormat;" internal fun ByteArray.execute(visitor: (ClassVisitor) -> T, flags: Int = 0, passes: Int = 2): ByteArray where T : ClassVisitor, T : Repeatable { - var reader = ClassReader(this) + var bytecode = this var writer = ClassWriter(flags) - val transformer = visitor(writer) + var transformer = visitor(writer) var count = max(passes, 1) - reader.accept(transformer, 0) - while (transformer.hasUnwantedElements && --count > 0) { - reader = ClassReader(writer.toByteArray()) + while (--count >= 0) { + ClassReader(bytecode).accept(transformer, 0) + bytecode = writer.toByteArray() + + if (!transformer.hasUnwantedElements) break + writer = ClassWriter(flags) - reader.accept(transformer.recreate(writer), 0) + transformer = transformer.recreate(writer) } - return writer.toByteArray() + return bytecode } diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteConstructorTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteConstructorTest.kt index 08599d0865..8a8651ef41 100644 --- a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteConstructorTest.kt +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/DeleteConstructorTest.kt @@ -36,7 +36,7 @@ class DeleteConstructorTest { @Test fun deleteConstructorWithLongParameter() { - val longConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, hasParam(Long::class)) + val longConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, Long::class) classLoaderFor(testProject.sourceJar).use { cl -> cl.load(SECONDARY_CONSTRUCTOR_CLASS).apply { @@ -58,7 +58,7 @@ class DeleteConstructorTest { @Test fun deleteConstructorWithStringParameter() { - val stringConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, hasParam(String::class)) + val stringConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, String::class) classLoaderFor(testProject.sourceJar).use { cl -> cl.load(SECONDARY_CONSTRUCTOR_CLASS).apply { @@ -80,7 +80,7 @@ class DeleteConstructorTest { @Test fun showUnannotatedConstructorIsUnaffected() { - val intConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, hasParam(Int::class)) + val intConstructor = isConstructor(SECONDARY_CONSTRUCTOR_CLASS, Int::class) classLoaderFor(testProject.filteredJar).use { cl -> cl.load(SECONDARY_CONSTRUCTOR_CLASS).apply { getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { @@ -96,7 +96,7 @@ class DeleteConstructorTest { @Test fun deletePrimaryConstructorWithStringParameter() { - val stringConstructor = isConstructor(STRING_PRIMARY_CONSTRUCTOR_CLASS, hasParam(String::class)) + val stringConstructor = isConstructor(STRING_PRIMARY_CONSTRUCTOR_CLASS, String::class) classLoaderFor(testProject.sourceJar).use { cl -> cl.load(STRING_PRIMARY_CONSTRUCTOR_CLASS).apply { @@ -119,7 +119,7 @@ class DeleteConstructorTest { @Test fun deletePrimaryConstructorWithLongParameter() { - val longConstructor = isConstructor(LONG_PRIMARY_CONSTRUCTOR_CLASS, hasParam(Long::class)) + val longConstructor = isConstructor(LONG_PRIMARY_CONSTRUCTOR_CLASS, Long::class) classLoaderFor(testProject.sourceJar).use { cl -> cl.load(LONG_PRIMARY_CONSTRUCTOR_CLASS).apply { @@ -142,7 +142,7 @@ class DeleteConstructorTest { @Test fun deletePrimaryConstructorWithIntParameter() { - val intConstructor = isConstructor(INT_PRIMARY_CONSTRUCTOR_CLASS, hasParam(Int::class)) + val intConstructor = isConstructor(INT_PRIMARY_CONSTRUCTOR_CLASS, Int::class) classLoaderFor(testProject.sourceJar).use { cl -> cl.load(INT_PRIMARY_CONSTRUCTOR_CLASS).apply { diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseConstructorTest.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseConstructorTest.kt new file mode 100644 index 0000000000..f45a26d082 --- /dev/null +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/SanitiseConstructorTest.kt @@ -0,0 +1,183 @@ +package net.corda.gradle.jarfilter + +import net.corda.gradle.jarfilter.matcher.* +import net.corda.gradle.unwanted.HasInt +import net.corda.gradle.unwanted.HasLong +import net.corda.gradle.unwanted.HasString +import org.assertj.core.api.Assertions.* +import org.hamcrest.core.IsCollectionContaining.hasItem +import org.junit.Assert.* +import org.junit.ClassRule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TemporaryFolder +import org.junit.rules.TestRule +import kotlin.jvm.kotlin +import kotlin.reflect.full.primaryConstructor +import kotlin.test.assertFailsWith + +class SanitiseConstructorTest { + companion object { + private const val COUNT_INITIAL_OVERLOADED = 1 + private const val COUNT_INITIAL_MULTIPLE = 2 + private val testProjectDir = TemporaryFolder() + private val testProject = JarFilterProject(testProjectDir, "sanitise-constructor") + + @ClassRule + @JvmField + val rules: TestRule = RuleChain + .outerRule(testProjectDir) + .around(testProject) + } + + @Test + fun deleteOverloadedLongConstructor() = checkClassWithLongParameter( + "net.corda.gradle.HasOverloadedLongConstructor", + COUNT_INITIAL_OVERLOADED + ) + + @Test + fun deleteMultipleLongConstructor() = checkClassWithLongParameter( + "net.corda.gradle.HasMultipleLongConstructors", + COUNT_INITIAL_MULTIPLE + ) + + private fun checkClassWithLongParameter(longClass: String, initialCount: Int) { + val longConstructor = isConstructor(longClass, Long::class) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(longClass).apply { + getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { + assertEquals(BIG_NUMBER, it.longData()) + } + kotlin.constructors.apply { + assertThat("(J) not found", this, hasItem(longConstructor)) + assertEquals(initialCount, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER) + + newInstance().also { + assertEquals(0, it.longData()) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(longClass).apply { + getDeclaredConstructor(Long::class.java).newInstance(BIG_NUMBER).also { + assertEquals(BIG_NUMBER, it.longData()) + } + kotlin.constructors.apply { + assertThat("(J) not found", this, hasItem(longConstructor)) + assertEquals(1, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(BIG_NUMBER).longData()).isEqualTo(BIG_NUMBER) + + assertFailsWith { getDeclaredConstructor() } + } + } + } + + @Test + fun deleteOverloadedIntConstructor() = checkClassWithIntParameter( + "net.corda.gradle.HasOverloadedIntConstructor", + COUNT_INITIAL_OVERLOADED + ) + + @Test + fun deleteMultipleIntConstructor() = checkClassWithIntParameter( + "net.corda.gradle.HasMultipleIntConstructors", + COUNT_INITIAL_MULTIPLE + ) + + private fun checkClassWithIntParameter(intClass: String, initialCount: Int) { + val intConstructor = isConstructor(intClass, Int::class) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(intClass).apply { + getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { + assertEquals(NUMBER, it.intData()) + } + kotlin.constructors.apply { + assertThat("(I) not found", this, hasItem(intConstructor)) + assertEquals(initialCount, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER) + + //assertThat("", constructors, hasItem(isConstructor("")) + newInstance().also { + assertEquals(0, it.intData()) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(intClass).apply { + getDeclaredConstructor(Int::class.java).newInstance(NUMBER).also { + assertEquals(NUMBER, it.intData()) + } + kotlin.constructors.apply { + assertThat("(I) not found", this, hasItem(intConstructor)) + assertEquals(1, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(NUMBER).intData()).isEqualTo(NUMBER) + + assertFailsWith { getDeclaredConstructor() } + } + } + } + + @Test + fun deleteOverloadedStringConstructor() = checkClassWithStringParameter( + "net.corda.gradle.HasOverloadedStringConstructor", + COUNT_INITIAL_OVERLOADED + ) + + @Test + fun deleteMultipleStringConstructor() = checkClassWithStringParameter( + "net.corda.gradle.HasMultipleStringConstructors", + COUNT_INITIAL_MULTIPLE + ) + + private fun checkClassWithStringParameter(stringClass: String, initialCount: Int) { + val stringConstructor = isConstructor(stringClass, String::class) + + classLoaderFor(testProject.sourceJar).use { cl -> + cl.load(stringClass).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { + assertEquals(MESSAGE, it.stringData()) + } + kotlin.constructors.apply { + assertThat("(String) not found", this, hasItem(stringConstructor)) + assertEquals(initialCount, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE) + + newInstance().also { + assertEquals(DEFAULT_MESSAGE, it.stringData()) + } + } + } + + classLoaderFor(testProject.filteredJar).use { cl -> + cl.load(stringClass).apply { + getDeclaredConstructor(String::class.java).newInstance(MESSAGE).also { + assertEquals(MESSAGE, it.stringData()) + } + kotlin.constructors.apply { + assertThat("(String) not found", this, hasItem(stringConstructor)) + assertEquals(1, this.size) + } + val primary = kotlin.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(MESSAGE).stringData()).isEqualTo(MESSAGE) + + assertFailsWith { getDeclaredConstructor() } + } + } + } +} diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/JavaMatchers.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/JavaMatchers.kt index 25a82e6c8d..19dc03a3bf 100644 --- a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/JavaMatchers.kt +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/JavaMatchers.kt @@ -13,10 +13,12 @@ fun isMethod(name: Matcher, returnType: Matcher>, vararg } fun isMethod(name: String, returnType: Class<*>, vararg parameters: Class<*>): Matcher { - return isMethod(equalTo(name), equalTo(returnType), *parameters.map(::equalTo).toTypedArray()) + return isMethod(equalTo(name), equalTo(returnType), *parameters.toMatchers()) } -val KClass.javaDeclaredMethods: List get() = java.declaredMethods.toList() +private fun Array>.toMatchers() = map(::equalTo).toTypedArray() + +val KClass<*>.javaDeclaredMethods: List get() = java.declaredMethods.toList() /** * Matcher logic for a Java [Method] object. Also applicable to constructors. diff --git a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/KotlinMatchers.kt b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/KotlinMatchers.kt index 2082ddc4dc..f5e0528927 100644 --- a/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/KotlinMatchers.kt +++ b/buildSrc/jarfilter/src/test/kotlin/net/corda/gradle/jarfilter/matcher/KotlinMatchers.kt @@ -17,7 +17,7 @@ fun isFunction(name: Matcher, returnType: Matcher, vararg } fun isFunction(name: String, returnType: KClass<*>, vararg parameters: KClass<*>): Matcher> { - return isFunction(equalTo(name), matches(returnType), *parameters.map(::hasParam).toTypedArray()) + return isFunction(equalTo(name), matches(returnType), *parameters.toMatchers()) } fun isConstructor(returnType: Matcher, vararg parameters: Matcher): Matcher> { @@ -25,11 +25,11 @@ fun isConstructor(returnType: Matcher, vararg parameters: Matcher, vararg parameters: KClass<*>): Matcher> { - return isConstructor(matches(returnType), *parameters.map(::hasParam).toTypedArray()) + return isConstructor(matches(returnType), *parameters.toMatchers()) } -fun isConstructor(returnType: String, vararg parameters: Matcher): Matcher> { - return isConstructor(equalTo(returnType), *parameters) +fun isConstructor(returnType: String, vararg parameters: KClass<*>): Matcher> { + return isConstructor(equalTo(returnType), *parameters.toMatchers()) } fun hasParam(type: Matcher): Matcher = KParameterMatcher(type) @@ -44,6 +44,8 @@ fun isClass(name: String): Matcher> = KClassMatcher(equalTo(name)) fun matches(type: KClass<*>): Matcher = equalTo(type.qualifiedName) +private fun Array>.toMatchers() = map(::hasParam).toTypedArray() + /** * Matcher logic for a Kotlin [KFunction] object. Also applicable to constructors. */ diff --git a/buildSrc/jarfilter/src/test/resources/sanitise-constructor/build.gradle b/buildSrc/jarfilter/src/test/resources/sanitise-constructor/build.gradle new file mode 100644 index 0000000000..c1cf56e499 --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/sanitise-constructor/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '$kotlin_version' + id 'net.corda.plugins.jar-filter' +} +apply from: 'repositories.gradle' + +sourceSets { + main { + kotlin { + srcDir files( + '../resources/test/sanitise-constructor/kotlin', + '../resources/test/annotations/kotlin' + ) + } + } +} + +dependencies { + compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + compileOnly files('../../unwanteds/build/libs/unwanteds.jar') +} + +jar { + baseName = 'sanitise-constructor' +} + +import net.corda.gradle.jarfilter.JarFilterTask +task jarFilter(type: JarFilterTask) { + jars jar + annotations { + forDelete = ["net.corda.gradle.jarfilter.DeleteMe"] + forSanitise = ["net.corda.gradle.jarfilter.DeleteMe"] + } +} diff --git a/buildSrc/jarfilter/src/test/resources/sanitise-constructor/kotlin/net/corda/gradle/HasOverloadedConstructors.kt b/buildSrc/jarfilter/src/test/resources/sanitise-constructor/kotlin/net/corda/gradle/HasOverloadedConstructors.kt new file mode 100644 index 0000000000..434244befc --- /dev/null +++ b/buildSrc/jarfilter/src/test/resources/sanitise-constructor/kotlin/net/corda/gradle/HasOverloadedConstructors.kt @@ -0,0 +1,34 @@ +@file:Suppress("UNUSED") +package net.corda.gradle + +import net.corda.gradle.jarfilter.DeleteMe +import net.corda.gradle.unwanted.* + +private const val DEFAULT_MESSAGE = "" + +class HasOverloadedStringConstructor @JvmOverloads @DeleteMe constructor(private val message: String = DEFAULT_MESSAGE) : HasString { + override fun stringData(): String = message +} + +class HasOverloadedLongConstructor @JvmOverloads @DeleteMe constructor(private val data: Long = 0) : HasLong { + override fun longData(): Long = data +} + +class HasOverloadedIntConstructor @JvmOverloads @DeleteMe constructor(private val data: Int = 0) : HasInt { + override fun intData(): Int = data +} + +class HasMultipleStringConstructors(private val message: String) : HasString { + @DeleteMe constructor() : this(DEFAULT_MESSAGE) + override fun stringData(): String = message +} + +class HasMultipleLongConstructors(private val data: Long) : HasLong { + @DeleteMe constructor() : this(0) + override fun longData(): Long = data +} + +class HasMultipleIntConstructors(private val data: Int) : HasInt { + @DeleteMe constructor() : this(0) + override fun intData(): Int = data +} diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt index 4f952a93a9..a5c458fb89 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/StringToMethodCallParser.kt @@ -107,7 +107,7 @@ open class StringToMethodCallParser @JvmOverloads constructor( protected val methodMap: Multimap = methodsFromType(targetType) /** A map of method name to parameter names for the target type. */ - val methodParamNames: Map> = targetType.declaredMethods.mapNotNull { + val methodParamNames: Map> = targetType.declaredMethods.filterNot(Method::isSynthetic).mapNotNull { try { it.name to paramNamesFromMethod(it) } catch (e: KotlinReflectionInternalError) { diff --git a/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt b/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt index 2b31f825d8..4d3f09b32b 100644 --- a/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt +++ b/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt @@ -27,7 +27,7 @@ import net.corda.finance.flows.CashPaymentFlow.PaymentRequest open class EventGenerator(val parties: List, val currencies: List, val notary: Party) { protected val partyGenerator = Generator.pickOne(parties) - protected val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) } + protected val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes.of(number.toByte()) } protected val amountGenerator = Generator.longRange(10000, 1000000) protected val currencyGenerator = Generator.pickOne(currencies) protected val currencyMap: MutableMap = mutableMapOf(USD to 0L, GBP to 0L) // Used for estimation of how much money we have in general. diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/FlowsExecutionModeRpcTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/FlowsExecutionModeRpcTest.kt index 87ecd1d0a6..8dd2b3c119 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/FlowsExecutionModeRpcTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/FlowsExecutionModeRpcTest.kt @@ -54,7 +54,7 @@ class FlowsExecutionModeRpcTest : IntegrationTest() { assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")) val user = User("mark", "dadada", setOf(invokeRpc("setFlowsDrainingModeEnabled"), invokeRpc("isFlowsDrainingModeEnabled"))) - driver(DriverParameters(isDebug = true, startNodesInProcess = true)) { + driver(DriverParameters(isDebug = true, inMemoryDB = false, startNodesInProcess = true)) { val nodeName = { val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() val nodeName = nodeHandle.nodeInfo.chooseIdentity().name diff --git a/constants.properties b/constants.properties index 8a3a81d761..652116e765 100644 --- a/constants.properties +++ b/constants.properties @@ -9,7 +9,7 @@ # gradlePluginsVersion=4.0.25 -kotlinVersion=1.2.50 +kotlinVersion=1.2.51 platformVersion=4 guavaVersion=21.0 proguardVersion=6.0.3 diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle index c2d2f65f79..cceb9d930c 100644 --- a/core-deterministic/build.gradle +++ b/core-deterministic/build.gradle @@ -86,7 +86,7 @@ task predeterminise(type: ProGuardTask) { keepattributes '*' keepdirectories - dontwarn '**$1$1' + dontwarn '**$1$1,org.hibernate.annotations.*' dontpreverify dontobfuscate dontoptimize @@ -112,7 +112,11 @@ task jarFilter(type: JarFilterTask) { "net.corda.core.StubOutForDJVM" ] forRemove = [ - "co.paralleluniverse.fibers.Suspendable" + "co.paralleluniverse.fibers.Suspendable", + "org.hibernate.annotations.Immutable" + ] + forSanitise = [ + "net.corda.core.DeleteForDJVM" ] } } diff --git a/core-deterministic/testing/build.gradle b/core-deterministic/testing/build.gradle index 89f06e350c..34e184bbd5 100644 --- a/core-deterministic/testing/build.gradle +++ b/core-deterministic/testing/build.gradle @@ -23,3 +23,6 @@ dependencies { testCompile "org.assertj:assertj-core:$assertj_version" testCompile "junit:junit:$junit_version" } + +// This module has no artifact and only contains tests. +jar.enabled = false diff --git a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/KeyStoreGenerator.kt b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/KeyStoreGenerator.kt index f14f933f10..a1511fbb42 100644 --- a/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/KeyStoreGenerator.kt +++ b/core-deterministic/testing/data/src/test/kotlin/net/corda/deterministic/data/KeyStoreGenerator.kt @@ -5,7 +5,6 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder import java.io.OutputStream -import java.math.BigInteger import java.math.BigInteger.TEN import java.security.KeyPairGenerator import java.security.KeyStore diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt index 5b66cf520e..0232711dd8 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt @@ -11,7 +11,7 @@ class PrivacySaltTest { @Test fun testValidSalt() { - PrivacySalt(ByteArray(SALT_SIZE, { 0x14 })) + PrivacySalt(ByteArray(SALT_SIZE) { 0x14 }) } @Test @@ -22,13 +22,13 @@ class PrivacySaltTest { @Test fun testTooShortPrivacySalt() { - val ex = assertFailsWith { PrivacySalt(ByteArray(SALT_SIZE - 1, { 0x7f })) } + val ex = assertFailsWith { PrivacySalt(ByteArray(SALT_SIZE - 1) { 0x7f }) } assertEquals("Privacy salt should be 32 bytes.", ex.message) } @Test fun testTooLongPrivacySalt() { - val ex = assertFailsWith { PrivacySalt(ByteArray(SALT_SIZE + 1, { 0x7f })) } + val ex = assertFailsWith { PrivacySalt(ByteArray(SALT_SIZE + 1) { 0x7f }) } assertEquals("Privacy salt should be 32 bytes.", ex.message) } } \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/UniqueIdentifierTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/UniqueIdentifierTest.kt new file mode 100644 index 0000000000..030f10fac3 --- /dev/null +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/UniqueIdentifierTest.kt @@ -0,0 +1,37 @@ +package net.corda.deterministic.contracts + +import net.corda.core.contracts.UniqueIdentifier +import org.assertj.core.api.Assertions.assertThat +import org.junit.Assert.* +import org.junit.Test +import java.util.* +import kotlin.reflect.full.primaryConstructor +import kotlin.test.assertFailsWith + +class UniqueIdentifierTest { + private companion object { + private const val NAME = "MyName" + private val TEST_UUID: UUID = UUID.fromString("00000000-1111-2222-3333-444444444444") + } + + @Test + fun testNewInstance() { + val id = UniqueIdentifier(NAME, TEST_UUID) + assertEquals("${NAME}_$TEST_UUID", id.toString()) + assertEquals(NAME, id.externalId) + assertEquals(TEST_UUID, id.id) + } + + @Test + fun testPrimaryConstructor() { + val primary = UniqueIdentifier::class.primaryConstructor ?: throw AssertionError("primary constructor missing") + assertThat(primary.call(NAME, TEST_UUID)).isEqualTo(UniqueIdentifier(NAME, TEST_UUID)) + } + + @Test + fun testConstructors() { + assertEquals(1, UniqueIdentifier::class.constructors.size) + val ex = assertFailsWith { UniqueIdentifier::class.constructors.first().call() } + assertThat(ex).hasMessage("Callable expects 2 arguments, but 0 were provided.") + } +} \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt index a9a069f589..de90178f22 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt @@ -33,7 +33,7 @@ class SecureHashTest { @Test fun testConstants() { assertArrayEquals(SecureHash.zeroHash.bytes, ByteArray(32)) - assertArrayEquals(SecureHash.allOnesHash.bytes, ByteArray(32, { 0xFF.toByte() })) + assertArrayEquals(SecureHash.allOnesHash.bytes, ByteArray(32) { 0xFF.toByte() }) } } diff --git a/core/src/main/kotlin/net/corda/core/contracts/UniqueIdentifier.kt b/core/src/main/kotlin/net/corda/core/contracts/UniqueIdentifier.kt index 5f6e95adeb..2514ef54de 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/UniqueIdentifier.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/UniqueIdentifier.kt @@ -30,10 +30,7 @@ import java.util.* */ @CordaSerializable @KeepForDJVM -data class UniqueIdentifier(val externalId: String?, val id: UUID) : Comparable { - @DeleteForDJVM constructor(externalId: String?) : this(externalId, UUID.randomUUID()) - @DeleteForDJVM constructor() : this(null) - +data class UniqueIdentifier @JvmOverloads @DeleteForDJVM constructor(val externalId: String? = null, val id: UUID = UUID.randomUUID()) : Comparable { override fun toString(): String = if (externalId != null) "${externalId}_$id" else id.toString() companion object { diff --git a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt index 4eae2b2356..a018f71125 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt @@ -104,7 +104,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { * This field provides more intuitive access from Java. */ @JvmField - val zeroHash: SHA256 = SecureHash.SHA256(ByteArray(32, { 0.toByte() })) + val zeroHash: SHA256 = SecureHash.SHA256(ByteArray(32) { 0.toByte() }) /** * A SHA-256 hash value consisting of 32 0x00 bytes. @@ -118,7 +118,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { * This field provides more intuitive access from Java. */ @JvmField - val allOnesHash: SHA256 = SecureHash.SHA256(ByteArray(32, { 255.toByte() })) + val allOnesHash: SHA256 = SecureHash.SHA256(ByteArray(32) { 255.toByte() }) /** * A SHA-256 hash value consisting of 32 0xFF bytes. diff --git a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt index 4d5cf9e683..55bf763716 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt @@ -15,6 +15,7 @@ import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.toHexString +import org.hibernate.annotations.Immutable import java.io.Serializable import javax.persistence.Column import javax.persistence.Embeddable @@ -100,6 +101,7 @@ class PersistentState(@EmbeddedId var stateRef: PersistentStateRef? = null) : St */ @KeepForDJVM @Embeddable +@Immutable data class PersistentStateRef( @Column(name = "transaction_id", length = 64, nullable = false) var txId: String, @@ -114,7 +116,8 @@ data class PersistentStateRef( * Marker interface to denote a persistable Corda state entity that will always have a transaction id and index */ @KeepForDJVM -interface StatePersistable : Serializable +interface StatePersistable + object MappedSchemaValidator { fun fieldsFromOtherMappedSchema(schema: MappedSchema) : List = schema.mappedTypes.map { entity -> diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 615bcea4cc..a803523fdc 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -12,6 +12,9 @@ Unreleased and within that file the ``bridgeMode`` propety has been modified to ``firewallMode`` for overall consistency. This will be a breaking change for early adopters and their deployments, but hopefully will be more future proof. +* The Corda JPA entities no longer implement java.io.Serializable, as this was causing persistence errors in obscure cases. + Java serialization is disabled globally in the node, but in the unlikely event you were relying on these types being Java serializable please contact us. + * Remove all references to the out-of-process transaction verification. * Introduced a hierarchy of ``DatabaseMigrationException``s, allowing ``NodeStartup`` to gracefully inform users of problems related to database migrations before exiting with a non-zero code. diff --git a/docs/source/corda-nodes-index.rst b/docs/source/corda-nodes-index.rst index 85b00fcc1c..68b991d96b 100644 --- a/docs/source/corda-nodes-index.rst +++ b/docs/source/corda-nodes-index.rst @@ -12,6 +12,7 @@ Nodes clientrpc shell node-database + node-database-access-h2 node-administration node-operations-upgrading node-operations-upgrade-cordapps diff --git a/docs/source/design/sgx-infrastructure/Example SGX deployment.png b/docs/source/design/sgx-infrastructure/ExampleSGXdeployment.png similarity index 100% rename from docs/source/design/sgx-infrastructure/Example SGX deployment.png rename to docs/source/design/sgx-infrastructure/ExampleSGXdeployment.png diff --git a/docs/source/design/sgx-infrastructure/design.md b/docs/source/design/sgx-infrastructure/design.md index 2bcdce7b7a..0c3bf30ef1 100644 --- a/docs/source/design/sgx-infrastructure/design.md +++ b/docs/source/design/sgx-infrastructure/design.md @@ -10,34 +10,34 @@ Kubernetes for parts specific to that. The main idea behind the infrastructure is to provide a highly available cluster of enclave services (hosts) which can serve enclaves on demand. It provides an interface for enclave business logic that's agnostic with regards to the -infrastructure, similar to [serverless architectures](details/serverless.md). The enclaves will use an opaque reference -to other enclaves or services in the form of [enclave channels](details/channels.md). Channels hides attestation details +infrastructure, similar to serverless architectures. The enclaves will use an opaque reference +to other enclaves or services in the form of enclave channels. Channels hides attestation details and provide a loose coupling between enclave/non-enclave functionality and specific enclave images/services implementing it. This loose coupling allows easier upgrade of enclaves, relaxed trust (whitelisting), dynamic deployment, and horizontal scaling as we can spin up enclaves dynamically on demand when a channel is requested. +For more information see: + +.. toctree:: + :maxdepth: 1 + + details/serverless.md + details/channels.md + ## Infrastructure components Here are the major components of the infrastructure. Note that this doesn't include business logic specific infrastructure pieces (like ORAM blob storage for Corda privacy model integration). -* [**Distributed key-value store**](details/kv-store.md): - Responsible for maintaining metadata about enclaves, hosts, sealed secrets and CPU locality. - -* [**Discovery service**](details/discovery.md) - Responsible for resolving an enclave channel to a specific enclave image and a host that can serve it using the - metadata in the key-value store. - -* [**Enclave host**](details/host.md): - This is a service capable of serving enclaves and driving the underlying traffic. Third party components like Intel's - SGX driver and aesmd also belong here. - -* [**Enclave storage**](details/enclave-storage.md): - Responsible for serving enclave images to hosts. This is a simple static content server. - -* [**IAS proxy**](details/ias-proxy.md): - This is an unfortunate necessity because of Intel's requirement to do mutual TLS with their services. - +.. toctree:: + :maxdepth: 1 + + details/kv-store.md + details/discovery.md + details/host.md + details/enclave-storage.md + details/ias-proxy.md + ## Infrastructure interactions * **Enclave deployment**: @@ -54,17 +54,23 @@ infrastructure pieces (like ORAM blob storage for Corda privacy model integratio ## Decisions to be made -* [**Strategic roadmap**](decisions/roadmap.md) -* [**CPU certification method**](decisions/certification.md) -* [**Enclave language of choice**](decisions/enclave-language.md) -* [**Key-value store**](decisions/kv-store.md) +.. toctree:: + :maxdepth: 1 + + decisions/roadmap.md + decisions/certification.md + decisions/enclave-language.md + decisions/kv-store.md ## Further details -* [**Attestation**](details/attestation.md) -* [**Calendar time for data at rest**](details/time.md) -* [**Enclave deployment**](details/enclave-deployment.md) - +.. toctree:: + :maxdepth: 1 + + details/attestation.md + details/time.md + details/enclave-deployment.md + ## Example deployment This is an example of how two Corda parties may use the above infrastructure. In this example R3 is hosting the IAS @@ -75,4 +81,4 @@ the enclave image store (although R3 will need to have a repository of the signe We may also decide to go the other way and have R3 host the enclave hosts and the discovery service, shared between parties (if e.g. they don't have access to/want to maintain SGX capable boxes). -![Example SGX deployment](Example%20SGX%20deployment.png) \ No newline at end of file +![Example SGX deployment](ExampleSGXdeployment.png) \ No newline at end of file diff --git a/docs/source/deterministic-modules.rst b/docs/source/deterministic-modules.rst index 1571100829..6a97836339 100644 --- a/docs/source/deterministic-modules.rst +++ b/docs/source/deterministic-modules.rst @@ -62,15 +62,16 @@ The build generates each of Corda's deterministic JARs in six steps: @CordaSerializable @KeepForDJVM - data class UniqueIdentifier(val externalId: String?, val id: UUID) : Comparable { - @DeleteForDJVM constructor(externalId: String?) : this(externalId, UUID.randomUUID()) - @DeleteForDJVM constructor() : this(null) + data class UniqueIdentifier @JvmOverloads @DeleteForDJVM constructor( + val externalId: String? = null, + val id: UUID = UUID.randomUUID() + ) : Comparable { ... } .. - While CorDapps will definitely need to handle ``UniqueIdentifier`` objects, both of the secondary constructors + While CorDapps will definitely need to handle ``UniqueIdentifier`` objects, all of the secondary constructors generate a new random ``UUID`` and so are non-deterministic. Hence the next "determinising" step is to pass the classes to the ``JarFilter`` tool, which strips out all of the elements which have been annotated as ``@DeleteForDJVM`` and stubs out any functions annotated with ``@StubOutForDJVM``. (Stub functions that @@ -270,11 +271,34 @@ Non-Deterministic Elements .. You must also ensure that a deterministic class's primary constructor does not reference any classes that are - not available in the deterministic ``rt.jar``, nor have any non-deterministic default parameter values such as - ``UUID.randomUUID()``. The biggest risk here would be that ``JarFilter`` would delete the primary constructor - and that the class could no longer be instantiated, although ``JarFilter`` will print a warning in this case. - However, it is also likely that the "determinised" class would have a different serialisation signature than - its non-deterministic version and so become unserialisable on the deterministic JVM. + not available in the deterministic ``rt.jar``. The biggest risk here would be that ``JarFilter`` would delete the + primary constructor and that the class could no longer be instantiated, although ``JarFilter`` will print a warning + in this case. However, it is also likely that the "determinised" class would have a different serialisation + signature than its non-deterministic version and so become unserialisable on the deterministic JVM. + + Primary constructors that have non-deterministic default parameter values must still be annotated as + ``@DeleteForDJVM`` because they cannot be refactored without breaking Corda's binary interface. The Kotlin compiler + will automatically apply this ``@DeleteForDJVM`` annotation - along with any others - to all of the class's + secondary constructors too. The ``JarFilter`` plugin can then remove the ``@DeleteForDJVM`` annotation from the + primary constructor so that it can subsequently delete only the secondary constructors. + + The annotations that ``JarFilter`` will "sanitise" from primary constructors in this way are listed in the plugin's + configuration block, e.g. + + .. sourcecode:: groovy + + task jarFilter(type: JarFilterTask) { + ... + annotations { + ... + + forSanitise = [ + "net.corda.core.DeleteForDJVM" + ] + } + } + + .. Be aware that package-scoped Kotlin properties are all initialised within a common ```` block inside their host ``.class`` file. This means that when ``JarFilter`` deletes these properties, it cannot also remove diff --git a/docs/source/index.rst b/docs/source/index.rst index 5602ff074b..d99aeb29f9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -60,6 +60,7 @@ application development please continue to refer to `the main project documentat azure-vm.rst aws-vm.rst certificate-revocation + loadtesting.rst .. Documentation is not included in the pdf unless it is included in a toctree somewhere .. only:: pdfmode @@ -68,4 +69,4 @@ application development please continue to refer to `the main project documentat :caption: Other documentation deterministic-modules.rst - changelog.rst + changelog.rst \ No newline at end of file diff --git a/docs/source/running-a-node.rst b/docs/source/running-a-node.rst index dbcf66bc2c..b0563f9dc5 100644 --- a/docs/source/running-a-node.rst +++ b/docs/source/running-a-node.rst @@ -82,8 +82,10 @@ This command line will start the node with JMX metrics accessible via HTTP on po See :ref:`Monitoring your node ` for further details. -Starting all nodes at once from the command line ------------------------------------------------- +Starting all nodes at once on a local machine from the command line +------------------------------------------------------------------- + +.. _starting-all-nodes-at-once: Native ~~~~~~ @@ -115,7 +117,35 @@ After the nodes are started up, you can use ``docker ps`` command to see how the and `Docker Compose documentation `_ for installation instructions for all major operating systems. +Starting all nodes at once on a remote machine from the command line +-------------------------------------------------------------------- + +By default, ``Cordform`` expects the nodes it generates to be run on the same machine where they were generated. +In order to run the nodes remotely, the nodes can be deployed locally and then copied to a remote server. +If after copying the nodes to the remote machine you encounter errors related to ``localhost`` resolution, you will additionally need to follow the steps below. + +To create nodes locally and run on a remote machine perform the following steps: + +1. Configure Cordform task and deploy the nodes locally as described in :doc:`generating-a-node`. + +2. Copy the generated directory structure to a remote machine using e.g. Secure Copy. + +3. Optionally, bootstrap the network on the remote machine. + + This is optional step when a remote machine doesn't accept ``localhost`` addresses, or the generated nodes are configured to run on another host's IP address. + + If required change host addresses in top level configuration files ``[NODE NAME]_node.conf`` for entries ``p2pAddress`` , ``rpcSettings.address`` and ``rpcSettings.adminAddress``. + + Run the network bootstrapper tool to regenerate the nodes network map (see for more explanation :doc:`network-bootstrapper`): + + ``java -jar corda-tools-network-bootstrapper-Master.jar --dir `` + +4. Run nodes on the remote machine using :ref:`runnodes command `. + +The above steps create a test deployment as ``deployNodes`` Gradle task would do on a local machine. + Database migrations ------------------- Depending on the versions of Corda and of the CorDapps used, database migration scripts might need to run before a node is able to start. -For more information refer to :doc:`database-management`. \ No newline at end of file +For more information refer to :doc:`database-management`. + diff --git a/docs/source/serialization-index.rst b/docs/source/serialization-index.rst index c5c35014a9..f59f687048 100644 --- a/docs/source/serialization-index.rst +++ b/docs/source/serialization-index.rst @@ -2,7 +2,6 @@ Serialization ============= .. toctree:: - :caption: Other docs :maxdepth: 1 diff --git a/docs/source/shell.rst b/docs/source/shell.rst index 5b9623e167..42a8e92847 100644 --- a/docs/source/shell.rst +++ b/docs/source/shell.rst @@ -33,8 +33,10 @@ with the node using RPC calls. The shell via the local terminal -------------------------------- -In development mode, the shell will display in the node's terminal window. -The shell connects to the node as 'shell' user with password 'shell' which is only available in dev mode. +.. note:: Local terminal shell works only in development mode! + +The shell will display in the node's terminal window. It connects to the node as 'shell' user with password 'shell' +(which is only available in dev mode). It may be disabled by passing the ``--no-local-shell`` flag when running the node. .. _ssh_server: diff --git a/node/src/integration-test/kotlin/net/corda/node/persistence/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/node/persistence/NodeStatePersistenceTests.kt index e32ada9538..934438739e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/persistence/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/persistence/NodeStatePersistenceTests.kt @@ -57,7 +57,7 @@ class NodeStatePersistenceTests : IntegrationTest() { fun `persistent state survives node restart`() { val user = User("mark", "dadada", setOf(startFlow(), invokeRpc("vaultQuery"))) val message = Message("Hello world!") - val stateAndRef: StateAndRef? = driver(DriverParameters(isDebug = true, startNodesInProcess = isQuasarAgentSpecified(), + val stateAndRef: StateAndRef? = driver(DriverParameters(isDebug = true, inMemoryDB = false, startNodesInProcess = isQuasarAgentSpecified(), portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) { val nodeName = { val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() @@ -90,7 +90,7 @@ class NodeStatePersistenceTests : IntegrationTest() { val user = User("mark", "dadada", setOf(startFlow(), invokeRpc("vaultQuery"))) val message = Message("Hello world!") - val stateAndRef: StateAndRef? = driver(DriverParameters(isDebug = true, startNodesInProcess = isQuasarAgentSpecified(), portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) { + val stateAndRef: StateAndRef? = driver(DriverParameters(isDebug = true, inMemoryDB = false, startNodesInProcess = isQuasarAgentSpecified(), portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) { val nodeName = { val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() val nodeName = nodeHandle.nodeInfo.singleIdentity().name diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/HardRestartTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/HardRestartTest.kt index 6ee3682456..3639234221 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/HardRestartTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/HardRestartTest.kt @@ -70,7 +70,7 @@ class HardRestartTest : IntegrationTest() { @Test fun restartShortPingPongFlowRandomly() { val demoUser = User("demo", "demo", setOf(Permissions.startFlow(), Permissions.all())) - driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) { + driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) { val (a, b) = listOf( startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")), startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000")) @@ -102,7 +102,7 @@ class HardRestartTest : IntegrationTest() { @Test fun restartLongPingPongFlowRandomly() { val demoUser = User("demo", "demo", setOf(Permissions.startFlow(), Permissions.all())) - driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) { + driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) { val (a, b) = listOf( startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")), startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000")) @@ -134,7 +134,7 @@ class HardRestartTest : IntegrationTest() { @Test fun softRestartLongPingPongFlowRandomly() { val demoUser = User("demo", "demo", setOf(Permissions.startFlow(), Permissions.all())) - driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) { + driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) { val (a, b) = listOf( startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")), startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000")) @@ -210,7 +210,7 @@ class HardRestartTest : IntegrationTest() { @Test fun restartRecursiveFlowRandomly() { val demoUser = User("demo", "demo", setOf(Permissions.startFlow(), Permissions.all())) - driver(DriverParameters(isDebug = true, startNodesInProcess = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) { + driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) { val (a, b) = listOf( startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:30000")), startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), customOverrides = mapOf("p2pAddress" to "localhost:40000")) diff --git a/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt b/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt index e27661cc51..34d02907a4 100644 --- a/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt +++ b/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt @@ -20,7 +20,6 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.services.persistence.NodePropertiesPersistentStore -import java.io.Serializable import javax.persistence.* object NodeInfoSchema @@ -66,7 +65,7 @@ object NodeInfoSchemaV1 : MappedSchema( */ @Column(name = "serial", nullable = false) val serial: Long - ) : Serializable { + ) { fun toNodeInfo(): NodeInfo { return NodeInfo( this.addresses.map { it.toHostAndPort() }, @@ -86,7 +85,7 @@ object NodeInfoSchemaV1 : MappedSchema( var id: Int, val host: String? = null, val port: Int? = null - ) : Serializable { + ) { companion object { fun fromHostAndPort(hostAndPort: NetworkHostAndPort) = DBHostAndPort( 0, hostAndPort.host, hostAndPort.port @@ -119,7 +118,7 @@ object NodeInfoSchemaV1 : MappedSchema( @ManyToMany(mappedBy = "legalIdentitiesAndCerts", cascade = [(CascadeType.ALL)]) // ManyToMany because of distributed services. private val persistentNodeInfos: Set = emptySet() - ) : Serializable { + ) { constructor(partyAndCert: PartyAndCertificate, isMain: Boolean = false) : this(partyAndCert.name.toString(), partyAndCert.party.owningKey.toStringShort(), diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index bc69b9987c..219f5a994d 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -21,13 +21,9 @@ import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.StateRef import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogicRefFactory -import net.corda.core.internal.FlowStateMachine -import net.corda.core.internal.ThreadBox -import net.corda.core.internal.VisibleForTesting +import net.corda.core.internal.* import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.join -import net.corda.core.internal.until import net.corda.core.node.ServicesForResolution import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.SingletonSerializeAsToken @@ -47,18 +43,9 @@ import net.corda.nodeapi.internal.persistence.contextTransaction import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.mina.util.ConcurrentHashSet import org.slf4j.Logger -import java.io.Serializable import java.time.Duration import java.time.Instant -import java.util.concurrent.CancellationException -import java.util.concurrent.CompletableFuture -import java.util.concurrent.CompletionStage -import java.util.concurrent.ExecutionException -import java.util.concurrent.Executor -import java.util.concurrent.Executors -import java.util.concurrent.Future -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException +import java.util.concurrent.* import javax.annotation.concurrent.ThreadSafe import javax.persistence.Column import javax.persistence.EmbeddedId @@ -159,7 +146,7 @@ class NodeSchedulerService(private val clock: CordaClock, @Column(name = "scheduled_at", nullable = false) var scheduledAt: Instant = Instant.now() - ) : Serializable + ) private class InnerState { var rescheduled: GuavaSettableFuture? = null diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index a8ce8c8a7e..8224b5759e 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -29,7 +29,6 @@ import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY -import java.io.Serializable import java.security.InvalidAlgorithmParameterException import java.security.PublicKey import java.security.cert.* @@ -96,7 +95,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, @Lob @Column(name = "identity_value", nullable = false) var identity: ByteArray = EMPTY_BYTE_ARRAY - ) : Serializable + ) @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}named_identities") @@ -107,7 +106,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, @Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE, nullable = true) var publicKeyHash: String? = "" - ) : Serializable + ) override val caCertStore: CertStore override val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null) diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index c569c71f3b..0f478401ba 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -21,7 +21,6 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.bouncycastle.operator.ContentSigner -import java.io.Serializable import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey @@ -54,7 +53,7 @@ class PersistentKeyManagementService(val identityService: IdentityService, @Lob @Column(name = "private_key", nullable = false) var privateKey: ByteArray = EMPTY_BYTE_ARRAY - ) : Serializable { + ) { constructor(publicKey: PublicKey, privateKey: PrivateKey) : this(publicKey.toStringShort(), publicKey.encoded, privateKey.encoded) } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt index fdeb3ffa37..0418f95e65 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessageDeduplicator.kt @@ -17,7 +17,6 @@ import net.corda.node.services.statemachine.DeduplicationId import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX -import java.io.Serializable import java.time.Instant import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit @@ -161,7 +160,7 @@ class P2PMessageDeduplicator(private val database: CordaPersistence) { @Column(name = "sequence_number", nullable = true) var seqNo: Long? = null - ) : Serializable + ) private data class MessageMeta(val insertionTime: Instant, val senderHash: String?, val senderSeqNo: Long?) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt index 4aadad235a..ac71d2604b 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt @@ -46,7 +46,7 @@ class DBCheckpointStorage : CheckpointStorage { @Lob @Column(name = "checkpoint_value", nullable = false) var checkpoint: ByteArray = EMPTY_BYTE_ARRAY - ) : Serializable + ) override fun addCheckpoint(id: StateMachineRunId, checkpoint: SerializedBytes) { currentDBSession().saveOrUpdate(DBCheckpoint().apply { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt index 2af6d459bd..6b13b24f56 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt @@ -23,7 +23,6 @@ import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction import rx.subjects.PublishSubject -import java.io.Serializable import java.util.* import javax.annotation.concurrent.ThreadSafe import javax.persistence.Column @@ -48,7 +47,7 @@ class DBTransactionMappingStorage(private val database: CordaPersistence) : Stat @Column(name = "state_machine_run_id", length = 36, nullable = true) var stateMachineRunId: String? = "" - ) : Serializable + ) private companion object { fun createMap(): AppendOnlyPersistentMap { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt index 77e668228f..c3561b2ce1 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt @@ -18,11 +18,7 @@ import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.concurrent.doneFuture import net.corda.core.messaging.DataFeed -import net.corda.core.serialization.SerializationDefaults -import net.corda.core.serialization.SerializedBytes -import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize +import net.corda.core.serialization.* import net.corda.core.toFuture import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.SignedTransaction @@ -37,12 +33,7 @@ import net.corda.serialization.internal.CordaSerializationEncoding.SNAPPY import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import rx.Observable import rx.subjects.PublishSubject -import java.io.Serializable -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.Id -import javax.persistence.Lob -import javax.persistence.Table +import javax.persistence.* // cache value type to just store the immutable bits of a signed transaction plus conversion helpers typealias TxCacheValue = Pair, List> @@ -62,7 +53,7 @@ class DBTransactionStorage(cacheSizeBytes: Long, private val database: CordaPers @Lob @Column(name = "transaction_value", nullable = false) var transaction: ByteArray = EMPTY_BYTE_ARRAY - ) : Serializable + ) private companion object { fun createTransactionsMap(maxSizeInBytes: Long) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index b88782b9cc..549fb619f4 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -30,11 +30,7 @@ import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.vault.AttachmentQueryCriteria import net.corda.core.node.services.vault.AttachmentSort -import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.SerializationToken -import net.corda.core.serialization.SerializeAsToken -import net.corda.core.serialization.SerializeAsTokenContext -import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.serialization.* import net.corda.core.utilities.contextLogger import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser @@ -48,22 +44,12 @@ import net.corda.nodeapi.internal.withContractsInJar import java.io.FilterInputStream import java.io.IOException import java.io.InputStream -import java.io.Serializable import java.nio.file.Paths import java.time.Instant import java.util.* import java.util.jar.JarInputStream import javax.annotation.concurrent.ThreadSafe -import javax.persistence.CollectionTable -import javax.persistence.Column -import javax.persistence.ElementCollection -import javax.persistence.Entity -import javax.persistence.ForeignKey -import javax.persistence.Id -import javax.persistence.Index -import javax.persistence.JoinColumn -import javax.persistence.Lob -import javax.persistence.Table +import javax.persistence.* /** * Stores attachments using Hibernate to database. @@ -125,7 +111,7 @@ class NodeAttachmentService( @CollectionTable(name = "${NODE_DATABASE_PREFIX}attachments_contracts", joinColumns = [(JoinColumn(name = "att_id", referencedColumnName = "att_id"))], foreignKey = ForeignKey(name = "FK__ctr_class__attachments")) var contractClassNames: List? = null - ) : Serializable + ) @VisibleForTesting var checkAttachmentsOnLoad = true diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodePropertiesPersistentStore.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodePropertiesPersistentStore.kt index d90ab42547..e7c16d6ba1 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodePropertiesPersistentStore.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodePropertiesPersistentStore.kt @@ -19,7 +19,6 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import org.slf4j.Logger import rx.subjects.PublishSubject -import java.io.Serializable import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Id @@ -45,7 +44,7 @@ class NodePropertiesPersistentStore(readPhysicalNodeId: () -> String, persistenc @Column(name = "property_value", nullable = true) var value: String? = "" - ) : Serializable + ) } private class FlowsDrainingModeOperationsImpl(readPhysicalNodeId: () -> String, private val persistence: CordaPersistence, logger: Logger) : FlowsDrainingModeOperations { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt index b935c898fd..5f7cb332b9 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt @@ -32,7 +32,6 @@ import net.corda.core.utilities.debug import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.currentDBSession -import java.io.Serializable import java.time.Clock import java.time.Instant import java.util.* @@ -49,7 +48,7 @@ class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, Singl @Column(name = "consuming_transaction_id", nullable = true) val consumingTxHash: String? - ) : Serializable + ) @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_request_log") @@ -72,7 +71,7 @@ class PersistentUniquenessProvider(val clock: Clock) : UniquenessProvider, Singl @Column(name = "request_timestamp", nullable = false) var requestDate: Instant - ) : Serializable + ) @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_states") diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 74489a8f14..e90268145b 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -42,7 +42,6 @@ import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX -import java.io.Serializable import java.nio.file.Path import java.time.Clock import java.util.concurrent.CompletableFuture @@ -104,7 +103,7 @@ class RaftUniquenessProvider( var value: String? = "", @Column(name = "raft_log_index", nullable = false) var index: Long = 0 - ) : Serializable + ) /** Directory storing the Raft log and state machine snapshots */ private val storagePath: Path = transportConfiguration.baseDirectory diff --git a/node/src/main/kotlin/net/corda/node/services/upgrade/ContractUpgradeServiceImpl.kt b/node/src/main/kotlin/net/corda/node/services/upgrade/ContractUpgradeServiceImpl.kt index dddaf4221d..cfb65f3b4e 100644 --- a/node/src/main/kotlin/net/corda/node/services/upgrade/ContractUpgradeServiceImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/upgrade/ContractUpgradeServiceImpl.kt @@ -14,9 +14,8 @@ import net.corda.core.contracts.StateRef import net.corda.core.contracts.UpgradedContract import net.corda.core.node.services.ContractUpgradeService import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.node.utilities.PersistentMap -import java.io.Serializable +import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Id @@ -34,7 +33,7 @@ class ContractUpgradeServiceImpl : ContractUpgradeService, SingletonSerializeAsT /** refers to the UpgradedContract class name*/ @Column(name = "contract_class_name", nullable = true) var upgradedContractClassName: String? = "" - ) : Serializable + ) private companion object { fun createContractUpgradesMap(): PersistentMap { diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt index 17e92153ee..4b1d333c01 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt @@ -21,7 +21,6 @@ import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes import org.hibernate.annotations.Type -import java.io.Serializable import java.time.Instant import java.util.* import javax.persistence.* @@ -167,7 +166,7 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio @Column(name = "note", nullable = true) var note: String? - ) : Serializable { + ) { constructor(txId: String, note: String) : this(0, txId, note) } } \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/serialization/kryo/KryoTests.kt b/node/src/test/kotlin/net/corda/node/serialization/kryo/KryoTests.kt index 91c23c15e1..83f040f025 100644 --- a/node/src/test/kotlin/net/corda/node/serialization/kryo/KryoTests.kt +++ b/node/src/test/kotlin/net/corda/node/serialization/kryo/KryoTests.kt @@ -180,7 +180,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { @Test fun `InputStream serialisation`() { - val rubbish = ByteArray(12345, { (it * it * 0.12345).toByte() }) + val rubbish = ByteArray(12345) { (it * it * 0.12345).toByte() } val readRubbishStream: InputStream = rubbish.inputStream().serialize(factory, context).deserialize(factory, context) for (i in 0..12344) { assertEquals(rubbish[i], readRubbishStream.read().toByte()) @@ -218,7 +218,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { @Test fun `HashCheckingStream (de)serialize`() { - val rubbish = ByteArray(12345, { (it * it * 0.12345).toByte() }) + val rubbish = ByteArray(12345) { (it * it * 0.12345).toByte() } val readRubbishStream: InputStream = NodeAttachmentService.HashCheckingStream( SecureHash.sha256(rubbish), rubbish.size, diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt index 9dba0ee9a6..b6ec99f77d 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/AppendOnlyPersistentMapTest.kt @@ -6,14 +6,12 @@ import net.corda.node.internal.configureDatabase import net.corda.node.services.schema.NodeSchemaService import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.persistence.DatabaseConfig - import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.junit.After import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -import java.io.Serializable import java.util.concurrent.CountDownLatch import javax.persistence.Column import javax.persistence.Entity @@ -270,7 +268,7 @@ class AppendOnlyPersistentMapTest(var scenario: Scenario) { @Column(name = "value", length = 16) var value: String = "" - ) : Serializable + ) class TestMap : AppendOnlyPersistentMap( toPersistentEntityKey = { it }, diff --git a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt index c8dd22f3eb..8ad7bf50ce 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt @@ -30,7 +30,6 @@ import org.hibernate.annotations.Cascade import org.hibernate.annotations.CascadeType import org.junit.Ignore import org.junit.Test -import java.io.Serializable import javax.persistence.* import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -155,7 +154,7 @@ object TestSchema : MappedSchema(SchemaFamily::class.java, 1, setOf(Parent::clas @Suppress("unused") @Entity @Table(name = "children") - class Child : Serializable { + class Child { @Id @GeneratedValue @Column(name = "child_id", unique = true, nullable = false) diff --git a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt index 725a14c756..b63a197495 100644 --- a/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt +++ b/samples/trader-demo/src/integration-test/kotlin/net/corda/traderdemo/TraderDemoTest.kt @@ -54,7 +54,7 @@ class TraderDemoTest : IntegrationTest() { startFlow(), startFlow(), all())) - driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) { + driver(DriverParameters(startNodesInProcess = true, inMemoryDB = false, extraCordappPackagesToScan = listOf("net.corda.finance"))) { val (nodeA, nodeB, bankNode) = listOf( startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser)), startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser)), @@ -96,7 +96,7 @@ class TraderDemoTest : IntegrationTest() { @Test fun `Tudor test`() { - driver(DriverParameters(isDebug = true, startNodesInProcess = false, extraCordappPackagesToScan = listOf("net.corda.finance"))) { + driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, extraCordappPackagesToScan = listOf("net.corda.finance"))) { val demoUser = User("demo", "demo", setOf(startFlow(), all())) val bankUser = User("user1", "test", permissions = setOf(all())) val (nodeA, nodeB, bankNode) = listOf( diff --git a/serialization-deterministic/build.gradle b/serialization-deterministic/build.gradle index fb023fd2f0..e8bd17131b 100644 --- a/serialization-deterministic/build.gradle +++ b/serialization-deterministic/build.gradle @@ -104,6 +104,9 @@ task jarFilter(type: JarFilterTask) { forRemove = [ "co.paralleluniverse.fibers.Suspendable" ] + forSanitise = [ + "net.corda.core.DeleteForDJVM" + ] } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 8afd6cb7f0..0266b0196d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -244,7 +244,8 @@ fun driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr jmxPolicy = defaultParameters.jmxPolicy, compatibilityZone = null, networkParameters = defaultParameters.networkParameters, - notaryCustomOverrides = defaultParameters.notaryCustomOverrides + notaryCustomOverrides = defaultParameters.notaryCustomOverrides, + inMemoryDB = defaultParameters.inMemoryDB ), coerce = { it }, dsl = dsl, @@ -277,6 +278,10 @@ fun driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr * @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be * empty as notaries are defined by [notarySpecs]. * @property notaryCustomOverrides Extra settings that need to be passed to the notary. + * @property initialiseSerialization Indicates whether to initialized the serialization subsystem. + * @property inMemoryDB Whether to use in-memory H2 for new nodes rather then on-disk (the node starts quicker, however + * the data is not persisted between node restarts). Has no effect if node is configured + * in any way to use database other than H2. */ @Suppress("unused") data class DriverParameters( @@ -293,7 +298,8 @@ data class DriverParameters( val jmxPolicy: JmxPolicy = JmxPolicy(), val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()), val notaryCustomOverrides: Map = emptyMap(), - val initialiseSerialization: Boolean = true + val initialiseSerialization: Boolean = true, + val inMemoryDB: Boolean = true ) { constructor( isDebug: Boolean, @@ -322,6 +328,7 @@ data class DriverParameters( jmxPolicy, networkParameters, emptyMap(), + true, true ) @@ -338,7 +345,8 @@ data class DriverParameters( extraCordappPackagesToScan: List, jmxPolicy: JmxPolicy, networkParameters: NetworkParameters, - initialiseSerialization: Boolean + initialiseSerialization: Boolean, + inMemoryDB: Boolean ) : this( isDebug, driverDirectory, @@ -353,7 +361,8 @@ data class DriverParameters( jmxPolicy, networkParameters, emptyMap(), - initialiseSerialization + initialiseSerialization, + inMemoryDB ) fun withIsDebug(isDebug: Boolean): DriverParameters = copy(isDebug = isDebug) @@ -370,6 +379,7 @@ data class DriverParameters( fun withJmxPolicy(jmxPolicy: JmxPolicy): DriverParameters = copy(jmxPolicy = jmxPolicy) fun withNetworkParameters(networkParameters: NetworkParameters): DriverParameters = copy(networkParameters = networkParameters) fun withNotaryCustomOverrides(notaryCustomOverrides: Map): DriverParameters = copy(notaryCustomOverrides = notaryCustomOverrides) + fun withInMemoryDB(inMemoryDB: Boolean): DriverParameters = copy(inMemoryDB = inMemoryDB) fun copy( isDebug: Boolean, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index ab0b9236a3..fd8782edcb 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -84,6 +84,7 @@ import java.util.* import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger import kotlin.collections.ArrayList import kotlin.collections.HashMap import kotlin.concurrent.thread @@ -103,7 +104,8 @@ class DriverDSLImpl( val notarySpecs: List, val compatibilityZone: CompatibilityZoneParams?, val networkParameters: NetworkParameters, - val notaryCustomOverrides: Map + val notaryCustomOverrides: Map, + val inMemoryDB: Boolean ) : InternalDriverDSL { private var _executorService: ScheduledExecutorService? = null @@ -122,6 +124,9 @@ class DriverDSLImpl( private lateinit var _notaries: CordaFuture> override val notaryHandles: List get() = _notaries.getOrThrow() + // While starting with inProcess mode, we need to have different names to avoid clashes + private val inMemoryCounter = AtomicInteger() + interface Waitable { @Throws(InterruptedException::class) fun waitFor() @@ -138,6 +143,16 @@ class DriverDSLImpl( private val jolokiaJarPath: String by lazy { resolveJar(".*jolokia-jvm-.*-agent\\.jar$") } + private fun NodeConfig.checkAndOverrideForInMemoryDB(): NodeConfig = this.run { + if (inMemoryDB && corda.dataSourceProperties.getProperty("dataSource.url").startsWith("jdbc:h2:")) { + val jdbcUrl = "jdbc:h2:mem:persistence${inMemoryCounter.getAndIncrement()};DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=100" + corda.dataSourceProperties.setProperty("dataSource.url", jdbcUrl) + NodeConfig(typesafe = typesafe + mapOf("dataSourceProperties" to mapOf("dataSource.url" to jdbcUrl)), corda = corda) + } else { + this + } + } + private fun resolveJar(jarNamePattern: String): String { return try { val cl = ClassLoader.getSystemClassLoader() @@ -246,7 +261,7 @@ class DriverDSLImpl( baseDirectory = baseDirectory(name), allowMissingConfig = true, configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true) - )) + )).checkAndOverrideForInMemoryDB() return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize, localNetworkMap) } @@ -264,7 +279,7 @@ class DriverDSLImpl( "adminAddress" to portAllocation.nextHostAndPort().toString() ), "devMode" to false) - )) + )).checkAndOverrideForInMemoryDB() val versionInfo = VersionInfo(1, "1", "1", "1") config.corda.certificatesDirectory.createDirectories() @@ -387,7 +402,7 @@ class DriverDSLImpl( configOverrides = rawConfig.toNodeOnly() ) val cordaConfig = typesafe.parseAsNodeConfiguration() - val config = NodeConfig(rawConfig, cordaConfig) + val config = NodeConfig(rawConfig, cordaConfig).checkAndOverrideForInMemoryDB() return startNodeInternal(config, webAddress, null, "512m", localNetworkMap) } @@ -1064,7 +1079,8 @@ fun genericDriver( notarySpecs = defaultParameters.notarySpecs, compatibilityZone = null, networkParameters = defaultParameters.networkParameters, - notaryCustomOverrides = defaultParameters.notaryCustomOverrides + notaryCustomOverrides = defaultParameters.notaryCustomOverrides, + inMemoryDB = defaultParameters.inMemoryDB ) ) val shutdownHook = addShutdownHook(driverDsl::shutdown) @@ -1143,6 +1159,7 @@ fun internalDriver( networkParameters: NetworkParameters = DriverParameters().networkParameters, compatibilityZone: CompatibilityZoneParams? = null, notaryCustomOverrides: Map = DriverParameters().notaryCustomOverrides, + inMemoryDB: Boolean = DriverParameters().inMemoryDB, dsl: DriverDSLImpl.() -> A ): A { return genericDriver( @@ -1160,7 +1177,8 @@ fun internalDriver( jmxPolicy = jmxPolicy, compatibilityZone = compatibilityZone, networkParameters = networkParameters, - notaryCustomOverrides = notaryCustomOverrides + notaryCustomOverrides = notaryCustomOverrides, + inMemoryDB = inMemoryDB ), coerce = { it }, dsl = dsl, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index 894537d493..0e9c2849eb 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -125,6 +125,7 @@ fun rpcDriver( jmxPolicy: JmxPolicy = JmxPolicy(), networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()), notaryCustomOverrides: Map = emptyMap(), + inMemoryDB: Boolean = true, dsl: RPCDriverDSL.() -> A ): A { return genericDriver( @@ -143,7 +144,8 @@ fun rpcDriver( jmxPolicy = jmxPolicy, compatibilityZone = null, networkParameters = networkParameters, - notaryCustomOverrides = notaryCustomOverrides + notaryCustomOverrides = notaryCustomOverrides, + inMemoryDB = inMemoryDB ), externalTrace ), coerce = { it }, diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index 4f104ee301..22fbe98361 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -194,7 +194,7 @@ class ExplorerSimulation(private val options: OptionSet) { for (ref in 0..1) { for ((currency, issuer) in issuers) { val amount = Amount(1_000_000, currency) - issuer.startFlow(::CashIssueAndPaymentFlow, amount, OpaqueBytes(ByteArray(1, { ref.toByte() })), + issuer.startFlow(::CashIssueAndPaymentFlow, amount, OpaqueBytes.of( ref.toByte() ), it, anonymous, notary).returnValue.getOrThrow() } } diff --git a/tools/shell/src/test/java/net/corda/tools/shell/InteractiveShellJavaTest.java b/tools/shell/src/test/java/net/corda/tools/shell/InteractiveShellJavaTest.java new file mode 100644 index 0000000000..498482b057 --- /dev/null +++ b/tools/shell/src/test/java/net/corda/tools/shell/InteractiveShellJavaTest.java @@ -0,0 +1,190 @@ +package net.corda.tools.shell; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.google.common.collect.Lists; +import kotlin.Pair; +import net.corda.client.jackson.JacksonSupport; +import net.corda.core.contracts.Amount; +import net.corda.core.crypto.SecureHash; +import net.corda.core.flows.FlowException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.FlowSession; +import net.corda.core.flows.StateMachineRunId; +import net.corda.core.identity.CordaX500Name; +import net.corda.core.identity.Party; +import net.corda.core.internal.concurrent.CordaFutureImplKt; +import net.corda.core.internal.concurrent.OpenFuture; +import net.corda.core.messaging.FlowProgressHandleImpl; +import net.corda.core.utilities.ProgressTracker; +import net.corda.node.services.identity.InMemoryIdentityService; +import net.corda.testing.core.TestIdentity; +import net.corda.testing.internal.InternalTestConstantsKt; +import org.jetbrains.annotations.Nullable; +import org.junit.Test; +import rx.Observable; + +import java.util.*; + +import static org.junit.Assert.assertEquals; + +public class InteractiveShellJavaTest { + private static TestIdentity megaCorp = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB")); + + // should guarantee that FlowA will have synthetic method to access this field + private static String synthetic = "synth"; + + abstract static class StringFlow extends FlowLogic { + abstract String getA(); + } + + public static class FlowA extends StringFlow { + + private String a; + + public FlowA(String a) { + if (!synthetic.isEmpty()) { + this.a = a; + } + } + + public FlowA(Integer b) { + this(b.toString()); + } + + public FlowA(Integer b, String c) { + this(b.toString() + c); + } + + public FlowA(Amount amount) { + this(amount.toString()); + } + + public FlowA(Pair, SecureHash.SHA256> pair) { + this(pair.toString()); + } + + public FlowA(Party party) { + this(party.getName().toString()); + } + + @Nullable + @Override + public ProgressTracker getProgressTracker() { + return new ProgressTracker(); + } + + @Override + public String call() throws FlowException { + return a; + } + + @Override + String getA() { + return a; + } + } + + public static class FlowB extends StringFlow { + + private Party party; + private String a; + + public FlowB(Party party, String a) { + this.party = party; + this.a = a; + } + + @Nullable + @Override + public ProgressTracker getProgressTracker() { + return new ProgressTracker(); + } + + @Override + public String call() throws FlowException { + FlowSession session = initiateFlow(party); + + + Integer integer = session.receive(Integer.class).unwrap((i) -> { + return i; + }); + + return integer.toString(); + + } + + @Override + String getA() { + return a; + } + } + + private InMemoryIdentityService ids = new InMemoryIdentityService(Lists.newArrayList(megaCorp.getIdentity()), InternalTestConstantsKt.getDEV_ROOT_CA().getCertificate()); + + private ObjectMapper om = JacksonSupport.createInMemoryMapper(ids, new YAMLFactory()); + + private String output; + + private void check(String input, String expected, Class flowClass) throws InteractiveShell.NoApplicableConstructor { + InteractiveShell.INSTANCE.runFlowFromString((clazz, args) -> { + + StringFlow instance = null; + try { + instance = (StringFlow)clazz.getConstructor(Arrays.stream(args).map(Object::getClass).toArray(Class[]::new)).newInstance(args); + } catch (Exception e) { + System.out.println(e); + } + output = instance.getA(); + OpenFuture future = CordaFutureImplKt.openFuture(); + future.set("ABC"); + return new FlowProgressHandleImpl(StateMachineRunId.Companion.createRandom(), future, Observable.just("Some string")); + }, input, flowClass, om); + assertEquals(input, expected, output); + } + + @Test + public void flowStartSimple() throws InteractiveShell.NoApplicableConstructor { + check("a: Hi there", "Hi there", FlowA.class); + check("b: 12", "12", FlowA.class); + check("b: 12, c: Yo", "12Yo", FlowA.class); + } + + @Test + public void flowStartWithComplexTypes() throws InteractiveShell.NoApplicableConstructor { + check("amount: £10", "10.00 GBP", FlowA.class); + } + + @Test + public void flowStartWithNestedTypes() throws InteractiveShell.NoApplicableConstructor { + check( + "pair: { first: $100.12, second: df489807f81c8c8829e509e1bcb92e6692b9dd9d624b7456435cb2f51dc82587 }", + "($100.12, df489807f81c8c8829e509e1bcb92e6692b9dd9d624b7456435cb2f51dc82587)", + FlowA.class); + } + + @Test(expected = InteractiveShell.NoApplicableConstructor.class) + public void flowStartNoArgs() throws InteractiveShell.NoApplicableConstructor { + check("", "", FlowA.class); + } + + @Test(expected = InteractiveShell.NoApplicableConstructor.class) + public void flowMissingParam() throws InteractiveShell.NoApplicableConstructor { + check("c: Yo", "", FlowA.class); + } + + @Test(expected = InteractiveShell.NoApplicableConstructor.class) + public void flowTooManyParams() throws InteractiveShell.NoApplicableConstructor { + check("b: 12, c: Yo, d: Bar", "", FlowA.class); + } + + @Test + public void party() throws InteractiveShell.NoApplicableConstructor { + check("party: \"" + megaCorp.getName() + "\"", megaCorp.getName().toString(), FlowA.class); + } + + @Test + public void unwrapLambda() throws InteractiveShell.NoApplicableConstructor { + check("party: \"" + megaCorp.getName() + "\", a: Bambam", "Bambam", FlowB.class); + } +}