mirror of
https://github.com/corda/corda.git
synced 2025-02-07 03:29:19 +00:00
Merge remote-tracking branch 'open/master' into os-merge-2018-06-18-17_42
This commit is contained in:
commit
ad76b7821e
@ -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<net.corda.core.identity.Party> 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 <init>(net.corda.core.contracts.Attachment, String)
|
||||
public <init>(net.corda.core.contracts.Attachment, String, java.util.Set<String>)
|
||||
public <init>(net.corda.core.contracts.Attachment, String, java.util.Set<String>, String)
|
||||
public void extractFile(String, java.io.OutputStream)
|
||||
@NotNull
|
||||
public final java.util.Set<String> 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 <init>(java.util.List<? extends net.corda.core.contracts.StateAndRef<? extends net.corda.core.contracts.ContractState>>, 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.crypto.TransactionSignature>, net.corda.core.node.NetworkParameters)
|
||||
public void checkSignaturesAreValid()
|
||||
@NotNull
|
||||
public final java.util.List<net.corda.core.contracts.StateAndRef<net.corda.core.contracts.ContractState>> 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<java.security.PublicKey> getMissingSigners()
|
||||
@NotNull
|
||||
public net.corda.core.identity.Party getNotary()
|
||||
@NotNull
|
||||
public java.util.List<net.corda.core.contracts.TransactionState<net.corda.core.contracts.ContractState>> 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<? extends java.security.PublicKey>)
|
||||
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 <init>(java.util.List<? extends net.corda.core.contracts.StateAndRef<? extends net.corda.core.contracts.ContractState>>, net.corda.core.identity.Party, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, java.util.List<net.corda.core.crypto.TransactionSignature>)
|
||||
public void checkSignaturesAreValid()
|
||||
@NotNull
|
||||
public final java.util.List<net.corda.core.contracts.StateAndRef<net.corda.core.contracts.ContractState>> component1()
|
||||
@NotNull
|
||||
@ -4900,8 +4890,6 @@ public final class net.corda.core.transactions.NotaryChangeLedgerTransaction ext
|
||||
@NotNull
|
||||
public java.util.List<String> getKeyDescriptions(java.util.Set<? extends java.security.PublicKey>)
|
||||
@NotNull
|
||||
public java.util.Set<java.security.PublicKey> 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<net.corda.core.crypto.TransactionSignature> getSigs()
|
||||
public int hashCode()
|
||||
public String toString()
|
||||
public void verifyRequiredSignatures()
|
||||
public void verifySignaturesExcept(java.util.Collection<? extends java.security.PublicKey>)
|
||||
public void verifySignaturesExcept(java.security.PublicKey...)
|
||||
##
|
||||
@DoNotImplement
|
||||
@CordaSerializable
|
||||
@ -4958,7 +4943,6 @@ public final class net.corda.core.transactions.SignedTransaction extends java.la
|
||||
public <init>(net.corda.core.transactions.CoreTransaction, java.util.List<net.corda.core.crypto.TransactionSignature>)
|
||||
@NotNull
|
||||
public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(java.util.function.Predicate<Object>)
|
||||
public void checkSignaturesAreValid()
|
||||
@NotNull
|
||||
public final net.corda.core.serialization.SerializedBytes<net.corda.core.transactions.CoreTransaction> component1()
|
||||
@NotNull
|
||||
@ -4974,8 +4958,6 @@ public final class net.corda.core.transactions.SignedTransaction extends java.la
|
||||
public final java.util.List<net.corda.core.contracts.StateRef> getInputs()
|
||||
@NotNull
|
||||
public java.util.ArrayList<String> getKeyDescriptions(java.util.Set<? extends java.security.PublicKey>)
|
||||
@NotNull
|
||||
public java.util.Set<java.security.PublicKey> 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<? extends java.security.PublicKey>)
|
||||
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<String> getKeyDescriptions(java.util.Set<? extends java.security.PublicKey>)
|
||||
@NotNull
|
||||
public abstract java.util.Set<java.security.PublicKey> getMissingSigners()
|
||||
public java.util.Set<java.security.PublicKey> getMissingSigners()
|
||||
@NotNull
|
||||
public abstract java.util.Set<java.security.PublicKey> getRequiredSigningKeys()
|
||||
@NotNull
|
||||
public abstract java.util.List<net.corda.core.crypto.TransactionSignature> getSigs()
|
||||
public abstract void verifyRequiredSignatures()
|
||||
public abstract void verifySignaturesExcept(java.util.Collection<? extends java.security.PublicKey>)
|
||||
public abstract void verifySignaturesExcept(java.security.PublicKey...)
|
||||
public void verifyRequiredSignatures()
|
||||
public void verifySignaturesExcept(java.util.Collection<? extends java.security.PublicKey>)
|
||||
public void verifySignaturesExcept(java.security.PublicKey...)
|
||||
##
|
||||
@DoNotImplement
|
||||
@CordaSerializable
|
||||
|
@ -100,6 +100,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).
|
||||
@ -195,7 +197,7 @@ allprojects {
|
||||
apiVersion = "1.2"
|
||||
jvmTarget = "1.8"
|
||||
javaParameters = true // Useful for reflection.
|
||||
freeCompilerArgs = ['-Xenable-jvm-default']
|
||||
freeCompilerArgs = ['-Xjvm-default=compatibility']
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
#
|
||||
|
||||
gradlePluginsVersion=4.0.23
|
||||
kotlinVersion=1.2.41
|
||||
kotlinVersion=1.2.50
|
||||
platformVersion=4
|
||||
guavaVersion=21.0
|
||||
proguardVersion=6.0.3
|
||||
|
@ -9,17 +9,15 @@
|
||||
*/
|
||||
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
|
||||
@ -44,18 +42,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.
|
||||
@ -173,7 +159,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/**'
|
||||
@ -209,3 +195,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,8 @@
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
evaluationDependsOn(':jdk8u-deterministic')
|
||||
|
||||
def jdkTask = project(':jdk8u-deterministic').assemble
|
||||
def deterministic_jdk_home = project(':jdk8u-deterministic').jdk_home
|
||||
apply from: '../../../deterministic.gradle'
|
||||
apply plugin: 'idea'
|
||||
|
||||
dependencies {
|
||||
compileOnly project(path: ':core-deterministic', configuration: 'runtimeArtifacts')
|
||||
@ -20,14 +16,10 @@ dependencies {
|
||||
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")) {
|
||||
jdkName project.property("deterministic_idea_sdk") as String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ import java.util.jar.JarInputStream
|
||||
@CordaSerializable
|
||||
interface Attachment : NamedByHash {
|
||||
fun open(): InputStream
|
||||
|
||||
@JvmDefault
|
||||
fun openAsJAR(): JarInputStream {
|
||||
val stream = open()
|
||||
try {
|
||||
@ -55,6 +57,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) }
|
||||
|
||||
/**
|
||||
|
@ -43,6 +43,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())
|
||||
|
||||
@ -58,6 +59,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))
|
||||
@ -75,6 +77,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<PublicKey>) {
|
||||
val needed = getMissingSigners() - allowedToBeMissing
|
||||
@ -92,6 +95,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) {
|
||||
@ -110,6 +114,7 @@ interface TransactionWithSignatures : NamedByHash {
|
||||
/**
|
||||
* Return the [PublicKey]s for which we still need signatures.
|
||||
*/
|
||||
@JvmDefault
|
||||
fun getMissingSigners(): Set<PublicKey> {
|
||||
val sigKeys = sigs.map { it.by }.toSet()
|
||||
// TODO Problem is that we can get single PublicKey wrapped as CompositeKey in allowedToBeMissing/mustSign
|
||||
|
29
deterministic.gradle
Normal file
29
deterministic.gradle
Normal file
@ -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
|
||||
}
|
@ -1,3 +1,9 @@
|
||||
.. highlight:: kotlin
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||
|
||||
Pluggable Serializers for CorDapps
|
||||
==================================
|
||||
|
||||
@ -21,16 +27,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: `<http://github.com/lukehutch/fast-classpath-scanner/wiki/2.-Constructor#scan-spec>`
|
||||
* 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: `<http://github.com/lukehutch/fast-classpath-scanner/wiki/2.-Constructor#scan-spec>`
|
||||
|
||||
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 +44,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 +61,166 @@ 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:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class Example {
|
||||
private int a;
|
||||
private int b;
|
||||
|
||||
public int getA() { return a; }
|
||||
public int getB() { return b; }
|
||||
|
||||
public Example(List<int> 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;
|
||||
|
||||
/**
|
||||
* 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; }
|
||||
|
||||
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<Example, ExampleProxy> {
|
||||
|
||||
/**
|
||||
* 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<int> l = new ArrayList<int>(2);
|
||||
l.add(proxy.getProxiedA());
|
||||
l.add(proxy.getProxiedB());
|
||||
return new Example(l);
|
||||
}
|
||||
}
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class ExampleSerializer : SerializationCustomSerializer<Example, ExampleSerializer.Proxy> {
|
||||
/**
|
||||
* 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
|
||||
----------------
|
||||
|
||||
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<Example, ExampleSerializer.Proxy> {
|
||||
data class Proxy(val a: Int, val b: Int)
|
||||
/**
|
||||
* 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.a, obj.b)
|
||||
override fun toProxy(obj: Example) = Proxy(obj)
|
||||
|
||||
override fun fromProxy(proxy: Proxy) : Example {
|
||||
val constructorArg = IntArray(2);
|
||||
constructorArg[0] = proxy.a
|
||||
constructorArg[1] = proxy.b
|
||||
return Example.create(constructorArg)
|
||||
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
|
||||
|
@ -1,3 +1,9 @@
|
||||
.. raw:: html
|
||||
|
||||
<style> .red {color:red} </style>
|
||||
|
||||
.. 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
|
||||
|
||||
<dependency>
|
||||
<groupId>net.corda</groupId>
|
||||
<artifactId>deterministic-rt</artifactId>
|
||||
<classifier>api</classifier>
|
||||
</dependency>
|
||||
|
||||
..
|
||||
|
||||
.. 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 ``<jdk>`` section to this file:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<jdk version="2">
|
||||
<name value="1.8 (Deterministic)"/>
|
||||
<type value="JavaSDK"/>
|
||||
<version value="java version "1.8.0""/>
|
||||
<homePath value=".. path to the deterministic JDK directory .."/>
|
||||
<roots>
|
||||
</roots>
|
||||
</jdk>
|
||||
|
||||
..
|
||||
|
||||
#. 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.
|
||||
|
@ -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
|
@ -5,6 +5,5 @@ Tools
|
||||
:maxdepth: 1
|
||||
|
||||
blob-inspector
|
||||
network-simulator
|
||||
demobench
|
||||
node-explorer
|
||||
|
1
jdk8u-deterministic/.gitignore
vendored
Normal file
1
jdk8u-deterministic/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
jdk/
|
@ -8,7 +8,8 @@ repositories {
|
||||
}
|
||||
|
||||
ext {
|
||||
jdk_home = "$buildDir/jdk"
|
||||
jdk_home = "$projectDir/jdk"
|
||||
rt_jar = "$jdk_home/jre/lib/rt.jar".toString()
|
||||
}
|
||||
|
||||
configurations {
|
||||
|
@ -259,23 +259,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<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
@ -298,7 +302,12 @@ data class NodeConfigurationImpl(
|
||||
override fun validate(): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
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()
|
||||
@ -313,7 +322,7 @@ data class NodeConfigurationImpl(
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun validateRpcOptions(options: NodeRpcOptions): List<String> {
|
||||
private fun validateRpcSettings(options: NodeRpcSettings): List<String> {
|
||||
val errors = mutableListOf<String>()
|
||||
if (options.address != null) {
|
||||
if (!options.useSsl && options.adminAddress == null) {
|
||||
|
@ -238,6 +238,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()
|
||||
|
@ -9,17 +9,15 @@
|
||||
*/
|
||||
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
|
||||
@ -38,18 +36,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.
|
||||
@ -158,7 +144,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/**'
|
||||
@ -193,3 +179,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
@ -15,9 +15,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
|
||||
@ -57,25 +57,12 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType
|
||||
*/
|
||||
override fun validate(list: List<Transform>, constants: Map<String, Int>) {
|
||||
uncheckedCast<List<Transform>, List<EnumDefaultSchemaTransform>>(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" }
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -93,21 +80,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<Transform>, constants: Map<String, Int>) {
|
||||
object : Any() {
|
||||
val from: MutableSet<String> = mutableSetOf()
|
||||
val to: MutableSet<String> = mutableSetOf()
|
||||
}.apply {
|
||||
@Suppress("UNCHECKED_CAST") (list as List<RenameSchemaTransform>).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<Node>()
|
||||
// Keep two maps of forward links and back links in order to build the graph in one pass
|
||||
val forwardLinks = hashMapOf<String, Node>()
|
||||
val reverseLinks = hashMapOf<String, Node>()
|
||||
|
||||
// build a dependency graph
|
||||
val transforms: List<RenameSchemaTransform> = 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
|
||||
//
|
||||
@ -131,9 +153,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]
|
||||
@ -143,5 +163,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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,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
|
||||
@ -202,6 +206,7 @@ class RenameSchemaTransform(val from: String, val to: String) : Transform() {
|
||||
data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, MutableList<Transform>>>) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<TransformsSchema> {
|
||||
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,
|
||||
@ -249,10 +254,17 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
|
||||
type: String,
|
||||
sf: SerializerFactory,
|
||||
map: MutableMap<String, EnumMap<TransformTypes, MutableList<Transform>>>) {
|
||||
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 ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Integer> 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<Example, ExampleProxy> {
|
||||
|
||||
/**
|
||||
* 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<Integer> l = new ArrayList<Integer>(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<Integer> l = new ArrayList<Integer>(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);
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -11,9 +11,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
|
||||
@ -452,6 +455,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
|
||||
//
|
||||
@ -544,5 +609,4 @@ class EnumEvolvabilityTests {
|
||||
SerializationOutput(sf).serialize(C(RejectBadDefaultToSelf.D))
|
||||
}.isInstanceOf(NotSerializableException::class.java)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -160,7 +160,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()}"
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -104,6 +104,7 @@ class DriverDSLImpl(
|
||||
val networkParameters: NetworkParameters,
|
||||
val notaryCustomOverrides: Map<String, Any?>
|
||||
) : InternalDriverDSL {
|
||||
|
||||
private var _executorService: ScheduledExecutorService? = null
|
||||
val executorService get() = _executorService!!
|
||||
private var _shutdownManager: ShutdownManager? = null
|
||||
@ -660,6 +661,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 {
|
||||
@ -668,8 +675,6 @@ class DriverDSLImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shutdownManager.registerProcessShutdown(process)
|
||||
}
|
||||
val p2pReadyFuture = addressMustBeBoundFuture(executorService, config.corda.p2pAddress, process)
|
||||
return p2pReadyFuture.flatMap {
|
||||
|
@ -17,12 +17,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<CordaFuture<() -> Unit>>()
|
||||
var isShuttingDown = false
|
||||
|
Loading…
x
Reference in New Issue
Block a user