From 701d8426df33ac374370a4c7a4433037d3c26014 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 18 Jun 2018 12:18:24 +0100 Subject: [PATCH 1/9] CORDA-1640 - Proxy serializer documentation (#3389) --- docs/source/cordapp-custom-serializers.rst | 84 ++++++++++++++++++++-- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/docs/source/cordapp-custom-serializers.rst b/docs/source/cordapp-custom-serializers.rst index 0e2554ebc7..14e0159e6b 100644 --- a/docs/source/cordapp-custom-serializers.rst +++ b/docs/source/cordapp-custom-serializers.rst @@ -21,16 +21,16 @@ Serializers must * Inherit from ``net.corda.core.serialization.SerializationCustomSerializer`` * Provide a proxy class to transform the object to and from * Implement the ``toProxy`` and ``fromProxy`` methods - * Be either included into CorDapp Jar or made known to the running process via ``amqp.custom.serialization.scanSpec`` - system property. This system property may be necessary to be able to discover custom serializer in the classpath. At a minimum the value - of the property should include comma separated set of packages where custom serializers located. Full syntax includes - scanning specification as defined by: `` + * Be either included into the CorDapp Jar or made known to the running process via the ``amqp.custom.serialization.scanSpec`` + system property. This system property may be necessary to be able to discover custom serializer in the classpath. + At a minimum the value of the property should include comma separated set of packages where custom serializers located. + Full syntax includes scanning specification as defined by: `` Serializers inheriting from ``SerializationCustomSerializer`` have to implement two methods and two types. Example ------- -Consider this example class: +Consider the following class: .. sourcecode:: java @@ -38,6 +38,9 @@ Consider this example class: private final Int a private final Int b + // Because this is marked private the serialization framework will not + // consider it when looking to see which constructor should be used + // when serializing instances of this class. private Example(Int a, Int b) { this.a = a; this.b = b; @@ -52,23 +55,90 @@ Consider this example class: Without a custom serializer we cannot serialize this class as there is no public constructor that facilitates the initialisation of all of its properties. -To be serializable by Corda this would require a custom serializer as follows: +.. note:: This is clearly a contrived example, simply making the constructor public would alleviate the issues. + However, for the purposes of this example we are assuming that for external reasons this cannot be done. + +To be serializable by Corda this would require a custom serializer to be written that can transform the unserializable +class into a form we can serialize. Continuing the above example, this could be written as follows: .. sourcecode:: kotlin class ExampleSerializer : SerializationCustomSerializer { + /** + * This is the actual proxy class that is used as an intermediate representation + * of the Example class + */ data class Proxy(val a: Int, val b: Int) + /** + * This method should be able to take an instance of the type being proxied and + * transpose it into that form, instantiating an instance of the Proxy object (it + * is this class instance that will be serialized into the byte stream. + */ override fun toProxy(obj: Example) = Proxy(obj.a, obj.b) + /** + * This method is used during deserialization. The bytes will have been read + * from the serialized blob and an instance of the Proxy class returned, we must + * now be able to transform that back into an instance of our original class. + * + * In our example this requires us to evoke the static *of* method on the + * Example class, transforming the serialized properties of the Proxy instance + * into a form expected by the construction method of Example. + */ override fun fromProxy(proxy: Proxy) : Example { val constructorArg = IntArray(2); constructorArg[0] = proxy.a constructorArg[1] = proxy.b - return Example.create(constructorArg) + return Example.of(constructorArg) } } +In the above ``ExampleSerializer`` is the actual serializer that will be loaded by the framework to +serialize instances of the ``Example`` type. + +``ExampleSerializer.Proxy`` is the intermediate representation used by the framework to represent +instances of ``Example`` within the wire format. + +The Proxy Object +---------------- + +The proxy object should be thought of as an intermediate representation that the serialization framework +can reason about. One is being written for a class because, for some reason, that class cannot be +introspected successfully but that framework. It is therefore important to note that the proxy class must +only contain elements that the framework can reason about. + +The proxy class itself is distinct from the proxy serializer. The serializer must refer to the unserializable +type in the ``toProxy`` and ``fromProxy`` methods. + +For example, the first thought a developer may have when implementing a proxy class is to simply *wrap* an +instance of the object being proxied. This is shown below + +.. sourcecode:: kotlin + + class ExampleSerializer : SerializationCustomSerializer { + /** + * In this example, we are trying to wrap the Example type to make it serializable + */ + data class Proxy(val e: Example) + + override fun toProxy(obj: Example) = Proxy(obj) + + override fun fromProxy(proxy: Proxy) : Example { + return proxy.e + } + } + +However, this will not work because what we've created is a recursive loop whereby synthesising a serializer +for the ``Example`` type requires synthesising one for ``ExampleSerializer.Proxy``. However, that requires +one for ``Example`` and so on and so forth until we get a ``StackOverflowException``. + +The solution, as shown initially, is to create the intermediate form (the Proxy object) purely in terms +the serialization framework can reason about. + +.. important:: When composing a proxy object for a class be aware that everything within that structure will be written + into the serialized byte stream. + Whitelisting ------------ By writing a custom serializer for a class it has the effect of adding that class to the whitelist, meaning such From 71e77845199049eebddb34a1942c104ae4f32e84 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Mon, 18 Jun 2018 12:40:35 +0100 Subject: [PATCH 2/9] ENT-1467: Document how to configure IntelliJ with a deterministic SDK. (#3371) * Allow deterministic modules to use a deterministic IntelliJ SDK. * Document how to configure IntelliJ with a deterministic SDK. * Small clarifications to deterministic IntelliJ SDK documentation. --- build.gradle | 2 + core-deterministic/build.gradle | 9 ++ .../testing/common/build.gradle | 9 ++ docs/source/deterministic-modules.rst | 105 +++++++++++++++++- jdk8u-deterministic/.gitignore | 1 + jdk8u-deterministic/build.gradle | 2 +- serialization-deterministic/build.gradle | 9 ++ 7 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 jdk8u-deterministic/.gitignore diff --git a/build.gradle b/build.gradle index 4329fa1c93..0ab201c2bd 100644 --- a/build.gradle +++ b/build.gradle @@ -83,6 +83,8 @@ buildscript { ext.jcabi_manifests_version = '1.1' ext.deterministic_rt_version = '1.0-SNAPSHOT' + // Name of the IntelliJ SDK created for the deterministic Java rt.jar. + // ext.deterministic_idea_sdk = '1.8 (Deterministic)' // Update 121 is required for ObjectInputFilter. // Updates [131, 161] also have zip compression bugs on MacOS (High Sierra). diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle index 8a78dd5c51..419a540adb 100644 --- a/core-deterministic/build.gradle +++ b/core-deterministic/build.gradle @@ -3,6 +3,7 @@ description 'Corda core (deterministic)' apply plugin: 'kotlin' apply plugin: 'com.jfrog.artifactory' apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'idea' evaluationDependsOn(':jdk8u-deterministic') evaluationDependsOn(":core") @@ -197,3 +198,11 @@ publish { // Must be after publish {} so that the previous install task exists for overwriting. task install(overwrite: true, dependsOn: 'publishToMavenLocal') + +idea { + module { + if (project.hasProperty("deterministic_idea_sdk")) { + jdkName project.property("deterministic_idea_sdk") as String + } + } +} diff --git a/core-deterministic/testing/common/build.gradle b/core-deterministic/testing/common/build.gradle index 49f2ceb748..4ef2891bda 100644 --- a/core-deterministic/testing/common/build.gradle +++ b/core-deterministic/testing/common/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'kotlin' +apply plugin: 'idea' evaluationDependsOn(':jdk8u-deterministic') @@ -22,3 +23,11 @@ tasks.withType(JavaCompile) { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { kotlinOptions.jdkHome = deterministic_jdk_home } + +idea { + module { + if (project.hasProperty("deterministic_idea_sdk")) { + jdkName project.property("deterministic_idea_sdk") as String + } + } +} diff --git a/docs/source/deterministic-modules.rst b/docs/source/deterministic-modules.rst index f6327f1649..bf86953cec 100644 --- a/docs/source/deterministic-modules.rst +++ b/docs/source/deterministic-modules.rst @@ -1,3 +1,9 @@ +.. raw:: html + + + +.. role:: red + Deterministic Corda Modules =========================== @@ -86,6 +92,103 @@ The build generates each of Corda's deterministic JARs in six steps: This step will fail if ProGuard spots any Java API references that still cannot be satisfied by the deterministic ``rt.jar``, and hence it will break the build. +Configuring IntelliJ with a Deterministic SDK +--------------------------------------------- + +We would like to configure IntelliJ so that it will highlight uses of non-deterministic Java APIs as :red:`not found`. +Or, more specifically, we would like IntelliJ to use the ``deterministic-rt.jar`` as a "Module SDK" for deterministic +modules rather than the ``rt.jar`` from the default project SDK, to make IntelliJ consistent with Gradle. + +This is possible, but slightly tricky to configure because IntelliJ will not recognise an SDK containing only the +``deterministic-rt.jar`` as being valid. It also requires that IntelliJ delegate all build tasks to Gradle, and that +Gradle be configured to use the Project's SDK. + +Creating the Deterministic SDK + #. Create a JDK Home directory with the following contents: + + ``jre/lib/rt.jar`` + + where ``rt.jar`` here is this renamed artifact: + + .. code-block:: xml + + + net.corda + deterministic-rt + api + + + .. + + .. note:: Gradle already creates this JDK in the project's ``jdk8u-deterministic/jdk`` directory, and you could + configure IntelliJ to use this location as well. However, you should also be aware that IntelliJ SDKs + are available for *all* projects to use. + + To create this deterministic JDK image, execute the following: + + .. code-block:: bash + + $ gradlew jdk8u-deterministic:copyJdk + + .. + + #. While IntelliJ is *not* running, locate the ``config/options/jdk.table.xml`` file in IntelliJ's configuration + directory. Add an empty ```` section to this file: + + .. code-block:: xml + + + + + + + + + + + .. + + #. Open IntelliJ and select ``File/Project Structure/Platform Settings/SDKs``. The "1.8 (Deterministic)" SDK should + now be present. Select it and then click on the ``Classpath`` tab. Press the "Add" / "Plus" button to add + ``rt.jar`` to the SDK's classpath. Then select the ``Annotations`` tab and include the same JAR(s) as the other + SDKs. + +Configuring the Corda Project + #. Open the root ``build.gradle`` file and define this property: + + .. code-block:: gradle + + buildscript { + ext { + ... + deterministic_idea_sdk = '1.8 (Deterministic)' + ... + } + } + + .. + +Configuring IntelliJ + #. Go to ``File/Settings/Build, Execution, Deployment/Build Tools/Gradle``, and configure Gradle's JVM to be the + project's JVM. + + #. Go to ``File/Settings/Build, Execution, Deployment/Build Tools/Gradle/Runner``, and select these options: + + - Delegate IDE build/run action to Gradle + - Run tests using the Gradle Test Runner + + #. Delete all of the ``out`` directories that IntelliJ has previously generated for each module. + + #. Go to ``View/Tool Windows/Gradle`` and click the ``Refresh all Gradle projects`` button. + +These steps will enable IntelliJ's presentation compiler to use the deterministic ``rt.jar`` with the following modules: + + - ``core-deterministic`` + - ``serialization-deterministic`` + - ``core-deterministic:testing:common`` + +but still build everything using Gradle with the full JDK. + Testing the Deterministic Modules --------------------------------- @@ -108,7 +211,7 @@ The ``testing`` module also has two sub-modules: .. _deterministic_annotations: Applying @KeepForDJVM and @DeleteForDJVM annotations ---------------------------------------------------------- +---------------------------------------------------- Corda developers need to understand how to annotate classes in the ``core`` and ``serialization`` modules correctly in order to maintain the deterministic JARs. diff --git a/jdk8u-deterministic/.gitignore b/jdk8u-deterministic/.gitignore new file mode 100644 index 0000000000..3216b4dc3d --- /dev/null +++ b/jdk8u-deterministic/.gitignore @@ -0,0 +1 @@ +jdk/ diff --git a/jdk8u-deterministic/build.gradle b/jdk8u-deterministic/build.gradle index 298953c30f..b2810ab8bc 100644 --- a/jdk8u-deterministic/build.gradle +++ b/jdk8u-deterministic/build.gradle @@ -8,7 +8,7 @@ repositories { } ext { - jdk_home = "$buildDir/jdk" + jdk_home = "$projectDir/jdk" } configurations { diff --git a/serialization-deterministic/build.gradle b/serialization-deterministic/build.gradle index ccb1c32558..06f89f6c65 100644 --- a/serialization-deterministic/build.gradle +++ b/serialization-deterministic/build.gradle @@ -3,6 +3,7 @@ description 'Corda serialization (deterministic)' apply plugin: 'kotlin' apply plugin: 'com.jfrog.artifactory' apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'idea' evaluationDependsOn(':jdk8u-deterministic') evaluationDependsOn(":serialization") @@ -184,3 +185,11 @@ publish { // Must be after publish {} so that the previous install task exists for overwriting. task install(overwrite: true, dependsOn: 'publishToMavenLocal') + +idea { + module { + if (project.hasProperty("deterministic_idea_sdk")) { + jdkName project.property("deterministic_idea_sdk") as String + } + } +} From a0c6de77580e070f5736a54296d618d1a1afe94a Mon Sep 17 00:00:00 2001 From: Anthony Keenan Date: Mon, 18 Jun 2018 13:34:35 +0100 Subject: [PATCH 3/9] CORDA-1498: serialization multiple transform bug (#3216) * Fix issue when evolving enums with transformation chains * Regenerate test data for deserializeWithRename test and unignore * Further tweaks / remove debugging * Formatting tweaks * Address review comments * Remove debug * Add classname to serialization tranform exceptions * Use direct node links instead of indexes to improve readability * More readability tweaks * More readability improvements * rename require to requireThat to resolve conflict with kotlin libraries * Add logging of error message * Change requireThat helper to inline function * remove unneeded toString * Further tweaks * Change NotSerializableException to more generic IOException * Make exception context clearer --- .../internal/NotSerializableExceptions.kt | 14 +++ .../internal/amqp/TransformTypes.kt | 92 +++++++++++------- .../internal/amqp/TransformsSchema.kt | 18 +++- .../internal/amqp/EnumEvolvabilityTests.kt | 66 ++++++++++++- .../internal/amqp/EnumEvolveTests.kt | 1 - ...EnumEvolveTests.deserializeWithRename.1.AA | Bin 763 -> 731 bytes .../EnumEvolveTests.deserializeWithRename.1.B | Bin 762 -> 730 bytes .../EnumEvolveTests.deserializeWithRename.1.C | Bin 872 -> 730 bytes ...EnumEvolveTests.deserializeWithRename.2.AA | Bin 792 -> 760 bytes ...EnumEvolveTests.deserializeWithRename.2.BB | Bin 792 -> 760 bytes .../EnumEvolveTests.deserializeWithRename.2.C | Bin 911 -> 759 bytes ...EnumEvolveTests.deserializeWithRename.3.AA | Bin 821 -> 789 bytes .../EnumEvolveTests.deserializeWithRename.3.C | Bin 952 -> 788 bytes ...EnumEvolveTests.deserializeWithRename.3.XX | Bin 821 -> 789 bytes 14 files changed, 153 insertions(+), 38 deletions(-) create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/NotSerializableExceptions.kt diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/NotSerializableExceptions.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/NotSerializableExceptions.kt new file mode 100644 index 0000000000..df8925e308 --- /dev/null +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/NotSerializableExceptions.kt @@ -0,0 +1,14 @@ +package net.corda.serialization.internal + +import java.io.IOException +import java.io.NotSerializableException + +class NotSerializableDetailedException(classname: String?, val reason: String) : NotSerializableException(classname) { + override fun toString(): String { + return "Unable to serialize/deserialize $message: $reason" + } +} + +// This exception is thrown when serialization isn't possible but at the point the exception +// is thrown the classname isn't known. It's caught and rethrown as a [NotSerializableDetailedException] +class NotSerializableWithReasonException(message: String?): IOException(message) \ No newline at end of file diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformTypes.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformTypes.kt index a36c56c374..3356ab2e47 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformTypes.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformTypes.kt @@ -5,9 +5,9 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.CordaSerializationTransformEnumDefaults import net.corda.core.serialization.CordaSerializationTransformRename +import net.corda.serialization.internal.NotSerializableWithReasonException import org.apache.qpid.proton.amqp.DescribedType import org.apache.qpid.proton.codec.DescribedTypeConstructor -import java.io.NotSerializableException /** * Enumerated type that represents each transform that can be applied to a class. Used as the key type in @@ -47,25 +47,12 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType */ override fun validate(list: List, constants: Map) { uncheckedCast, List>(list).forEach { - if (!constants.contains(it.new)) { - throw NotSerializableException("Unknown enum constant ${it.new}") - } - - if (!constants.contains(it.old)) { - throw NotSerializableException( - "Enum extension defaults must be to a valid constant: ${it.new} -> ${it.old}. ${it.old} " + - "doesn't exist in constant set $constants") - } - - if (it.old == it.new) { - throw NotSerializableException("Enum extension ${it.new} cannot default to itself") - } - - if (constants[it.old]!! >= constants[it.new]!!) { - throw NotSerializableException( - "Enum extensions must default to older constants. ${it.new}[${constants[it.new]}] " + - "defaults to ${it.old}[${constants[it.old]}] which is greater") - } + requireThat(constants.contains(it.new)) {"Unknown enum constant ${it.new}"} + requireThat(constants.contains(it.old)) { "Enum extension defaults must be to a valid constant: ${it.new} -> ${it.old}. ${it.old} " + + "doesn't exist in constant set $constants" } + requireThat(it.old != it.new) { "Enum extension ${it.new} cannot default to itself" } + requireThat(constants[it.old]!! < constants[it.new]!!) { "Enum extensions must default to older constants. ${it.new}[${constants[it.new]}] " + + "defaults to ${it.old}[${constants[it.old]}] which is greater" } } } }, @@ -83,21 +70,56 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType * @param constants The list of enum constants on the type the transforms are being applied to */ override fun validate(list: List, constants: Map) { - object : Any() { - val from: MutableSet = mutableSetOf() - val to: MutableSet = mutableSetOf() - }.apply { - @Suppress("UNCHECKED_CAST") (list as List).forEach { rename -> - if (rename.to in this.to || rename.from in this.from) { - throw NotSerializableException("Cyclic renames are not allowed (${rename.to})") - } + data class Node(val transform: RenameSchemaTransform, var next: Node?, var prev: Node?, var visitedBy: Node? = null) { + fun visit(visitedBy: Node) { + this.visitedBy = visitedBy + } + val visited get() = visitedBy != null + } - this.to.add(rename.from) - this.from.add(rename.to) + val graph = mutableListOf() + // Keep two maps of forward links and back links in order to build the graph in one pass + val forwardLinks = hashMapOf() + val reverseLinks = hashMapOf() + + // build a dependency graph + val transforms: List = uncheckedCast(list) + transforms.forEach { rename -> + requireThat(!forwardLinks.contains(rename.from)) { "There are multiple transformations from ${rename.from}, which is not allowed" } + requireThat(!reverseLinks.contains(rename.to)) { "There are multiple transformations to ${rename.to}, which is not allowed" } + val node = Node(rename, forwardLinks[rename.to], reverseLinks[rename.from]) + graph.add(node) + node.next?.prev = node + node.prev?.next = node + forwardLinks[rename.from] = node + reverseLinks[rename.to] = node + } + + // Check that every property in the current type is at the end of a renaming chain, if it is in one + constants.keys.forEach { + requireThat(reverseLinks[it]?.next == null) { "$it is specified as a previously evolved type, but it also exists in the current type" } + } + + // Check for cyclic dependencies + graph.forEach { + if (it.visited) return@forEach + // Find an unvisited node + var currentNode = it + currentNode.visit(it) + while (currentNode.next != null) { + currentNode = currentNode.next!! + if (currentNode.visited) { + requireThat(currentNode.visitedBy != it) { "Cyclic renames are not allowed (${currentNode.transform.from})" } + // we have found the start of another non-cyclic chain of dependencies + // if they were cyclic we would have gone round in a loop and already thrown + break + } + currentNode.visit(it) } } } } + // Transform used to test the unknown handler, leave this at as the final constant, uncomment // when regenerating test cases - if Java had a pre-processor this would be much neater // @@ -121,9 +143,7 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType override fun newInstance(obj: Any?): TransformTypes { val describedType = obj as DescribedType - if (describedType.descriptor != DESCRIPTOR) { - throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.") - } + requireThat(describedType.descriptor == DESCRIPTOR) { "Unexpected descriptor ${describedType.descriptor}." } return try { values()[describedType.described as Int] @@ -133,5 +153,11 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType } override fun getTypeClass(): Class<*> = TransformTypes::class.java + + protected inline fun requireThat(expr: Boolean, errorMessage: () -> String) { + if (!expr) { + throw NotSerializableWithReasonException(errorMessage()) + } + } } } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformsSchema.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformsSchema.kt index 100fe70fb0..0d63078cd4 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformsSchema.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformsSchema.kt @@ -3,6 +3,10 @@ package net.corda.serialization.internal.amqp import net.corda.core.KeepForDJVM import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.CordaSerializationTransformRename +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.trace +import net.corda.serialization.internal.NotSerializableDetailedException +import net.corda.serialization.internal.NotSerializableWithReasonException import org.apache.qpid.proton.amqp.DescribedType import org.apache.qpid.proton.codec.DescribedTypeConstructor import java.io.NotSerializableException @@ -192,6 +196,7 @@ class RenameSchemaTransform(val from: String, val to: String) : Transform() { data class TransformsSchema(val types: Map>>) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_SCHEMA.amqpDescriptor + private val logger = contextLogger() /** * Takes a class name and either returns a cached instance of the TransformSet for it or, on a cache miss, @@ -239,10 +244,17 @@ data class TransformsSchema(val types: Map>>) { - get(type, sf).apply { - if (isNotEmpty()) { - map[type] = this + try { + get(type, sf).apply { + if (isNotEmpty()) { + map[type] = this + } } + } catch (e: NotSerializableWithReasonException) { + val message = "Error running transforms for $type: ${e.message}" + logger.error(message) + logger.trace { e.toString() } + throw NotSerializableDetailedException(type, e.message ?: "") } } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumEvolvabilityTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumEvolvabilityTests.kt index e56d4a93c6..74026d2df6 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumEvolvabilityTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumEvolvabilityTests.kt @@ -1,9 +1,12 @@ package net.corda.serialization.internal.amqp import net.corda.core.serialization.* +import net.corda.serialization.internal.NotSerializableDetailedException import net.corda.serialization.internal.amqp.testutils.* import net.corda.testing.common.internal.ProjectStructure.projectRootDir +import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy +import org.assertj.core.api.Condition import org.junit.Test import java.io.NotSerializableException import java.net.URI @@ -442,6 +445,68 @@ class EnumEvolvabilityTests { assertTrue(envelope.transformsSchema.types[WithUnknownTest::class.java.name]!!.containsKey(TransformTypes.Unknown)) } + // + // In this test we check that multiple transforms of a property are accepted + // + @CordaSerializationTransformRenames( + CordaSerializationTransformRename(from = "A", to = "B"), + CordaSerializationTransformRename(from = "B", to = "C") + ) + enum class AcceptMultipleRename { C } + + @Test + fun acceptMultipleRename() { + data class C(val e: AcceptMultipleRename) + + val sf = testDefaultFactory() + SerializationOutput(sf).serialize(C(AcceptMultipleRename.C)) + } + + // + // In this example we will try to rename two different things to the same thing, + // which is not allowed + // + @CordaSerializationTransformRenames( + CordaSerializationTransformRename(from = "D", to = "C"), + CordaSerializationTransformRename(from = "E", to = "C") + ) + enum class RejectMultipleRenameTo { A, B, C } + + @Test + fun rejectMultipleRenameTo() { + data class C(val e: RejectMultipleRenameTo) + + val sf = testDefaultFactory() + assertThatThrownBy { + SerializationOutput(sf).serialize(C(RejectMultipleRenameTo.A)) + }.isInstanceOfSatisfying(NotSerializableDetailedException::class.java) { ex -> + assertThat(ex.reason).isEqualToIgnoringCase("There are multiple transformations to C, which is not allowed") + assertThat(ex.message).endsWith(RejectMultipleRenameTo::class.simpleName) + } + } + + // + // In this example we will try to rename two different things from the same thing, + // which is not allowed + // + @CordaSerializationTransformRenames( + CordaSerializationTransformRename(from = "D", to = "C"), + CordaSerializationTransformRename(from = "D", to = "B") + ) + enum class RejectMultipleRenameFrom { A, B, C } + + @Test + fun rejectMultipleRenameFrom() { + data class C(val e: RejectMultipleRenameFrom) + + val sf = testDefaultFactory() + assertThatThrownBy { + SerializationOutput(sf).serialize(C(RejectMultipleRenameFrom.A)) + }.isInstanceOf(NotSerializableException::class.java) + .hasToString("Unable to serialize/deserialize net.corda.serialization.internal.amqp.EnumEvolvabilityTests\$RejectMultipleRenameFrom: " + + "There are multiple transformations from D, which is not allowed") + } + // // In this example we will have attempted to rename D back to C // @@ -534,5 +599,4 @@ class EnumEvolvabilityTests { SerializationOutput(sf).serialize(C(RejectBadDefaultToSelf.D)) }.isInstanceOf(NotSerializableException::class.java) } - } diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumEvolveTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumEvolveTests.kt index 90032a0c59..94129747e1 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumEvolveTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumEvolveTests.kt @@ -150,7 +150,6 @@ class EnumEvolveTests { // Finally, the version we're using to test with enum class DeserializeWithRename { A, B, C } - @Ignore("https://r3-cev.atlassian.net/browse/CORDA-1498") @Test fun deserializeWithRename() { val resource = "${javaClass.simpleName}.${testName()}" diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.1.AA b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.1.AA index 16b9b723f87c5def34ec66e655a43fc29143dd14..2e13e6009e1f43368219dbdb7b2ae5bf21511f48 100644 GIT binary patch literal 731 zcmYe!FG@*dWME)uIGO|`85kHZFfcG30Wz2w7AxhYmgpseR9J?U^{S>jjF~wxGrSL$t*5`+kozIT#@B)z?vDZiTwZvIO-NM z8X$=Qqm$8TA)_Ihkn=)DBe)Rf!5Sv2dg!1IB8hN^FddLZL=V>i0p^8lkjP%h=m<({ E0NQQoJ^%m! literal 763 zcmYe!FG@*dWME)uIGO|`85kHZFfcGZ0Wz2w7AxhYmgpseR9IEHI$F9Ur)!&gmAd8_ z2Icz)_yifI+u9z`XGBrwX_j8*TsQIW`3St zVs2r9o@-udu4`F-PFZS5YH>-iN=hnBQEGT*Nk&j=USe*linD_&+!p2oYD~oV(cwTg zBU}^9fjIVsjHwHQDRZ3*HrKg2I5;}NZDKocmW?LXIJhok$;m7(f!l;05V#`I;ea(W zTod~N4set%WHdk$14c8W(?Ui=G$H4Oj7D%F&VwyXH1XI$9Yhl14q-YVi-fkFIgA*!Y$V;PA$qz%*m`uEXmBz)62{&NiE7t%+X8CEiBM;%`44y zEz8d-OASdaE-6+?NrkCS4bLpe2ujUM%uQ8sc5sE8&U`?Pi5Rar97skum*qeh`$ERl zh2G@3$_1ONTpb)7o#4)5J8+bZs>VCGE@a8cEG~iDfbMZzapiEpni;N%{Qw8kLMBJY zg^UJBV(g&UT*zpMCgi-3(FiWYd9a3wsvbJ1gGeIWAxsBk5z)hSK!AB68ziz9GCG1B F3;-8o==%Tw literal 762 zcmYe!FG@*dWME)uIGO|`85kHZFfcGZ1~Ql#7AxhYmgpseR9IEHI$F9Ur)!&gmAd8_ z2Icz)_yifI+u9z`V?s%ci9G&3KV>@t`jV9JOxGrSL$t*5`+k_qvxZ=>^fHgB* z6Z-)UriDz7jtdzLki^(QvAmGc5KYK=A)^soi1T0z6HPpJPzRBOxI>r@$ReVR>wp0B OLN-VuSjgxIayS6udG(_J diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.1.C b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.1.C index ef16d41cf7171872a2462d584a1695b7e17867aa..77d2e4c7088e8c2f6f7938426580b171b9e65ede 100644 GIT binary patch literal 730 zcmYe!FG@*dWME)uIGO|`85kHZFfcG31~Ql#7AxhYmgpseR9J zp*MN1a>3>*R|f}2C%6r42ad8))p!Tjg)BLl#U*eX&^?YTt{e_nGs88pAK+kG$mHm_ zkkJ51j2#%8P74_g(S)2AG8(~!I1kn^QPo2Sbr4B}JA~{o==}fy literal 872 zcmb_a+e*Vg5Zz5{>gB~B@I{I*%F+m0suXhRf;UiNMDRg~=^CTSMw2ww{+swo{(>_} z6;@ghDGal-JF|1no}D1ghCU^Pe15!x-9bbMg@_QX@}z;OO(_Z%VC}(#TfIRLZg)>c zJT~0){Nl_t!hRpB82wdyydRd%+tF$F#^`m%cJOGA_wHY-S{>U{$I}$#8necq(wJ`4 zx+S;_hRf9tHI|Dk^5baY7f~u%Bny#AKW2XN^31p#CA>`IQg|XS^7c@CSBvYYcyNXE z6VY}|uJCC9z!sAFg8(L!Jyi-oPz%|F=(Yup3UHVIV>+0{l$)kyD^ztr0K;XGKqiNV z%{a;n1#Olv^L`tJIjqJAf?62VWEpkUEDJpCD9th3s^-JSq%kMyU~*SUuD}r(-d&^! e&~8T^z%>ssHq`H4g`;&;Mz6;SB8tz0M}7f+Xk?%O diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.2.AA b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.2.AA index 4f75773756defb3f45cade9196fe2f1e9d44f46a..e55546c2ae739e777193464d8e400335f42c02bc 100644 GIT binary patch literal 760 zcmYe!FG@*dWME)uIGO|`85kHZFfcGZ1TvTz7AxhYmgpseR9F@Hc$*eFd%HMUL>1+x zyIEvd7^dfy+u9z`XGBp~Sd{B(>0J-iN=hnBb!vEKNk&j=USe*linD_&+;rvxYD~oV&EY^YBU}^9fiU)k zjHwH~$#azpHdnbiI5;}NZD2cajE$`N{9eOwel_i literal 792 zcmYe!FG@*dWME)uIGO|`85kHZFfcHK1eh5XE9Iq@=p}dq60Deo8lQBTw&=7Qpe7;kjc?81Yr>q&?3e+ zP#YN^0~t(kN3b6F$+|EUZo6JyeoAU$L8e}2UP)?EUSf`3acWU!VoqjNVo7Fxo?c>Z zVS%1&UTLmtS$wDojyocxFjPP-#Q4$SKsF;> z6U%`(_Jxe83xg?hoeMVCxjHyFI>BvXJ8+JTCe}E(E@a8cEG~iDgdPyMBGKW14KrL5 z`vDGclrCg6KofIvTF7XKBnFInM(2f$MsOj{gA15w;!LWDG`oFgcq0sueZ{`mj^ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.2.BB b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.2.BB index 521bf9ec2fb5712e1369d09c9d110e1c35e348e4..29df5002fa7d2da31a6cae8a65eb0bfb48cae26a 100644 GIT binary patch literal 760 zcmYe!FG@*dWME)uIGO|`85kHZFfcGZ1TvTz7AxhYmgpseR9F@Hc$*eFd%HMUL>1+x zyIEvd7^dfy+u9z`XGBp~Sd{B(>0JpZV?mE zBE~CF8yQan8BB0F)&nnD7y80&*DFpf%1q43tV%4&%+J%y%qvMP%1g}AOUx}S&~wcz z&2=ry&nZg{Ni8lZR!K>PsZI^gEXfE;%}dNpRdIH3g`3WNK#hqQzd0O8MmU${Kp6W% z#?*!0l1^VLD)m;Bp-hU|z@uiS31q PPN2jFcL%DRBgi)ZO;YkP literal 792 zcmYe!FG@*dWME)uIGO|`85kHZFfcHK1eh5XE9Iq@=p}dq60Deo8lQBTw&=7Qpe7;kjcp@gb{8L6VM{Y zH&7cH9|IXoa5>fkKUo)s!fn^f%TGy7EXdT$%qvMP%1g}AD^4xSOw7rwN-W9D&(llH zEiBM;%`44yEz8d-OASdaE-6+?Nrfp&4bLpe2ujUM%uQ8sc5sE;!hArDi5NdR9LPpE zo8>?p`$ERlg~61$&IOz6Tpb)7o#4)6J8+JTCe}E(E@a8cEG~iDgdPyMBGKW14KrL5 z`vDGMlsY;tWHdk%b8=e9Xow^RjCw}rg^WgUA1+x zyIEvd7^dfy+u9z`V?0Jwp0BLN-WjFJyFD O$mE0wSyVYkkZ%Bgr}6Co literal 911 zcmb_a+e*Vg5Zz5`>*d97kmBR2Ej~mni^U+S(q_XGBq#<7Qrw6kd^J;+|%bkr-j(TkaNQ=?GNE&a{xp(J=&J5fjiN z#w$=88BYTlOmIiA9(c*R&=+pIUU6zsW@1ieRbokIex6=tUP)?EUSf`3Vs2r9o@-ud zu4`F-PFZS5YH>-iN=hnBb!vEKNk&j=USe*linD_&+;rvxYD~oV&EY^YBU}^9fiU)k zjHwH~$#azpHdnbiI5;}NZD2cajE$Yg2waiqaKMHc zu8I8s2RKR>G8&+XMMNxQG(-{uMm?kRLPjIF5a+?YOf>P?!Bj*V;tpXt5QcCr*8u_M kg=~;iu#nMdA(Im#q`3|VBIJM)3z;G!;EqMr=m<*10AE!Fs{jB1 diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.3.C b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.3.C index 342fdce5084caa38f4d3e60832fccd5d2b5ebee5..dd259450b3af79da45e3be85230719676f0327cd 100644 GIT binary patch literal 788 zcmYe!FG@*dWME)uIGO|`85kHZFfcIv1u~c!7AxhYmgpseR9J;)ct#dGhFSy!WEePG zdgK^U+S(q_V?!zHEM$s^fIAXZqa(A z3R8J&;34Sq@F`7?W#l@ZJ982@40Jb0WE41_?)amjE`qTyCZRWxO&KR~p(ht?*>;ou zoeUgb7V3sj^kgt#pG5t^00Xqa+yQ{p#w^fd9*Ik&`~N9DTc%VPhG{8MbC`gj&ngEQ z0u8gC8z;)NWybvW+GNb>e2oB7W5|MfUWPYGD|Ek(HRS$nu^Xc|bv7{6?aD<@p xDqTQ3sczs_lV-HkYi;6ao2<#D`vk--B+XLaig-IJ4-_KWwsM_i4aqRr_71rQhsFQ^ diff --git a/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.3.XX b/serialization/src/test/resources/net/corda/serialization/internal/amqp/EnumEvolveTests.deserializeWithRename.3.XX index 86d9a1672179da0a36e78cfdcd8d195c40d25d50..5bd475b237eda37b9b20b29f6edebdf7e42564b3 100644 GIT binary patch literal 789 zcmYe!FG@*dWME)uIGO|`85kHZFfcIv12UKy7AxhYmgpseR9J;)ct#dGhFSy!WEePG zdgK^U+S(q_XGBq#<7Qrw6kd^J;+|%bkr-j(TkaNQ=?GNE&a{vzA|iwlZV?mE zBE~CF8yQan8BB0F)&nnD7y80&*DFpf%1q43tV%4&%+J%y%qvMP%1g}AOUx}S&~wcz z&2=ry&nZg{Ni8lZR!K>PsZI^gEXfE;%}dNpRdIH3g`3WNK#hqQzd0O8MmU${Kp6W% z#?*!0l1^VLA|oa3w%xF3q#?y>*eLAq$U<*>SgAYq!#5R=I9lt7G);pWL71XWaj7T zCFT|u=(*;V=DL>U=ai*}q!yPHtE8mD6s3k|mShB_<|XE)syI8i!fjzbpvFXu9~};4 zBb?20AdY<@W9q_S%3SAy&2_F04vtQ6=dm3)$3_!t99$Q&Yg2waiqaKMHc zu8I8s2QW$<9Tzehpov99EMzo95(7p(qw_*WBe)Rf!M#j0@!7#tL>l4_VLA|oa4y#Y k0p^8lkW{dc(P<%*6C$L!4hSOTfD)jT3U@53Mn{mB0parn+5i9m From c2585e8c8e54aede6e9c32451057c1f7026c7239 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Mon, 18 Jun 2018 13:41:31 +0100 Subject: [PATCH 4/9] [CORDA-1595]: If adminAddress is not included in rpcSettings configuration node fails with NullPointerException (fix). (#3385) --- .../node/services/config/NodeConfiguration.kt | 31 ++++++++++++------- .../config/NodeConfigurationImplTest.kt | 10 ++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 0a3e9e4a98..2fb3fae86f 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -200,23 +200,27 @@ data class NodeConfigurationImpl( } - override val rpcOptions: NodeRpcOptions = initialiseRpcOptions(rpcAddress, rpcSettings, BrokerRpcSslOptions(baseDirectory / "certificates" / "nodekeystore.jks", keyStorePassword)) + private val actualRpcSettings: NodeRpcSettings - private fun initialiseRpcOptions(explicitAddress: NetworkHostAndPort?, settings: NodeRpcSettings, fallbackSslOptions: BrokerRpcSslOptions): NodeRpcOptions { - return when { - explicitAddress != null -> { - require(settings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." } + init { + actualRpcSettings = when { + rpcAddress != null -> { + require(rpcSettings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." } logger.warn("Top-level declaration of property 'rpcAddress' is deprecated. Please use 'rpcSettings.address' instead.") - settings.copy(address = explicitAddress) + rpcSettings.copy(address = rpcAddress) } else -> { - settings.address ?: throw ConfigException.Missing("rpcSettings.address") - settings + rpcSettings.address ?: throw ConfigException.Missing("rpcSettings.address") + rpcSettings } - }.asOptions(fallbackSslOptions) + } } + override val rpcOptions: NodeRpcOptions + get() { + return actualRpcSettings.asOptions(BrokerRpcSslOptions(baseDirectory / "certificates" / "nodekeystore.jks", keyStorePassword)) + } private fun validateTlsCertCrlConfig(): List { val errors = mutableListOf() @@ -239,7 +243,12 @@ data class NodeConfigurationImpl( override fun validate(): List { val errors = mutableListOf() errors += validateDevModeOptions() - errors += validateRpcOptions(rpcOptions) + val rpcSettingsErrors = validateRpcSettings(rpcSettings) + errors += rpcSettingsErrors + if (rpcSettingsErrors.isEmpty()) { + // Forces lazy property to initialise in order to throw exceptions + rpcOptions + } errors += validateTlsCertCrlConfig() errors += validateNetworkServices() errors += validateH2Settings() @@ -254,7 +263,7 @@ data class NodeConfigurationImpl( return errors } - private fun validateRpcOptions(options: NodeRpcOptions): List { + private fun validateRpcSettings(options: NodeRpcSettings): List { val errors = mutableListOf() if (options.address != null) { if (!options.useSsl && options.adminAddress == null) { diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt index cc646a1795..9f183d0b9a 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt @@ -191,6 +191,16 @@ class NodeConfigurationImplTest { assertThatCode { rawConfig.parseAsNodeConfiguration() }.doesNotThrowAnyException() } + @Test + fun `missing rpcSettings_adminAddress cause a graceful failure`() { + var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false)) + rawConfig = rawConfig.withoutPath("rpcSettings.adminAddress") + + val config = rawConfig.parseAsNodeConfiguration() + + assertThat(config.validate().filter { it.contains("rpcSettings.adminAddress") }).isNotEmpty + } + @Test fun `compatiilityZoneURL populates NetworkServices`() { val compatibilityZoneURL = URI.create("https://r3.com").toURL() From 9148d0529ad0e9e84a45305d763cfee485e6b5c3 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Mon, 18 Jun 2018 14:14:48 +0100 Subject: [PATCH 5/9] ENT-1463, ENT-1903: Refactor Gradle for deterministic modules (#3387) * Export locations of both deterministic rt.jar and its JDK_HOME as properties. * Refactor deterministic Java/Kotlin configuration into a script plugin. --- core-deterministic/build.gradle | 19 ++---------- .../testing/common/build.gradle | 19 +----------- deterministic.gradle | 29 +++++++++++++++++++ jdk8u-deterministic/build.gradle | 1 + serialization-deterministic/build.gradle | 19 ++---------- 5 files changed, 35 insertions(+), 52 deletions(-) create mode 100644 deterministic.gradle diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle index 419a540adb..80a0232dbf 100644 --- a/core-deterministic/build.gradle +++ b/core-deterministic/build.gradle @@ -1,17 +1,14 @@ description 'Corda core (deterministic)' -apply plugin: 'kotlin' +apply from: '../deterministic.gradle' apply plugin: 'com.jfrog.artifactory' apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'idea' -evaluationDependsOn(':jdk8u-deterministic') evaluationDependsOn(":core") def javaHome = System.getProperty('java.home') def jarBaseName = "corda-${project.name}".toString() -def jdkTask = project(':jdk8u-deterministic').assemble -def deterministic_jdk_home = project(':jdk8u-deterministic').jdk_home configurations { runtimeLibraries @@ -36,18 +33,6 @@ dependencies { runtimeLibraries "org.slf4j:slf4j-api:$slf4j_version" } -tasks.withType(AbstractCompile) { - dependsOn jdkTask -} - -tasks.withType(JavaCompile) { - options.compilerArgs << '-bootclasspath' << "$deterministic_jdk_home/jre/lib/rt.jar".toString() -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { - kotlinOptions.jdkHome = deterministic_jdk_home -} - jar { baseName 'DOES-NOT-EXIST' // Don't build a jar here because it would be the wrong one. @@ -162,7 +147,7 @@ task metafix(type: MetaFixerTask) { task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) { injars metafix - libraryjars "$deterministic_jdk_home/jre/lib/rt.jar" + libraryjars deterministic_rt_jar configurations.runtimeLibraries.forEach { libraryjars it.path, filter: '!META-INF/versions/**' diff --git a/core-deterministic/testing/common/build.gradle b/core-deterministic/testing/common/build.gradle index 4ef2891bda..f7c48a6c7a 100644 --- a/core-deterministic/testing/common/build.gradle +++ b/core-deterministic/testing/common/build.gradle @@ -1,29 +1,12 @@ -apply plugin: 'kotlin' +apply from: '../../../deterministic.gradle' apply plugin: 'idea' -evaluationDependsOn(':jdk8u-deterministic') - -def jdkTask = project(':jdk8u-deterministic').assemble -def deterministic_jdk_home = project(':jdk8u-deterministic').jdk_home - dependencies { compileOnly project(path: ':core-deterministic', configuration: 'runtimeArtifacts') compileOnly project(path: ':serialization-deterministic', configuration: 'runtimeArtifacts') compileOnly "junit:junit:$junit_version" } -tasks.withType(AbstractCompile) { - dependsOn jdkTask -} - -tasks.withType(JavaCompile) { - options.compilerArgs << '-bootclasspath' << "$deterministic_jdk_home/jre/lib/rt.jar".toString() -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { - kotlinOptions.jdkHome = deterministic_jdk_home -} - idea { module { if (project.hasProperty("deterministic_idea_sdk")) { diff --git a/deterministic.gradle b/deterministic.gradle new file mode 100644 index 0000000000..fefca67c76 --- /dev/null +++ b/deterministic.gradle @@ -0,0 +1,29 @@ +/* + * Gradle script plugin: Configure a module such that the Java and Kotlin + * compilers use the deterministic rt.jar instead of the full JDK rt.jar. + */ +apply plugin: 'kotlin' + +evaluationDependsOn(':jdk8u-deterministic') + +def jdk8uDeterministic = project(':jdk8u-deterministic') + +ext { + jdkTask = jdk8uDeterministic.assemble + deterministic_jdk_home = jdk8uDeterministic.jdk_home + deterministic_rt_jar = jdk8uDeterministic.rt_jar +} + +tasks.withType(AbstractCompile) { + dependsOn jdkTask + + // This is a bit ugly, but Gradle isn't recognising the KotlinCompile task + // as it does the built-in JavaCompile task. + if (it.class.name.startsWith("org.jetbrains.kotlin.gradle.tasks.KotlinCompile")) { + kotlinOptions.jdkHome = deterministic_jdk_home + } +} + +tasks.withType(JavaCompile) { + options.compilerArgs << '-bootclasspath' << deterministic_rt_jar +} diff --git a/jdk8u-deterministic/build.gradle b/jdk8u-deterministic/build.gradle index b2810ab8bc..13f468581f 100644 --- a/jdk8u-deterministic/build.gradle +++ b/jdk8u-deterministic/build.gradle @@ -9,6 +9,7 @@ repositories { ext { jdk_home = "$projectDir/jdk" + rt_jar = "$jdk_home/jre/lib/rt.jar".toString() } configurations { diff --git a/serialization-deterministic/build.gradle b/serialization-deterministic/build.gradle index 06f89f6c65..97ef9f7076 100644 --- a/serialization-deterministic/build.gradle +++ b/serialization-deterministic/build.gradle @@ -1,17 +1,14 @@ description 'Corda serialization (deterministic)' -apply plugin: 'kotlin' +apply from: '../deterministic.gradle' apply plugin: 'com.jfrog.artifactory' apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'idea' -evaluationDependsOn(':jdk8u-deterministic') evaluationDependsOn(":serialization") def javaHome = System.getProperty('java.home') def jarBaseName = "corda-${project.name}".toString() -def jdkTask = project(':jdk8u-deterministic').assemble -def deterministic_jdk_home = project(':jdk8u-deterministic').jdk_home configurations { runtimeLibraries @@ -30,18 +27,6 @@ dependencies { runtimeLibraries "org.iq80.snappy:snappy:$snappy_version" } -tasks.withType(AbstractCompile) { - dependsOn jdkTask -} - -tasks.withType(JavaCompile) { - options.compilerArgs << "-bootclasspath" << "$deterministic_jdk_home/jre/lib/rt.jar".toString() -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { - kotlinOptions.jdkHome = deterministic_jdk_home -} - jar { baseName 'DOES-NOT-EXIST' // Don't build a jar here because it would be the wrong one. @@ -150,7 +135,7 @@ task metafix(type: MetaFixerTask) { task checkDeterminism(type: ProGuardTask, dependsOn: jdkTask) { injars metafix - libraryjars "$deterministic_jdk_home/jre/lib/rt.jar" + libraryjars deterministic_rt_jar configurations.runtimeLibraries.forEach { libraryjars it.path, filter: '!META-INF/versions/**' From 6f149a38c9fa052e32765583478f22b77fd01339 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Mon, 18 Jun 2018 15:36:33 +0100 Subject: [PATCH 6/9] ENT-1463: Upgrade to Kotlin 1.2.50 (#3383) * Upgrade to Kotlin 1.2.50 * Remove methods whose implementations are now inherited. --- .ci/api-current.txt | 35 ++++--------------- build.gradle | 2 +- constants.properties | 2 +- .../net/corda/core/contracts/Attachment.kt | 3 ++ .../transactions/TransactionWithSignatures.kt | 5 +++ 5 files changed, 17 insertions(+), 30 deletions(-) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 9b0c4dcb3c..52e9092d84 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -427,14 +427,14 @@ public static final class net.corda.core.contracts.AmountTransfer$Companion exte ## @CordaSerializable public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash - public abstract void extractFile(String, java.io.OutputStream) + public void extractFile(String, java.io.OutputStream) @NotNull public abstract java.util.List getSigners() public abstract int getSize() @NotNull public abstract java.io.InputStream open() @NotNull - public abstract java.util.jar.JarInputStream openAsJAR() + public java.util.jar.JarInputStream openAsJAR() ## @DoNotImplement @CordaSerializable @@ -526,7 +526,6 @@ public final class net.corda.core.contracts.ContractAttachment extends java.lang public (net.corda.core.contracts.Attachment, String) public (net.corda.core.contracts.Attachment, String, java.util.Set) public (net.corda.core.contracts.Attachment, String, java.util.Set, String) - public void extractFile(String, java.io.OutputStream) @NotNull public final java.util.Set getAdditionalContracts() @NotNull @@ -545,8 +544,6 @@ public final class net.corda.core.contracts.ContractAttachment extends java.lang @NotNull public java.io.InputStream open() @NotNull - public java.util.jar.JarInputStream openAsJAR() - @NotNull public String toString() ## @CordaSerializable @@ -4613,7 +4610,6 @@ public static final class net.corda.core.transactions.ContractUpgradeFilteredTra @DoNotImplement public final class net.corda.core.transactions.ContractUpgradeLedgerTransaction extends net.corda.core.transactions.FullTransaction implements net.corda.core.transactions.TransactionWithSignatures public (java.util.List>, net.corda.core.identity.Party, net.corda.core.contracts.Attachment, String, net.corda.core.contracts.Attachment, net.corda.core.crypto.SecureHash, net.corda.core.contracts.PrivacySalt, java.util.List, net.corda.core.node.NetworkParameters) - public void checkSignaturesAreValid() @NotNull public final java.util.List> component1() @NotNull @@ -4642,8 +4638,6 @@ public final class net.corda.core.transactions.ContractUpgradeLedgerTransaction @NotNull public final net.corda.core.contracts.Attachment getLegacyContractAttachment() @NotNull - public java.util.Set getMissingSigners() - @NotNull public net.corda.core.identity.Party getNotary() @NotNull public java.util.List> getOutputs() @@ -4659,9 +4653,6 @@ public final class net.corda.core.transactions.ContractUpgradeLedgerTransaction public final String getUpgradedContractClassName() public int hashCode() public String toString() - public void verifyRequiredSignatures() - public void verifySignaturesExcept(java.util.Collection) - public void verifySignaturesExcept(java.security.PublicKey...) ## @DoNotImplement @CordaSerializable @@ -4879,7 +4870,6 @@ public final class net.corda.core.transactions.MissingContractAttachments extend @DoNotImplement public final class net.corda.core.transactions.NotaryChangeLedgerTransaction extends net.corda.core.transactions.FullTransaction implements net.corda.core.transactions.TransactionWithSignatures public (java.util.List>, net.corda.core.identity.Party, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, java.util.List) - public void checkSignaturesAreValid() @NotNull public final java.util.List> component1() @NotNull @@ -4900,8 +4890,6 @@ public final class net.corda.core.transactions.NotaryChangeLedgerTransaction ext @NotNull public java.util.List getKeyDescriptions(java.util.Set) @NotNull - public java.util.Set getMissingSigners() - @NotNull public final net.corda.core.identity.Party getNewNotary() @NotNull public net.corda.core.identity.Party getNotary() @@ -4913,9 +4901,6 @@ public final class net.corda.core.transactions.NotaryChangeLedgerTransaction ext public java.util.List getSigs() public int hashCode() public String toString() - public void verifyRequiredSignatures() - public void verifySignaturesExcept(java.util.Collection) - public void verifySignaturesExcept(java.security.PublicKey...) ## @DoNotImplement @CordaSerializable @@ -4958,7 +4943,6 @@ public final class net.corda.core.transactions.SignedTransaction extends java.la public (net.corda.core.transactions.CoreTransaction, java.util.List) @NotNull public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(java.util.function.Predicate) - public void checkSignaturesAreValid() @NotNull public final net.corda.core.serialization.SerializedBytes component1() @NotNull @@ -4974,8 +4958,6 @@ public final class net.corda.core.transactions.SignedTransaction extends java.la public final java.util.List getInputs() @NotNull public java.util.ArrayList getKeyDescriptions(java.util.Set) - @NotNull - public java.util.Set getMissingSigners() @Nullable public final net.corda.core.identity.Party getNotary() @NotNull @@ -5012,9 +4994,6 @@ public final class net.corda.core.transactions.SignedTransaction extends java.la public String toString() public final void verify(net.corda.core.node.ServiceHub) public final void verify(net.corda.core.node.ServiceHub, boolean) - public void verifyRequiredSignatures() - public void verifySignaturesExcept(java.util.Collection) - public void verifySignaturesExcept(java.security.PublicKey...) @NotNull public final net.corda.core.transactions.SignedTransaction withAdditionalSignature(java.security.KeyPair, net.corda.core.crypto.SignatureMetadata) @NotNull @@ -5115,18 +5094,18 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob ## @DoNotImplement public interface net.corda.core.transactions.TransactionWithSignatures extends net.corda.core.contracts.NamedByHash - public abstract void checkSignaturesAreValid() + public void checkSignaturesAreValid() @NotNull public abstract java.util.List getKeyDescriptions(java.util.Set) @NotNull - public abstract java.util.Set getMissingSigners() + public java.util.Set getMissingSigners() @NotNull public abstract java.util.Set getRequiredSigningKeys() @NotNull public abstract java.util.List getSigs() - public abstract void verifyRequiredSignatures() - public abstract void verifySignaturesExcept(java.util.Collection) - public abstract void verifySignaturesExcept(java.security.PublicKey...) + public void verifyRequiredSignatures() + public void verifySignaturesExcept(java.util.Collection) + public void verifySignaturesExcept(java.security.PublicKey...) ## @DoNotImplement @CordaSerializable diff --git a/build.gradle b/build.gradle index 0ab201c2bd..02f3e87385 100644 --- a/build.gradle +++ b/build.gradle @@ -180,7 +180,7 @@ allprojects { apiVersion = "1.2" jvmTarget = "1.8" javaParameters = true // Useful for reflection. - freeCompilerArgs = ['-Xenable-jvm-default'] + freeCompilerArgs = ['-Xjvm-default=compatibility'] } } diff --git a/constants.properties b/constants.properties index be17f677e5..e22fe28052 100644 --- a/constants.properties +++ b/constants.properties @@ -1,5 +1,5 @@ gradlePluginsVersion=4.0.23 -kotlinVersion=1.2.41 +kotlinVersion=1.2.50 platformVersion=4 guavaVersion=21.0 proguardVersion=6.0.3 diff --git a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt index 66bdf08a30..8cf0ed5839 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt @@ -32,6 +32,8 @@ import java.util.jar.JarInputStream @CordaSerializable interface Attachment : NamedByHash { fun open(): InputStream + + @JvmDefault fun openAsJAR(): JarInputStream { val stream = open() try { @@ -45,6 +47,7 @@ interface Attachment : NamedByHash { * Finds the named file case insensitively and copies it to the output stream. * @throws FileNotFoundException if the given path doesn't exist in the attachment. */ + @JvmDefault fun extractFile(path: String, outputTo: OutputStream) = openAsJAR().use { it.extractFile(path, outputTo) } /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt index 237abc3e35..74b2a2760a 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt @@ -33,6 +33,7 @@ interface TransactionWithSignatures : NamedByHash { * @throws SignatureException if any signatures are invalid or unrecognised. * @throws SignaturesMissingException if any signatures should have been present but were not. */ + @JvmDefault @Throws(SignatureException::class) fun verifyRequiredSignatures() = verifySignaturesExcept(emptySet()) @@ -48,6 +49,7 @@ interface TransactionWithSignatures : NamedByHash { * @throws SignatureException if any signatures are invalid or unrecognised. * @throws SignaturesMissingException if any signatures should have been present but were not. */ + @JvmDefault @Throws(SignatureException::class) fun verifySignaturesExcept(vararg allowedToBeMissing: PublicKey) { verifySignaturesExcept(Arrays.asList(*allowedToBeMissing)) @@ -65,6 +67,7 @@ interface TransactionWithSignatures : NamedByHash { * @throws SignatureException if any signatures are invalid or unrecognised. * @throws SignaturesMissingException if any signatures should have been present but were not. */ + @JvmDefault @Throws(SignatureException::class) fun verifySignaturesExcept(allowedToBeMissing: Collection) { val needed = getMissingSigners() - allowedToBeMissing @@ -82,6 +85,7 @@ interface TransactionWithSignatures : NamedByHash { * @throws InvalidKeyException if the key on a signature is invalid. * @throws SignatureException if a signature fails to verify. */ + @JvmDefault @Throws(InvalidKeyException::class, SignatureException::class) fun checkSignaturesAreValid() { for (sig in sigs) { @@ -100,6 +104,7 @@ interface TransactionWithSignatures : NamedByHash { /** * Return the [PublicKey]s for which we still need signatures. */ + @JvmDefault fun getMissingSigners(): Set { val sigKeys = sigs.map { it.by }.toSet() // TODO Problem is that we can get single PublicKey wrapped as CompositeKey in allowedToBeMissing/mustSign From e12185139f1014e6f9cead395619186ccb26b6f8 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 18 Jun 2018 16:09:31 +0100 Subject: [PATCH 7/9] CORDA-1641 - Add Java example to proxy serializer documentation (#3390) * CORDA-1641 - Add Java example to proxy serializer documentation * Review comments --- docs/source/cordapp-custom-serializers.rst | 144 ++++++++++++++---- .../amqp/JavaCustomSerializerTests.java | 108 +++++++++++++ 2 files changed, 221 insertions(+), 31 deletions(-) create mode 100644 serialization/src/test/java/net/corda/serialization/internal/amqp/JavaCustomSerializerTests.java diff --git a/docs/source/cordapp-custom-serializers.rst b/docs/source/cordapp-custom-serializers.rst index 14e0159e6b..30be31039a 100644 --- a/docs/source/cordapp-custom-serializers.rst +++ b/docs/source/cordapp-custom-serializers.rst @@ -1,3 +1,9 @@ +.. highlight:: kotlin +.. raw:: html + + + + Pluggable Serializers for CorDapps ================================== @@ -61,44 +67,120 @@ initialisation of all of its properties. To be serializable by Corda this would require a custom serializer to be written that can transform the unserializable class into a form we can serialize. Continuing the above example, this could be written as follows: -.. sourcecode:: kotlin +.. container:: codeset - class ExampleSerializer : SerializationCustomSerializer { - /** - * This is the actual proxy class that is used as an intermediate representation - * of the Example class - */ - data class Proxy(val a: Int, val b: Int) + .. sourcecode:: java /** - * This method should be able to take an instance of the type being proxied and - * transpose it into that form, instantiating an instance of the Proxy object (it - * is this class instance that will be serialized into the byte stream. + * The class lacks a public constructor that takes parameters it can associate + * with its properties and is thus not serializable by the CORDA serialization + * framework. */ - override fun toProxy(obj: Example) = Proxy(obj.a, obj.b) - - /** - * This method is used during deserialization. The bytes will have been read - * from the serialized blob and an instance of the Proxy class returned, we must - * now be able to transform that back into an instance of our original class. - * - * In our example this requires us to evoke the static *of* method on the - * Example class, transforming the serialized properties of the Proxy instance - * into a form expected by the construction method of Example. - */ - override fun fromProxy(proxy: Proxy) : Example { - val constructorArg = IntArray(2); - constructorArg[0] = proxy.a - constructorArg[1] = proxy.b - return Example.of(constructorArg) + class Example { + private int a; + private int b; + + public int getA() { return a; } + public int getB() { return b; } + + public Example(List l) { + this.a = l.get(0); + this.b = l.get(1); + } } - } + + /** + * This is the class that will Proxy instances of Example within the serializer + */ + public class ExampleProxy { + /** + * These properties will be serialized into the byte stream, this is where we choose how to + * represent instances of the object we're proxying. In this example, which is somewhat + * contrived, this choice is obvious. In your own classes / 3rd party libraries, however, this + * may require more thought. + */ + private int proxiedA; + private int proxiedB; -In the above ``ExampleSerializer`` is the actual serializer that will be loaded by the framework to -serialize instances of the ``Example`` type. + /** + * The proxy class itself must be serializable by the framework, it must thus have a constructor that + * can be mapped to the properties of the class via getter methods. + */ + public int getProxiedA() { return proxiedA; } + public int getProxiedB() { return proxiedB; } -``ExampleSerializer.Proxy`` is the intermediate representation used by the framework to represent -instances of ``Example`` within the wire format. + public ExampleProxy(int proxiedA, int proxiedB) { + this.proxiedA = proxiedA; + this.proxiedB = proxiedB; + } + } + + /** + * Finally this is the custom serializer that will automatically loaded into the serialization + * framework when the CorDapp Jar is scanned at runtime. + */ + public class ExampleSerializer implements SerializationCustomSerializer { + + /** + * Given an instance of the Example class, create an instance of the proxying object ExampleProxy. + * + * Essentially convert Example -> ExampleProxy + */ + public ExampleProxy toProxy(Example obj) { + return new ExampleProxy(obj.getA(), obj.getB()); + } + + /** + * Conversely, given an instance of the proxy object, revert that back to an instance of the + * type being proxied. + * + * Essentially convert ExampleProxy -> Example + */ + public Example fromProxy(ExampleProxy proxy) { + List l = new ArrayList(2); + l.add(proxy.getProxiedA()); + l.add(proxy.getProxiedB()); + return new Example(l); + } + } + + .. sourcecode:: kotlin + + class ExampleSerializer : SerializationCustomSerializer { + /** + * This is the actual proxy class that is used as an intermediate representation + * of the Example class + */ + data class Proxy(val a: Int, val b: Int) + + /** + * This method should be able to take an instance of the type being proxied and + * transpose it into that form, instantiating an instance of the Proxy object (it + * is this class instance that will be serialized into the byte stream. + */ + override fun toProxy(obj: Example) = Proxy(obj.a, obj.b) + + /** + * This method is used during deserialization. The bytes will have been read + * from the serialized blob and an instance of the Proxy class returned, we must + * now be able to transform that back into an instance of our original class. + * + * In our example this requires us to evoke the static "of" method on the + * Example class, transforming the serialized properties of the Proxy instance + * into a form expected by the construction method of Example. + */ + override fun fromProxy(proxy: Proxy) : Example { + val constructorArg = IntArray(2); + constructorArg[0] = proxy.a + constructorArg[1] = proxy.b + return Example.of(constructorArg) + } + } + +In the above examples + +- ``ExampleSerializer`` is the actual serializer that will be loaded by the framework to serialize instances of the ``Example`` type. +- ``ExampleSerializer.Proxy``, in the Kotlin example, and ``ExampleProxy`` in the Java example, is the intermediate representation used by the framework to represent instances of ``Example`` within the wire format. The Proxy Object ---------------- diff --git a/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaCustomSerializerTests.java b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaCustomSerializerTests.java new file mode 100644 index 0000000000..b6248d9c16 --- /dev/null +++ b/serialization/src/test/java/net/corda/serialization/internal/amqp/JavaCustomSerializerTests.java @@ -0,0 +1,108 @@ +package net.corda.serialization.internal.amqp; + +import net.corda.core.serialization.SerializationCustomSerializer; +import net.corda.serialization.internal.AllWhitelist; +import net.corda.serialization.internal.amqp.testutils.TestSerializationContext; +import org.junit.Test; + +import java.io.NotSerializableException; +import java.util.ArrayList; +import java.util.List; + +public class JavaCustomSerializerTests { + /** + * The class lacks a public constructor that takes parameters it can associate + * with its properties and is thus not serializable by the CORDA serialization + * framework. + */ + static class Example { + private Integer a; + private Integer b; + + Integer getA() { return a; } + Integer getB() { return b; } + + public Example(List l) { + this.a = l.get(0); + this.b = l.get(1); + } + } + + /** + * This is the class that will Proxy instances of Example within the serializer + */ + public static class ExampleProxy { + /** + * These properties will be serialized into the byte stream, this is where we choose how to + * represent instances of the object we're proxying. In this example, which is somewhat + * contrived, this choice is obvious. In your own classes / 3rd party libraries, however, this + * may require more thought. + */ + private Integer proxiedA; + private Integer proxiedB; + + /** + * The proxu class itself must be serializable by the framework, it must thus have a constructor that + * can be mapped to the properties of the class via getter methods. + */ + public Integer getProxiedA() { return proxiedA; } + public Integer getProxiedB() { return proxiedB; } + + + public ExampleProxy(Integer proxiedA, Integer proxiedB) { + this.proxiedA = proxiedA; + this.proxiedB = proxiedB; + } + } + + /** + * Finally this is the custom serializer that will automatically loaded into the serialization + * framework when the CorDapp Jar is scanned at runtime. + */ + public static class ExampleSerializer implements SerializationCustomSerializer { + + /** + * Given an instance of the Example class, create an instance of the proxying object ExampleProxy. + * + * Essentially convert Example -> ExampleProxy + */ + public ExampleProxy toProxy(Example obj) { + return new ExampleProxy(obj.getA(), obj.getB()); + } + + /** + * Conversely, given an instance of the proxy object, revert that back to an instance of the + * type being proxied. + * + * Essentially convert ExampleProxy -> Example + * + */ + public Example fromProxy(ExampleProxy proxy) { + List l = new ArrayList(2); + l.add(proxy.getProxiedA()); + l.add(proxy.getProxiedB()); + return new Example(l); + } + + } + + @Test + public void serializeExample() throws NotSerializableException, NoSuchFieldException, IllegalAccessException { + SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(), + new EvolutionSerializerGetter(), + new SerializerFingerPrinter()); + SerializationOutput ser = new SerializationOutput(factory); + + List l = new ArrayList(2); + l.add(10); + l.add(20); + Example e = new Example(l); + + CorDappCustomSerializer ccs = new CorDappCustomSerializer(new ExampleSerializer(), factory); + factory.registerExternal(ccs); + + ser.serialize(e, TestSerializationContext.testSerializationContext); + + + } +} From 56f47aaa0a7b0b737b80fce7150d1fcde5f32264 Mon Sep 17 00:00:00 2001 From: Jonathan Sphar Date: Mon, 18 Jun 2018 12:35:26 -0400 Subject: [PATCH 8/9] remove network simulator references in docs (#3396) --- docs/source/network-simulator.rst | 65 ------------------------------- docs/source/tools-index.rst | 1 - 2 files changed, 66 deletions(-) delete mode 100644 docs/source/network-simulator.rst diff --git a/docs/source/network-simulator.rst b/docs/source/network-simulator.rst deleted file mode 100644 index 0d0916d68e..0000000000 --- a/docs/source/network-simulator.rst +++ /dev/null @@ -1,65 +0,0 @@ -Network Simulator -================= - -A network simulator is provided which shows traffic between nodes through the lifecycle of an interest rate swap -contract. It can optionally also show network setup, during which nodes register themselves with the network -map service and are notified of the changes to the map. The network simulator is run from the command line via Gradle: - -**Windows**:: - - gradlew.bat :samples:network-visualiser:run - -**Other**:: - - ./gradlew :samples:network-visualiser:run - -You can produce a standalone JAR of the tool by using the ``:samples:network-visualiser:deployVisualiser`` target -and then using the ``samples/network-visualiser/build/libs/network-visualiser-*-capsule.jar`` file, where * is -whatever the current Corda version is. - -What it is and is not ---------------------- - -The simulator currently exists as an illustrative tool to help with explaining how Corda works in an example scenario. -It utilises the ``Simulator`` tools that support creating a simulated Corda network and the nodes running in it within -a single JVM, as an extension of the ``MockNetwork`` testing framework. See more about the ``MockNetwork`` and -testing flows here: :doc:`flow-testing`. - -Whilst it is not yet fully generic or full featured, the intention is for the simulator to mature into the following, -which it presently cannot do without writing more code: - -1. A tool for visualising new CorDapps and their flows to help with debugging, presentations, explanations and tutorials, - but still running as a simulation in a single JVM. -2. A tool to visualise the activity on a real Corda network deployment, with activity streams being fed from each node - running in its own JVM, most likely on remote hosts. - -Both of these scenarios would be fed by the standard observables in the RPC framework, rather than the local binding -that the simulator uses currently. The ability to step through a flow one step at a time would obviously be restricted -to single JVM simulations. - -Interface ---------- - -.. image:: resources/network-simulator.png - -The network simulator can be run automatically, or stepped manually through each step of the interest rate swap. The -options on the simulator window are: - -Simulate initialisation - If checked, the nodes registering with the network map is shown. Normally this setup step - is not shown, but may be of interest to understand the details of node discovery. -Run - Runs the network simulation in automatic mode, in which it progresses each step on a timed basis. Once running, - the simulation can be paused in order to manually progress it, or reset. -Next - Manually progress the simulation to the next step. -Reset - Reset the simulation (only available when paused). -Map/Circle - How the nodes are shown, by default nodes are rendered on a world map, but alternatively they can rendered - in a circle layout. - -While the simulation runs, details of the steps currently being executed are shown in a sidebar on the left hand side -of the window. - -.. TODO: Add documentation on how to use with different contracts for testing/debugging diff --git a/docs/source/tools-index.rst b/docs/source/tools-index.rst index c9bcafc44b..f2213c7496 100644 --- a/docs/source/tools-index.rst +++ b/docs/source/tools-index.rst @@ -5,6 +5,5 @@ Tools :maxdepth: 1 blob-inspector - network-simulator demobench node-explorer From 100008b139e9dcff1b9d6abc5273b834f2e982df Mon Sep 17 00:00:00 2001 From: Florian Friemel Date: Mon, 18 Jun 2018 17:39:46 +0100 Subject: [PATCH 9/9] [CORDA-1634] Destroy child processes when parent exits. (#3368) * [CORDA-1634] Destroy child processes when parent exits. * Add comment. * Register Shutdownhook for processes regardless of whether the Driver was initialized with * Add comment. * Revert "Add comment." This reverts commit a5e78c379fba4e132cb3287997e77e21a6600947. * Add comment. * Add shutdown hook in ShutdownManager.registerProcessShutdown. * Initialize the ShutdownManager with a shutdown hook to ensure that is called. * Add comment. --- .../net/corda/testing/node/internal/DriverDSLImpl.kt | 9 +++++++-- .../net/corda/testing/node/internal/ShutdownManager.kt | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) 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 fb803440cc..484ad3fa08 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 @@ -93,6 +93,7 @@ class DriverDSLImpl( val networkParameters: NetworkParameters, val notaryCustomOverrides: Map ) : InternalDriverDSL { + private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! private var _shutdownManager: ShutdownManager? = null @@ -653,6 +654,12 @@ class DriverDSLImpl( val debugPort = if (isDebug) debugPortAllocation.nextPort() else null val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null val process = startOutOfProcessNode(config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, systemProperties, cordappPackages, maximumHeapSize) + + // Destroy the child process when the parent exits.This is needed even when `waitForAllNodesToFinish` is + // true because we don't want orphaned processes in the case that the parent process is terminated by the + // user, for example when the `tools:explorer:runDemoNodes` gradle task is stopped with CTRL-C. + shutdownManager.registerProcessShutdown(process) + if (waitForAllNodesToFinish) { state.locked { processes += object : Waitable { @@ -661,8 +668,6 @@ class DriverDSLImpl( } } } - } else { - shutdownManager.registerProcessShutdown(process) } val p2pReadyFuture = addressMustBeBoundFuture(executorService, config.corda.p2pAddress, process) return p2pReadyFuture.flatMap { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt index 433cff4c00..f69c4e8de1 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ShutdownManager.kt @@ -7,12 +7,18 @@ import net.corda.core.utilities.Try import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds +import net.corda.nodeapi.internal.addShutdownHook import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicInteger class ShutdownManager(private val executorService: ExecutorService) { + + init { + addShutdownHook { shutdown() } + } + private class State { val registeredShutdowns = ArrayList Unit>>() var isShuttingDown = false