From 222c5b9db8a85e3286f94da4bc952d078266f2c7 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 25 Jan 2018 20:48:20 +0000 Subject: [PATCH 001/114] CORDA-943 - Fix trader demo This is a multi issue problem 1. Fingerprinting of generics treats and differently, forcing the evolver to be used when not needed 2. However, the evolver is required sometimes as generics are not guaranteed to fingerprinting bi-directionally (thanks to type erasure of deeply nested generic types). However, with serialization now writing properties in a specific order, we need to ensure they're read back in that order before applying them to an evolved constructor so as to not corrupt the object reference cache --- .../net/corda/core/contracts/ContractState.kt | 1 - .../amqp/AMQPPrimitiveSerializer.kt | 2 +- .../serialization/amqp/AMQPSerializer.kt | 2 +- .../serialization/amqp/ArraySerializer.kt | 32 ++-- .../amqp/CollectionSerializer.kt | 4 +- .../amqp/CorDappCustomSerializer.kt | 2 +- .../serialization/amqp/CustomSerializer.kt | 2 +- .../amqp/DeserializationInput.kt | 74 +++++---- .../amqp/EnumEvolutionSerializer.kt | 2 +- .../serialization/amqp/EnumSerializer.kt | 2 +- .../serialization/amqp/EvolutionSerializer.kt | 79 ++++++---- .../serialization/amqp/MapSerializer.kt | 6 +- .../serialization/amqp/ObjectSerializer.kt | 4 +- .../serialization/amqp/PropertySerializer.kt | 10 +- .../internal/serialization/amqp/Schema.kt | 32 ++-- .../serialization/amqp/SerializationHelper.kt | 9 +- .../serialization/amqp/SerializationOutput.kt | 12 +- .../serialization/amqp/SerializerFactory.kt | 3 +- .../serialization/amqp/SingletonSerializer.kt | 2 +- .../carpenter/AMQPSchemaExtensions.kt | 7 +- .../serialization/amqp/EvolvabilityTests.kt | 1 + .../serialization/amqp/GenericsTests.kt | 146 +++++++++++++++--- 22 files changed, 295 insertions(+), 139 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/contracts/ContractState.kt b/core/src/main/kotlin/net/corda/core/contracts/ContractState.kt index f797254f21..b9f3d48fc4 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ContractState.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ContractState.kt @@ -1,7 +1,6 @@ package net.corda.core.contracts import net.corda.core.identity.AbstractParty -import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable // DOCSTART 1 diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt index dc673940b1..8695c630f7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt @@ -18,7 +18,7 @@ class AMQPPrimitiveSerializer(clazz: Class<*>) : AMQPSerializer { override fun writeClassInfo(output: SerializationOutput) { } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { if (obj is ByteArray) { data.putObject(Binary(obj)) } else { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt index e70b55d8fc..f306b31f21 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt @@ -30,7 +30,7 @@ interface AMQPSerializer { /** * Write the given object, with declared type, to the output. */ - fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) + fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int = 0) /** * Read the given object from the input. The envelope is provided in case the schema is required. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt index 46046a88f2..7e3e51da97 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt @@ -45,12 +45,12 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory) } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { // Write described data.withDescribed(typeNotation.descriptor) { withList { for (entry in obj as Array<*>) { - output.writeObjectOrNull(entry, this, elementType) + output.writeObjectOrNull(entry, this, elementType, offset) } } } @@ -109,15 +109,15 @@ abstract class PrimArraySerializer(type: Type, factory: SerializerFactory) : Arr class PrimIntArraySerializer(factory: SerializerFactory) : PrimArraySerializer(IntArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { - localWriteObject(data) { (obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + localWriteObject(data) { (obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } } } class PrimCharArraySerializer(factory: SerializerFactory) : PrimArraySerializer(CharArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { - localWriteObject(data) { (obj as CharArray).forEach { output.writeObjectOrNull(it, data, elementType) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + localWriteObject(data) { (obj as CharArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } } override fun List.toArrayOfType(type: Type): Any { @@ -132,35 +132,35 @@ class PrimCharArraySerializer(factory: SerializerFactory) : class PrimBooleanArraySerializer(factory: SerializerFactory) : PrimArraySerializer(BooleanArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { - localWriteObject(data) { (obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + localWriteObject(data) { (obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } } } class PrimDoubleArraySerializer(factory: SerializerFactory) : PrimArraySerializer(DoubleArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { - localWriteObject(data) { (obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + localWriteObject(data) { (obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } } } class PrimFloatArraySerializer(factory: SerializerFactory) : PrimArraySerializer(FloatArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { - localWriteObject(data) { (obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + localWriteObject(data) { (obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } } } class PrimShortArraySerializer(factory: SerializerFactory) : PrimArraySerializer(ShortArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { - localWriteObject(data) { (obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + localWriteObject(data) { (obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } } } class PrimLongArraySerializer(factory: SerializerFactory) : PrimArraySerializer(LongArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { - localWriteObject(data) { (obj as LongArray).forEach { output.writeObjectOrNull(it, data, elementType) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + localWriteObject(data) { (obj as LongArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt index d39456cb9e..90a3b8fe54 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt @@ -66,12 +66,12 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({ declaredType.typeName }) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) = ifThrowsAppend({ declaredType.typeName }) { // Write described data.withDescribed(typeNotation.descriptor) { withList { for (entry in obj as Collection<*>) { - output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0]) + output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0], offset) } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt index 003da13268..12508753f6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt @@ -64,7 +64,7 @@ class CorDappCustomSerializer( override fun writeClassInfo(output: SerializationOutput) {} - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { val proxy = uncheckedCast, SerializationCustomSerializer>(serializer).toProxy(obj) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt index fc0891e93a..143b114fdc 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt @@ -40,7 +40,7 @@ abstract class CustomSerializer : AMQPSerializer, SerializerFor { */ override val revealSubclassesInSchema: Boolean get() = false - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { data.withDescribed(descriptor) { writeDescribedObject(uncheckedCast(obj), data, type, output) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt index d34ee9c12e..ce353f0140 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt @@ -1,5 +1,6 @@ package net.corda.nodeapi.internal.serialization.amqp +import com.google.common.primitives.Primitives import net.corda.core.internal.getStackTraceAsString import net.corda.core.serialization.SerializedBytes import net.corda.core.utilities.ByteSequence @@ -51,12 +52,11 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) { } @Throws(NotSerializableException::class) - inline fun deserialize(bytes: SerializedBytes): T = - deserialize(bytes, T::class.java) + inline fun deserialize(bytes: SerializedBytes): T = deserialize(bytes, T::class.java) @Throws(NotSerializableException::class) inline internal fun deserializeAndReturnEnvelope(bytes: SerializedBytes): ObjectAndEnvelope = - deserializeAndReturnEnvelope(bytes, T::class.java) + deserializeAndReturnEnvelope(bytes, T::class.java) @Throws(NotSerializableException::class) internal fun getEnvelope(bytes: ByteSequence): Envelope { @@ -106,41 +106,47 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) { ObjectAndEnvelope(clazz.cast(readObjectOrNull(envelope.obj, SerializationSchemas(envelope.schema, envelope.transformsSchema), clazz)), envelope) } - internal fun readObjectOrNull(obj: Any?, schema: SerializationSchemas, type: Type): Any? { - return if (obj == null) null else readObject(obj, schema, type) + internal fun readObjectOrNull(obj: Any?, schema: SerializationSchemas, type: Type, offset: Int = 0): Any? { + return if (obj == null) null else readObject(obj, schema, type, offset) } - internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type): Any = - if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) { - // It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference. - val objectIndex = (obj.described as UnsignedInteger).toInt() - if (objectIndex !in 0..objectHistory.size) - throw NotSerializableException("Retrieval of existing reference failed. Requested index $objectIndex " + - "is outside of the bounds for the list of size: ${objectHistory.size}") + internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type, offset: Int = 0): Any { + return if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) { + // It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference. + val objectIndex = (obj.described as UnsignedInteger).toInt() + if (objectIndex !in 0..objectHistory.size) + throw NotSerializableException("Retrieval of existing reference failed. Requested index $objectIndex " + + "is outside of the bounds for the list of size: ${objectHistory.size}") - val objectRetrieved = objectHistory[objectIndex] - if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!)) - throw NotSerializableException("Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}'") - objectRetrieved - } else { - val objectRead = when (obj) { - is DescribedType -> { - // Look up serializer in factory by descriptor - val serializer = serializerFactory.get(obj.descriptor, schemas) - if (SerializerFactory.AnyType != type && serializer.type != type && with(serializer.type) { !isSubClassOf(type) && !materiallyEquivalentTo(type) }) - throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " + - "expected to be of type $type but was ${serializer.type}") - serializer.readObject(obj.described, schemas, this) - } - is Binary -> obj.array - else -> obj // this will be the case for primitive types like [boolean] et al. - } - - // Store the reference in case we need it later on. - // Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content - if (suitableForObjectReference(objectRead.javaClass)) objectHistory.add(objectRead) - objectRead + val objectRetrieved = objectHistory[objectIndex] + if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!)) { + throw NotSerializableException( + "Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}' " + + "@ ${objectIndex}") } + objectRetrieved + } else { + val objectRead = when (obj) { + is DescribedType -> { + // Look up serializer in factory by descriptor + val serializer = serializerFactory.get(obj.descriptor, schemas) + if (SerializerFactory.AnyType != type && serializer.type != type && with(serializer.type) { !isSubClassOf(type) && !materiallyEquivalentTo(type) }) + throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " + + "expected to be of type $type but was ${serializer.type}") + serializer.readObject(obj.described, schemas, this) + } + is Binary -> obj.array + else -> obj // this will be the case for primitive types like [boolean] et al. + } + + // Store the reference in case we need it later on. + // Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content + if (suitableForObjectReference(objectRead.javaClass)) { + objectHistory.add(objectRead) + } + objectRead + } + } /** * Currently performs checks aimed at: diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt index 6e96d4bad8..e7d7010338 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt @@ -130,7 +130,7 @@ class EnumEvolutionSerializer( throw UnsupportedOperationException("It should be impossible to write an evolution serializer") } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { throw UnsupportedOperationException("It should be impossible to write an evolution serializer") } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt index 6aaaf8701e..203c1f8ed9 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt @@ -39,7 +39,7 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria return fromOrd } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { if (obj !is Enum<*>) throw NotSerializableException("Serializing $obj as enum when it isn't") data.withDescribed(typeNotation.descriptor) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt index e3357b84d9..103e0d082e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt @@ -9,15 +9,24 @@ import kotlin.reflect.KFunction import kotlin.reflect.full.findAnnotation import kotlin.reflect.jvm.javaType + /** * Serializer for deserializing objects whose definition has changed since they * were serialised. + * + * @property oldReaders A linked map representing the properties of the object as they were serialized. Note + * this may contain properties that are no longer needed by the class. These *must* be read however to ensure + * any refferenced objects in the object stream are captured properly + * @property kotlinConstructor + * @property constructorArgs used to hold the properties as sent to the object's constructor. Passed in as a + * pre populated array as properties not present on the old constructor must be initialised in the factory */ class EvolutionSerializer( clazz: Type, factory: SerializerFactory, - val readers: List, - override val kotlinConstructor: KFunction?) : ObjectSerializer(clazz, factory) { + private val oldReaders: Map, + override val kotlinConstructor: KFunction?, + private val constructorArgs: Array) : ObjectSerializer(clazz, factory) { // explicitly set as empty to indicate it's unused by this type of serializer override val propertySerializers = PropertySerializersEvolution() @@ -27,13 +36,17 @@ class EvolutionSerializer( * when it was serialised and NOT how that class appears now * * @param type The jvm type of the parameter - * @param idx where in the parameter list this parameter falls. Required as the parameter - * order may have been changed and we need to know where into the list to look + * @param resultsIndex index into the constructor argument list where the read property + * should be placed * @param property object to read the actual property value */ - data class OldParam(val type: Type, val idx: Int, val property: PropertySerializer) { - fun readProperty(paramValues: List<*>, schemas: SerializationSchemas, input: DeserializationInput) = - property.readProperty(paramValues[idx], schemas, input) + data class OldParam(val type: Type, var resultsIndex: Int, val property: PropertySerializer) { + fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput, new: Array) = + property.readProperty(obj, schemas, input).apply { + if(resultsIndex >= 0) { + new[resultsIndex] = this + } + } } companion object { @@ -47,11 +60,11 @@ class EvolutionSerializer( * TODO: Type evolution * TODO: rename annotation */ - private fun getEvolverConstructor(type: Type, oldArgs: Map): KFunction? { + private fun getEvolverConstructor(type: Type, oldArgs: Map): KFunction? { val clazz: Class<*> = type.asClass()!! if (!isConcrete(clazz)) return null - val oldArgumentSet = oldArgs.map { Pair(it.key, it.value) } + val oldArgumentSet = oldArgs.map { Pair(it.key as String?, it.value.type) } var maxConstructorVersion = Integer.MIN_VALUE var constructor: KFunction? = null @@ -83,34 +96,42 @@ class EvolutionSerializer( fun make(old: CompositeType, new: ObjectSerializer, factory: SerializerFactory): AMQPSerializer { - val oldFieldToType = old.fields.map { - it.name as String? to it.getTypeAsClass(factory.classloader) as Type - }.toMap() + val readersAsSerialized = linkedMapOf( + *(old.fields.map { + val returnType = try { + it.getTypeAsClass(factory.classloader) + } catch (e: ClassNotFoundException) { + throw NotSerializableException(e.message) + } - val constructor = getEvolverConstructor(new.type, oldFieldToType) ?: + it.name to OldParam( + returnType, + -1, + PropertySerializer.make( + it.name, PublicPropertyReader(null), returnType, factory)) + }.toTypedArray()) + ) + + val constructor = getEvolverConstructor(new.type, readersAsSerialized) ?: throw NotSerializableException( "Attempt to deserialize an interface: ${new.type}. Serialized form is invalid.") - val oldArgs = mutableMapOf() - var idx = 0 - old.fields.forEach { - val returnType = it.getTypeAsClass(factory.classloader) - oldArgs[it.name] = OldParam( - returnType, idx++, PropertySerializer.make(it.name, PublicPropertyReader(null), returnType, factory)) - } + val constructorArgs = arrayOfNulls(constructor.parameters.size) - val readers = constructor.parameters.map { - oldArgs[it.name!!] ?: if (!it.type.isMarkedNullable) { + constructor.parameters.withIndex().forEach { + readersAsSerialized.get(it.value.name!!)?.apply { + this.resultsIndex = it.index + } ?: if (!it.value.type.isMarkedNullable) { throw NotSerializableException( - "New parameter ${it.name} is mandatory, should be nullable for evolution to worK") - } else null + "New parameter ${it.value.name} is mandatory, should be nullable for evolution to worK") + } } - return EvolutionSerializer(new.type, factory, readers, constructor) + return EvolutionSerializer(new.type, factory, readersAsSerialized, constructor, constructorArgs) } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { throw UnsupportedOperationException("It should be impossible to write an evolution serializer") } @@ -126,7 +147,11 @@ class EvolutionSerializer( override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any { if (obj !is List<*>) throw NotSerializableException("Body of described type is unexpected $obj") - return construct(readers.map { it?.readProperty(obj, schemas, input) }) + // *must* read all the parameters in the order they were serialized + oldReaders.values.zip(obj).map { it.first.readProperty(it.second, schemas, input, constructorArgs) } + + return javaConstructor?.newInstance(*(constructorArgs)) ?: + throw NotSerializableException("Attempt to deserialize an interface: $clazz. Serialized form is invalid.") } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt index ae7f73e6c9..bf7f9c6e84 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt @@ -73,7 +73,7 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({ declaredType.typeName }) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) = ifThrowsAppend({ declaredType.typeName }) { obj.javaClass.checkSupportedMapType() // Write described data.withDescribed(typeNotation.descriptor) { @@ -81,8 +81,8 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial data.putMap() data.enter() for ((key, value) in obj as Map<*, *>) { - output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0]) - output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1]) + output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0], offset) + output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1], offset) } data.exit() // exit map } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt index 034d4b7f93..ad969bea35 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt @@ -52,13 +52,13 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) = ifThrowsAppend({ clazz.typeName }) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) = ifThrowsAppend({ clazz.typeName }) { // Write described data.withDescribed(typeNotation.descriptor) { // Write list withList { propertySerializers.serializationOrder.forEach { property -> - property.getter.writeProperty(obj, this, output) + property.getter.writeProperty(obj, this, output, offset+4) } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt index f749be0ca8..e0df115ff5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt @@ -9,7 +9,7 @@ import java.lang.reflect.Type */ sealed class PropertySerializer(val name: String, val propertyReader: PropertyReader, val resolvedType: Type) { abstract fun writeClassInfo(output: SerializationOutput) - abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) + abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int = 0) abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? val type: String = generateType() @@ -80,8 +80,8 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe input.readObjectOrNull(obj, schemas, resolvedType) } - override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) = ifThrowsAppend({ nameForDebug }) { - output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType) + override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int) = ifThrowsAppend({ nameForDebug }) { + output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType, offset) } private val nameForDebug = "$name(${resolvedType.typeName})" @@ -100,7 +100,7 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe return if (obj is Binary) obj.array else obj } - override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) { + override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int) { val value = propertyReader.read(obj) if (value is ByteArray) { data.putObject(Binary(value)) @@ -123,7 +123,7 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe return if (obj == null) null else (obj as Short).toChar() } - override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput) { + override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int) { val input = propertyReader.read(obj) if (input != null) data.putShort((input as Char).toShort()) else data.putNull() } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index 596d04a2be..6d344a0f98 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -350,13 +350,15 @@ private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFact // creating a unique string for a type which we then hash in the calling function above. private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet, hasher: Hasher, factory: SerializerFactory, offset: Int = 4): Hasher { - // We don't include Example and Example where type is ? or T in this otherwise we // generate different fingerprints for class Outer(val a: Inner) when serialising // and deserializing (assuming deserialization is occurring in a factory that didn't // serialise the object in the first place (and thus the cache lookup fails). This is also // true of Any, where we need Example and Example to have the same fingerprint - return if (type in alreadySeen && (type !is SerializerFactory.AnyType) && (type !is TypeVariable<*>)) { + return if ((type in alreadySeen) + && (type !is SerializerFactory.AnyType) + && (type !is TypeVariable<*>) + && (type !is WildcardType)) { hasher.putUnencodedChars(ALREADY_SEEN_HASH) } else { alreadySeen += type @@ -379,14 +381,20 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta fingerprintForType(paramType, type, alreadySeen, orig, factory, offset+4) } } - // Treat generic types as "any type" to prevent fingerprint mismatch. This case we fall into when - // looking at A and B from Example (remember we call this function recursively). When - // serialising a concrete example of the type we have A and B which are TypeVariables<*>'s but - // when deserializing we only have the wildcard placeholder ?, or AnyType + // Previously, we drew a distinction between TypeVariable, Wildcard, and AnyType, changing + // the signature of the fingerprinted object. This, however, doesn't work as it breaks bi- + // directional fingerprints. That is, fingerprinting a concrete instance of a generic + // type (Example), creates a different fingerprint from the generic type itself (Example) // - // Note, TypeVariable<*> used to be encoded as TYPE_VARIABLE_HASH but that again produces a - // differing fingerprint on serialisation and deserialization + // On serialization Example is treated as Example, a TypeVariable + // On deserialisation it is seen as Example, A wildcard *and* a TypeVariable + // Note: AnyType is a special case of WildcarcType used in other parts of the + // serializer so both cases need to be dealt with here + // + // If we treat these types as fundamentally different and alter the fingerprint we will + // end up breaking into the evolver when we shouldn't or, worse, evoking the carpenter. is SerializerFactory.AnyType, + is WildcardType, is TypeVariable<*> -> { hasher.putUnencodedChars("?").putUnencodedChars(ANY_TYPE_HASH) } @@ -418,9 +426,10 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta } } // Hash the element type + some array hash - is GenericArrayType -> fingerprintForType(type.genericComponentType, contextType, alreadySeen, - hasher, factory, offset+4).putUnencodedChars(ARRAY_HASH) - // TODO: include bounds + is GenericArrayType -> { + fingerprintForType(type.genericComponentType, contextType, alreadySeen, + hasher, factory, offset+4).putUnencodedChars(ARRAY_HASH) + } is WildcardType -> { hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH) } @@ -446,6 +455,7 @@ private fun fingerprintForObject( offset: Int = 0): Hasher { // Hash the class + properties + interfaces val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type") + propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory) .serializationOrder .fold(hasher.putUnencodedChars(name)) { orig, prop -> diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 98274bd638..92aa09fe57 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -62,11 +62,12 @@ internal fun constructorForDeserialization(type: Type): KFunction? { } /** - * Identifies the properties to be used during serialization by attempting to find those that match the parameters to the - * deserialization constructor, if the class is concrete. If it is abstract, or an interface, then use all the properties. + * Identifies the properties to be used during serialization by attempting to find those that match the parameters + * to the deserialization constructor, if the class is concrete. If it is abstract, or an interface, then use all + * the properties. * - * Note, you will need any Java classes to be compiled with the `-parameters` option to ensure constructor parameters have - * names accessible via reflection. + * Note, you will need any Java classes to be compiled with the `-parameters` option to ensure constructor parameters + * have names accessible via reflection. */ internal fun propertiesForSerialization( kotlinConstructor: KFunction?, diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt index 901df19ac2..ff20b8ab68 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt @@ -86,15 +86,15 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory data.putObject(transformsSchema) } - internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type) { + internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type, offset: Int) { if (obj == null) { data.putNull() } else { - writeObject(obj, data, if (type == SerializerFactory.AnyType) obj.javaClass else type) + writeObject(obj, data, if (type == SerializerFactory.AnyType) obj.javaClass else type, offset) } } - internal fun writeObject(obj: Any, data: Data, type: Type) { + internal fun writeObject(obj: Any, data: Data, type: Type, offset: Int = 0) { val serializer = serializerFactory.get(obj.javaClass, type) if (serializer !in serializerHistory) { serializerHistory.add(serializer) @@ -103,11 +103,13 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory val retrievedRefCount = objectHistory[obj] if (retrievedRefCount == null) { - serializer.writeObject(obj, data, type, this) + serializer.writeObject(obj, data, type, this, offset) // Important to do it after serialization such that dependent object will have preceding reference numbers // assigned to them first as they will be first read from the stream on receiving end. // Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content - if (suitableForObjectReference(obj.javaClass)) objectHistory.put(obj, objectHistory.size) + if (suitableForObjectReference(obj.javaClass)) { + objectHistory.put(obj, objectHistory.size) + } } else { data.writeReferencedObject(ReferencedObject(retrievedRefCount)) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index 75f9bf0ded..7bd8178a6d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -112,7 +112,7 @@ open class SerializerFactory( */ // TODO: test GenericArrayType private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>, - declaredType: Type) : Type? = when (declaredType) { + declaredType: Type): Type? = when (declaredType) { is ParameterizedType -> inferTypeVariables(actualClass, declaredClass, declaredType) // Nothing to infer, otherwise we'd have ParameterizedType is Class<*> -> actualClass @@ -218,7 +218,6 @@ open class SerializerFactory( for (typeNotation in schemaAndDescriptor.schemas.schema.types) { try { val serialiser = processSchemaEntry(typeNotation) - // if we just successfully built a serializer for the type but the type fingerprint // doesn't match that of the serialised object then we are dealing with different // instance of the class, as such we need to build an EvolutionSerializer diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt index ac226008f7..7a25baf7cd 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt @@ -22,7 +22,7 @@ class SingletonSerializer(override val type: Class<*>, val singleton: Any, facto output.writeTypeNotations(typeNotation) } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { data.withDescribed(typeNotation.descriptor) { data.putBoolean(false) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt index 6117695b77..b2d3b2d598 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt @@ -124,7 +124,12 @@ fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type "string" -> String::class.java "binary" -> ByteArray::class.java "*" -> if (requires.isEmpty()) Any::class.java else classloader.loadClass(requires[0]) - else -> classloader.loadClass(type) + else -> { + classloader.loadClass( + if (type.endsWith("")) { + type.substring(0, type.length-3) + } else type) + } } fun AMQPField.validateType(classloader: ClassLoader) = when (type) { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index 6d084408f3..164feb2bab 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -6,6 +6,7 @@ import net.corda.testing.common.internal.ProjectStructure.projectRootDir import org.junit.Test import java.io.File import java.io.NotSerializableException +import java.net.URI import kotlin.test.assertEquals // To regenerate any of the binary test files do the following diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt index bd858bb103..d12a5c4056 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt @@ -1,14 +1,30 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.contracts.* import net.corda.core.serialization.SerializedBytes import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.testing.common.internal.ProjectStructure.projectRootDir import org.junit.Test +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.transactions.WireTransaction +import net.corda.testing.core.TestIdentity +import org.hibernate.Transaction import java.io.File import java.net.URI +import java.util.* import java.util.concurrent.ConcurrentHashMap import kotlin.test.assertEquals +data class TestContractState( + override val participants: List +) : ContractState + +class TestAttachmentConstraint : AttachmentConstraint { + override fun isSatisfiedBy(attachment: Attachment) = true +} + class GenericsTests { companion object { val VERBOSE = false @@ -16,6 +32,8 @@ class GenericsTests { @Suppress("UNUSED") var localPath = projectRootDir.toUri().resolve( "node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp") + + val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) } private fun printSeparator() = if (VERBOSE) println("\n\n-------------------------------------------\n\n") else Unit @@ -252,29 +270,119 @@ class GenericsTests { File(GenericsTests::class.java.getResource(resource).toURI()).readBytes())).t) } - interface DifferentBounds { - fun go() + data class StateAndString(val state: TransactionState<*>, val ref: String) + data class GenericStateAndString(val state: TransactionState, val ref: String) + + // + // If this doesn't blow up all is fine + private fun fingerprintingDiffersStrip(state: Any) { + class cl : ClassLoader() + + val m = ClassLoader::class.java.getDeclaredMethod("findLoadedClass", *arrayOf>(String::class.java)) + m.isAccessible = true + + val factory1 = testDefaultFactory() + factory1.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer) + val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state) + + // attempt at having a class loader without some of the derived non core types loaded and thus + // possibly altering how we serialise things + val altClassLoader = cl() + + val factory2 = SerializerFactory(AllWhitelist, altClassLoader) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer) + val ser2 = TestSerializationOutput(VERBOSE, factory2).serializeAndReturnSchema(state) + + // now deserialise those objects + val factory3 = testDefaultFactory() + factory3.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer) + val des1 = DeserializationInput(factory3).deserializeAndReturnEnvelope(ser1.obj) + + val factory4 = SerializerFactory(AllWhitelist, cl()) + factory4.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer) + val des2 = DeserializationInput(factory4).deserializeAndReturnEnvelope(ser2.obj) + } @Test - fun differentBounds() { - data class A (val a: Int): DifferentBounds { - override fun go() { - println(a) - } - } + fun fingerprintingDiffers() { + val state = TransactionState ( + TestContractState(listOf(miniCorp.party)), + "wibble", miniCorp.party, + encumbrance = null, + constraint = TestAttachmentConstraint()) - data class G(val b: T) + val sas = StateAndString(state, "wibble") - val factorys = listOf( - SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()), - SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())) - - val ser = SerializationOutput(factorys[0]) - - ser.serialize(G(A(10))).apply { - factorys.forEach { - } - } + fingerprintingDiffersStrip(sas) } + + @Test + fun fingerprintingDiffersList() { + val state = TransactionState ( + TestContractState(listOf(miniCorp.party)), + "wibble", miniCorp.party, + encumbrance = null, + constraint = TestAttachmentConstraint()) + + val sas = StateAndString(state, "wibble") + + fingerprintingDiffersStrip(Collections.singletonList(sas)) + } + + + // + // Force object to be serialised as Example and deserialized as Example + // + @Test + fun fingerprintingDiffersListLoaded() { + // + // using this wrapper class we force the object to be serialised as + // net.corda.core.contracts.TransactionState + // + data class TransactionStateWrapper (val o: List>) + + val state = TransactionState ( + TestContractState(listOf(miniCorp.party)), + "wibble", miniCorp.party, + encumbrance = null, + constraint = TestAttachmentConstraint()) + + val sas = GenericStateAndString(state, "wibble") + + val factory1 = testDefaultFactoryNoEvolution() + val factory2 = testDefaultFactory() + + factory1.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer) + factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer) + + val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema( + TransactionStateWrapper(Collections.singletonList(sas))) + + val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj) + + assertEquals(sas.ref, des1.obj.o.firstOrNull()?.ref ?: "WILL NOT MATCH") + } + + @Test + fun anotherTry() { + open class BaseState(val a : Int) + class DState(a: Int) : BaseState(a) + data class LTransactionState constructor(val data: T) + data class StateWrapper(val state: LTransactionState) + + val factory1 = testDefaultFactoryNoEvolution() + + val state = LTransactionState(DState(1020304)) + val stateAndString = StateWrapper(state) + + val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString) + + //val factory2 = testDefaultFactoryNoEvolution() + val factory2 = testDefaultFactory() + val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj) + + assertEquals(state.data.a, des1.obj.state.data.a) + } + } From 941429d3a7641c6348dd5096207059af96daae5e Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Wed, 31 Jan 2018 17:05:52 +0000 Subject: [PATCH 002/114] Remove word restriction in corda x500 name (#2439) * Remove word restriction in x500 name https://github.com/corda/corda/issues/2326 * remove unused const * address PR issue * address PR issue * address PR issue --- .../main/kotlin/net/corda/core/identity/CordaX500Name.kt | 4 +--- .../kotlin/net/corda/core/internal/LegalNameValidator.kt | 3 ++- .../net/corda/core/internal/LegalNameValidatorTest.kt | 7 ------- docs/source/changelog.rst | 2 ++ docs/source/generating-a-node.rst | 1 - 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt index abe3d21fd4..5d7e27ac47 100644 --- a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt +++ b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt @@ -50,9 +50,6 @@ data class CordaX500Name(val commonName: String?, // Legal name checks. LegalNameValidator.validateOrganization(organisation, LegalNameValidator.Validation.MINIMAL) - // Attribute data width checks. - require(country.length == LENGTH_COUNTRY) { "Invalid country '$country' Country code must be $LENGTH_COUNTRY letters ISO code " } - require(country.toUpperCase() == country) { "Country code should be in upper case." } require(country in countryCodes) { "Invalid country code $country" } require(organisation.length < MAX_LENGTH_ORGANISATION) { @@ -74,6 +71,7 @@ data class CordaX500Name(val commonName: String?, } companion object { + @Deprecated("Not Used") const val LENGTH_COUNTRY = 2 const val MAX_LENGTH_ORGANISATION = 128 const val MAX_LENGTH_LOCALITY = 64 diff --git a/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt b/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt index ffdba208b8..9f97612852 100644 --- a/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt +++ b/core/src/main/kotlin/net/corda/core/internal/LegalNameValidator.kt @@ -91,7 +91,8 @@ object LegalNameValidator { CapitalLetterRule() ) val legalNameRules: List> = attributeRules + listOf( - WordRule("node", "server"), + // Removal of word restriction was requested in https://github.com/corda/corda/issues/2326 + // WordRule("node", "server"), X500NameRule() ) val legalNameFullRules: List> = legalNameRules + listOf( diff --git a/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt b/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt index 5db052fcc1..922a99117a 100644 --- a/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/LegalNameValidatorTest.kt @@ -27,13 +27,6 @@ class LegalNameValidatorTest { } } - @Test - fun `blacklisted words`() { - assertFailsWith(IllegalArgumentException::class) { - LegalNameValidator.validateOrganization("Test Server", LegalNameValidator.Validation.FULL) - } - } - @Test fun `blacklisted characters`() { LegalNameValidator.validateOrganization("Test", LegalNameValidator.Validation.FULL) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 0878044a79..afe5cd3215 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,6 +6,8 @@ from the previous milestone release. UNRELEASED ---------- +* Removed blacklisted word checks in Corda X.500 name to allow "Server" or "Node" to be use as part of the legal name. + * Separated our pre-existing Artemis broker into an RPC broker and a P2P broker. * Refactored ``NodeConfiguration`` to expose ``NodeRpcOptions`` (using top-level "rpcAddress" property still works with warning). diff --git a/docs/source/generating-a-node.rst b/docs/source/generating-a-node.rst index 6a11dc2721..99c5022344 100644 --- a/docs/source/generating-a-node.rst +++ b/docs/source/generating-a-node.rst @@ -63,7 +63,6 @@ The name must also obey the following constraints: * The organisation field of the name also obeys the following constraints: * No double-spacing - * Does not contain the words "node" or "server" * This is to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and character confusability attacks From 409cdf05e0d24c49768c47f4c2d37f754320dd76 Mon Sep 17 00:00:00 2001 From: Clinton Date: Wed, 31 Jan 2018 20:46:31 +0000 Subject: [PATCH 003/114] Added option to enable docs only builds if a specific file is present (#2441) Added option to enable docs only builds if a specific file is present --- .gitignore | 1 + build.gradle | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f267b20469..0468370c55 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ tags .DS_Store *.log *.orig +corda-docs-only-build # Created by .ignore support plugin (hsz.mobi) diff --git a/build.gradle b/build.gradle index 74f4b681de..c65590cebf 100644 --- a/build.gradle +++ b/build.gradle @@ -334,8 +334,12 @@ task generateApi(type: net.corda.plugins.GenerateApi){ } // This exists to reduce CI build time when the envvar is set (can save up to 40 minutes) -if(System.getenv('CORDA_DOCS_ONLY_BUILD') != null) { - logger.info("Tests are disabled due to presence of envvar CORDA_DOCS_ONLY_BUILD") +if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUILD') != null)) { + if(file('corda-docs-only-build').exists()) { + logger.info("Tests are disabled due to presence of file 'corda-docs-only-build' in the project root") + } else { + logger.info("Tests are disabled due to the presence of envvar CORDA_DOCS_ONLY_BUILD") + } allprojects { test { From 781d517761ff9f01422913b45d763ba65dbc07ab Mon Sep 17 00:00:00 2001 From: Clinton Date: Wed, 31 Jan 2018 20:56:25 +0000 Subject: [PATCH 004/114] Added documentation and extra logging to publish tasks (#2427) Added documentation and extra logging to publish tasks --- .../src/main/groovy/net/corda/plugins/PublishTasks.groovy | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy index 7112ee8117..772ac99d23 100644 --- a/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy +++ b/gradle-plugins/publish-utils/src/main/groovy/net/corda/plugins/PublishTasks.groovy @@ -30,6 +30,10 @@ class PublishTasks implements Plugin { createConfigurations() } + /** + * This call must come at the end of any publish block because it configures the publishing and any + * values set after this call in the DSL will not be configured properly (and will use the default value) + */ void setPublishName(String publishName) { project.logger.info("Changing publishing name from ${project.name} to ${publishName}") this.publishName = publishName @@ -51,12 +55,14 @@ class PublishTasks implements Plugin { } void configureMavenPublish(BintrayConfigExtension bintrayConfig) { + project.logger.info("Configuring maven publish for $publishName") project.apply([plugin: 'maven-publish']) project.publishing.publications.create(publishName, MavenPublication) { groupId project.group artifactId publishName if (publishConfig.publishSources) { + project.logger.info("Publishing sources for $publishName") artifact project.tasks.sourceJar } artifact project.tasks.javadocJar From 0c93f1d4b15828b7da19c837e4b7fb236d64536d Mon Sep 17 00:00:00 2001 From: Anthony Keenan Date: Thu, 1 Feb 2018 10:31:11 +0000 Subject: [PATCH 005/114] Remove exposure of internal hibernate configuration from mockservices (#2442) --- .../services/persistence/HibernateConfigurationTest.kt | 7 +++++-- .../src/main/kotlin/net/corda/testing/node/MockServices.kt | 5 ++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index ffd8aeb02e..dd724c5a6b 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -34,6 +34,7 @@ import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.vault.VaultSchemaV1 import net.corda.node.services.api.IdentityServiceInternal +import net.corda.node.services.vault.NodeVaultService import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.HibernateConfiguration @@ -49,6 +50,7 @@ import org.assertj.core.api.Assertions.assertThat import org.hibernate.SessionFactory import org.junit.* import java.math.BigDecimal +import java.time.Clock import java.time.Instant import java.util.* import javax.persistence.EntityManager @@ -111,11 +113,12 @@ class HibernateConfigurationTest { database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService, schemaService) database.transaction { hibernateConfig = database.hibernateConfig + // `consumeCash` expects we can self-notarise transactions services = object : MockServices(cordappPackages, rigorousMock().also { doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == BOB_NAME }) }, BOB_NAME, generateKeyPair(), dummyNotary.keyPair) { - override val vaultService = makeVaultService(database.hibernateConfig, schemaService) + override val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, validatedTransactions, hibernateConfig) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { for (stx in txs) { validatedTransactions.addTransaction(stx) @@ -127,7 +130,7 @@ class HibernateConfigurationTest { override fun jdbcSession() = database.createSession() } vaultFiller = VaultFiller(services, dummyNotary, notary, ::Random) - hibernatePersister = services.hibernatePersister + hibernatePersister = HibernateObserver.install(services.vaultService.rawUpdates, hibernateConfig, schemaService) } identity = services.myInfo.singleIdentity() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index da99b28c8e..5d1eae0305 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -198,11 +198,10 @@ open class MockServices private constructor( override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2) val mockCordappProvider = MockCordappProvider(cordappLoader, attachments) override val cordappProvider: CordappProvider get() = mockCordappProvider - lateinit var hibernatePersister: HibernateObserver - fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal { + internal fun makeVaultService(hibernateConfig: HibernateConfiguration, schemaService: SchemaService): VaultServiceInternal { val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, validatedTransactions, hibernateConfig) - hibernatePersister = HibernateObserver.install(vaultService.rawUpdates, hibernateConfig, schemaService) + HibernateObserver.install(vaultService.rawUpdates, hibernateConfig, schemaService) return vaultService } From 00b90a98fbabc8b7f6ed55634c0cf08e2fee0105 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 1 Feb 2018 12:19:32 +0000 Subject: [PATCH 006/114] CORDA-943 - Cope with multiple generics at str->type conversion in AMQP Also fixes an odd bug where the inferred type of a getter wasn't matching the constructor parameter type because that was still unbounded and seen as T, looking at the raw type allows us to inspect this properly --- .../amqp/AMQPPrimitiveSerializer.kt | 2 +- .../serialization/amqp/AMQPSerializer.kt | 2 +- .../serialization/amqp/ArraySerializer.kt | 46 ++++--- .../amqp/CollectionSerializer.kt | 14 +- .../amqp/CorDappCustomSerializer.kt | 2 +- .../serialization/amqp/CustomSerializer.kt | 2 +- .../amqp/DeserializationInput.kt | 68 +++++----- .../amqp/EnumEvolutionSerializer.kt | 2 +- .../serialization/amqp/EnumSerializer.kt | 2 +- .../serialization/amqp/MapSerializer.kt | 11 +- .../serialization/amqp/ObjectSerializer.kt | 9 +- .../serialization/amqp/PropertySerializer.kt | 10 +- .../internal/serialization/amqp/Schema.kt | 27 ++-- .../serialization/amqp/SerializationHelper.kt | 21 ++- .../serialization/amqp/SerializationOutput.kt | 8 +- .../serialization/amqp/SingletonSerializer.kt | 2 +- .../carpenter/AMQPSchemaExtensions.kt | 6 +- .../serialization/amqp/GenericsTests.kt | 120 +++++++++++++++++- 18 files changed, 254 insertions(+), 100 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt index 8695c630f7..27db491d33 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPPrimitiveSerializer.kt @@ -18,7 +18,7 @@ class AMQPPrimitiveSerializer(clazz: Class<*>) : AMQPSerializer { override fun writeClassInfo(output: SerializationOutput) { } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { if (obj is ByteArray) { data.putObject(Binary(obj)) } else { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt index f306b31f21..9dfcd03cb7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializer.kt @@ -30,7 +30,7 @@ interface AMQPSerializer { /** * Write the given object, with declared type, to the output. */ - fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int = 0) + fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int = 0) /** * Read the given object from the input. The envelope is provided in case the schema is required. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt index 7e3e51da97..b6b20a204e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ArraySerializer.kt @@ -45,12 +45,12 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory) } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { // Write described data.withDescribed(typeNotation.descriptor) { withList { for (entry in obj as Array<*>) { - output.writeObjectOrNull(entry, this, elementType, offset) + output.writeObjectOrNull(entry, this, elementType, debugIndent) } } } @@ -109,15 +109,19 @@ abstract class PrimArraySerializer(type: Type, factory: SerializerFactory) : Arr class PrimIntArraySerializer(factory: SerializerFactory) : PrimArraySerializer(IntArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { - localWriteObject(data) { (obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + localWriteObject(data) { + (obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + } } } class PrimCharArraySerializer(factory: SerializerFactory) : PrimArraySerializer(CharArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { - localWriteObject(data) { (obj as CharArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + localWriteObject(data) { (obj as CharArray).forEach { + output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + } } override fun List.toArrayOfType(type: Type): Any { @@ -132,35 +136,45 @@ class PrimCharArraySerializer(factory: SerializerFactory) : class PrimBooleanArraySerializer(factory: SerializerFactory) : PrimArraySerializer(BooleanArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { - localWriteObject(data) { (obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + localWriteObject(data) { + (obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + } } } class PrimDoubleArraySerializer(factory: SerializerFactory) : PrimArraySerializer(DoubleArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { - localWriteObject(data) { (obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + localWriteObject(data) { + (obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + } } } class PrimFloatArraySerializer(factory: SerializerFactory) : PrimArraySerializer(FloatArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { - localWriteObject(data) { (obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + localWriteObject(data) { + (obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + } } } class PrimShortArraySerializer(factory: SerializerFactory) : PrimArraySerializer(ShortArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { - localWriteObject(data) { (obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + localWriteObject(data) { + (obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + } } } class PrimLongArraySerializer(factory: SerializerFactory) : PrimArraySerializer(LongArray::class.java, factory) { - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { - localWriteObject(data) { (obj as LongArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } } + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { + localWriteObject(data) { + (obj as LongArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) } + } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt index 90a3b8fe54..bd19e98599 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CollectionSerializer.kt @@ -66,18 +66,26 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) = ifThrowsAppend({ declaredType.typeName }) { + override fun writeObject( + obj: Any, + data: Data, + type: Type, + output: SerializationOutput, + debugIndent: Int) = ifThrowsAppend({ declaredType.typeName }) { // Write described data.withDescribed(typeNotation.descriptor) { withList { for (entry in obj as Collection<*>) { - output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0], offset) + output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0], debugIndent) } } } } - override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) { + override fun readObject( + obj: Any, + schemas: SerializationSchemas, + input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) { // TODO: Can we verify the entries in the list? concreteBuilder((obj as List<*>).map { input.readObjectOrNull(it, schemas, declaredType.actualTypeArguments[0]) }) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt index 12508753f6..161c18d4cc 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CorDappCustomSerializer.kt @@ -64,7 +64,7 @@ class CorDappCustomSerializer( override fun writeClassInfo(output: SerializationOutput) {} - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { val proxy = uncheckedCast, SerializationCustomSerializer>(serializer).toProxy(obj) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt index 143b114fdc..f2ee28d01b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/CustomSerializer.kt @@ -40,7 +40,7 @@ abstract class CustomSerializer : AMQPSerializer, SerializerFor { */ override val revealSubclassesInSchema: Boolean get() = false - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { data.withDescribed(descriptor) { writeDescribedObject(uncheckedCast(obj), data, type, output) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt index ce353f0140..ac61820218 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt @@ -1,6 +1,5 @@ package net.corda.nodeapi.internal.serialization.amqp -import com.google.common.primitives.Primitives import net.corda.core.internal.getStackTraceAsString import net.corda.core.serialization.SerializedBytes import net.corda.core.utilities.ByteSequence @@ -56,7 +55,7 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) { @Throws(NotSerializableException::class) inline internal fun deserializeAndReturnEnvelope(bytes: SerializedBytes): ObjectAndEnvelope = - deserializeAndReturnEnvelope(bytes, T::class.java) + deserializeAndReturnEnvelope(bytes, T::class.java) @Throws(NotSerializableException::class) internal fun getEnvelope(bytes: ByteSequence): Envelope { @@ -110,43 +109,42 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) { return if (obj == null) null else readObject(obj, schema, type, offset) } - internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type, offset: Int = 0): Any { - return if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) { - // It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference. - val objectIndex = (obj.described as UnsignedInteger).toInt() - if (objectIndex !in 0..objectHistory.size) - throw NotSerializableException("Retrieval of existing reference failed. Requested index $objectIndex " + - "is outside of the bounds for the list of size: ${objectHistory.size}") + internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type, debugIndent: Int = 0): Any = + if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) { + // It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference. + val objectIndex = (obj.described as UnsignedInteger).toInt() + if (objectIndex !in 0..objectHistory.size) + throw NotSerializableException("Retrieval of existing reference failed. Requested index $objectIndex " + + "is outside of the bounds for the list of size: ${objectHistory.size}") - val objectRetrieved = objectHistory[objectIndex] - if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!)) { - throw NotSerializableException( - "Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}' " + - "@ ${objectIndex}") - } - objectRetrieved - } else { - val objectRead = when (obj) { - is DescribedType -> { - // Look up serializer in factory by descriptor - val serializer = serializerFactory.get(obj.descriptor, schemas) - if (SerializerFactory.AnyType != type && serializer.type != type && with(serializer.type) { !isSubClassOf(type) && !materiallyEquivalentTo(type) }) - throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " + - "expected to be of type $type but was ${serializer.type}") - serializer.readObject(obj.described, schemas, this) + val objectRetrieved = objectHistory[objectIndex] + if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!)) { + throw NotSerializableException( + "Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}' " + + "@ ${objectIndex}") + } + objectRetrieved + } else { + val objectRead = when (obj) { + is DescribedType -> { + // Look up serializer in factory by descriptor + val serializer = serializerFactory.get(obj.descriptor, schemas) + if (SerializerFactory.AnyType != type && serializer.type != type && with(serializer.type) { !isSubClassOf(type) && !materiallyEquivalentTo(type) }) + throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " + + "expected to be of type $type but was ${serializer.type}") + serializer.readObject(obj.described, schemas, this) + } + is Binary -> obj.array + else -> obj // this will be the case for primitive types like [boolean] et al. } - is Binary -> obj.array - else -> obj // this will be the case for primitive types like [boolean] et al. - } - // Store the reference in case we need it later on. - // Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content - if (suitableForObjectReference(objectRead.javaClass)) { - objectHistory.add(objectRead) + // Store the reference in case we need it later on. + // Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content + if (suitableForObjectReference(objectRead.javaClass)) { + objectHistory.add(objectRead) + } + objectRead } - objectRead - } - } /** * Currently performs checks aimed at: diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt index e7d7010338..a9bc0916b1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolutionSerializer.kt @@ -130,7 +130,7 @@ class EnumEvolutionSerializer( throw UnsupportedOperationException("It should be impossible to write an evolution serializer") } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { throw UnsupportedOperationException("It should be impossible to write an evolution serializer") } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt index 203c1f8ed9..5678f094ed 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumSerializer.kt @@ -39,7 +39,7 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria return fromOrd } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { if (obj !is Enum<*>) throw NotSerializableException("Serializing $obj as enum when it isn't") data.withDescribed(typeNotation.descriptor) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt index bf7f9c6e84..5472074d62 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/MapSerializer.kt @@ -73,7 +73,12 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) = ifThrowsAppend({ declaredType.typeName }) { + override fun writeObject( + obj: Any, + data: Data, + type: Type, + output: SerializationOutput, + debugIndent: Int) = ifThrowsAppend({ declaredType.typeName }) { obj.javaClass.checkSupportedMapType() // Write described data.withDescribed(typeNotation.descriptor) { @@ -81,8 +86,8 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial data.putMap() data.enter() for ((key, value) in obj as Map<*, *>) { - output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0], offset) - output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1], offset) + output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0], debugIndent) + output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1], debugIndent) } data.exit() // exit map } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt index ad969bea35..fa723fd3de 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt @@ -52,13 +52,18 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) = ifThrowsAppend({ clazz.typeName }) { + override fun writeObject( + obj: Any, + data: Data, + type: Type, + output: SerializationOutput, + debugIndent: Int) = ifThrowsAppend({ clazz.typeName }) { // Write described data.withDescribed(typeNotation.descriptor) { // Write list withList { propertySerializers.serializationOrder.forEach { property -> - property.getter.writeProperty(obj, this, output, offset+4) + property.getter.writeProperty(obj, this, output, debugIndent+1) } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt index e0df115ff5..f9fa3d8f83 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializer.kt @@ -9,7 +9,7 @@ import java.lang.reflect.Type */ sealed class PropertySerializer(val name: String, val propertyReader: PropertyReader, val resolvedType: Type) { abstract fun writeClassInfo(output: SerializationOutput) - abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int = 0) + abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int = 0) abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any? val type: String = generateType() @@ -80,8 +80,8 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe input.readObjectOrNull(obj, schemas, resolvedType) } - override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int) = ifThrowsAppend({ nameForDebug }) { - output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType, offset) + override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) = ifThrowsAppend({ nameForDebug }) { + output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType, debugIndent) } private val nameForDebug = "$name(${resolvedType.typeName})" @@ -100,7 +100,7 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe return if (obj is Binary) obj.array else obj } - override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int) { + override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) { val value = propertyReader.read(obj) if (value is ByteArray) { data.putObject(Binary(value)) @@ -123,7 +123,7 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe return if (obj == null) null else (obj as Short).toChar() } - override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int) { + override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) { val input = propertyReader.read(obj) if (input != null) data.putShort((input as Char).toShort()) else data.putNull() } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index 6d344a0f98..372cfed9be 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -349,7 +349,7 @@ private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFact // This method concatenates various elements of the types recursively as unencoded strings into the hasher, effectively // creating a unique string for a type which we then hash in the calling function above. private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet, - hasher: Hasher, factory: SerializerFactory, offset: Int = 4): Hasher { + hasher: Hasher, factory: SerializerFactory, debugIndent: Int = 1): Hasher { // We don't include Example and Example where type is ? or T in this otherwise we // generate different fingerprints for class Outer(val a: Inner) when serialising // and deserializing (assuming deserialization is occurring in a factory that didn't @@ -372,23 +372,23 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta hasher.putUnencodedChars(clazz.name) } else { hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) { - fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4) + fingerprintForObject(type, type, alreadySeen, hasher, factory, debugIndent+1) } } // ... and concatenate the type data for each parameter type. type.actualTypeArguments.fold(startingHash) { orig, paramType -> - fingerprintForType(paramType, type, alreadySeen, orig, factory, offset+4) + fingerprintForType(paramType, type, alreadySeen, orig, factory, debugIndent+1) } } - // Previously, we drew a distinction between TypeVariable, Wildcard, and AnyType, changing + // Previously, we drew a distinction between TypeVariable, WildcardType, and AnyType, changing // the signature of the fingerprinted object. This, however, doesn't work as it breaks bi- // directional fingerprints. That is, fingerprinting a concrete instance of a generic // type (Example), creates a different fingerprint from the generic type itself (Example) // // On serialization Example is treated as Example, a TypeVariable - // On deserialisation it is seen as Example, A wildcard *and* a TypeVariable - // Note: AnyType is a special case of WildcarcType used in other parts of the + // On deserialisation it is seen as Example, A WildcardType *and* a TypeVariable + // Note: AnyType is a special case of WildcardType used in other parts of the // serializer so both cases need to be dealt with here // // If we treat these types as fundamentally different and alter the fingerprint we will @@ -400,7 +400,7 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta } is Class<*> -> { if (type.isArray) { - fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, offset+4) + fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, debugIndent+1) .putUnencodedChars(ARRAY_HASH) } else if (SerializerFactory.isPrimitive(type)) { hasher.putUnencodedChars(type.name) @@ -420,7 +420,7 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta // to the CorDapp but maybe reference to the JAR in the short term. hasher.putUnencodedChars(type.name) } else { - fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4) + fingerprintForObject(type, type, alreadySeen, hasher, factory, debugIndent+1) } } } @@ -428,10 +428,7 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta // Hash the element type + some array hash is GenericArrayType -> { fingerprintForType(type.genericComponentType, contextType, alreadySeen, - hasher, factory, offset+4).putUnencodedChars(ARRAY_HASH) - } - is WildcardType -> { - hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH) + hasher, factory, debugIndent+1).putUnencodedChars(ARRAY_HASH) } else -> throw NotSerializableException("Don't know how to hash") } @@ -452,17 +449,17 @@ private fun fingerprintForObject( alreadySeen: MutableSet, hasher: Hasher, factory: SerializerFactory, - offset: Int = 0): Hasher { + debugIndent: Int = 0): Hasher { // Hash the class + properties + interfaces val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type") propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory) .serializationOrder .fold(hasher.putUnencodedChars(name)) { orig, prop -> - fingerprintForType(prop.getter.resolvedType, type, alreadySeen, orig, factory, offset+4) + fingerprintForType(prop.getter.resolvedType, type, alreadySeen, orig, factory, debugIndent+1) .putUnencodedChars(prop.getter.name) .putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH) } - interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, offset+4) } + interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, debugIndent+4) } return hasher } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 92aa09fe57..9dd0fb30cf 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -114,8 +114,8 @@ internal fun propertiesForSerializationFromConstructor( val returnType = resolveTypeVariables(getter.genericReturnType, type) if (!constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param.value)) { throw NotSerializableException( - "Property type $returnType for $name of $clazz differs from constructor parameter " - + "type ${param.value.type.javaType}") + "Property type '$returnType' for '$name' of '$clazz' differs from constructor parameter " + + "type '${param.value.type.javaType}'") } Pair(PublicPropertyReader(getter), returnType) @@ -166,9 +166,20 @@ private fun propertiesForSerializationFromSetters( } } -private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawGetterReturnType: Type, param: KParameter): Boolean { - val typeToken = TypeToken.of(param.type.javaType) - return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType) +private fun constructorParamTakesReturnTypeOfGetter( + getterReturnType: Type, + rawGetterReturnType: Type, + param: KParameter): Boolean { + val paramToken = TypeToken.of(param.type.javaType) + val rawParamType = TypeToken.of(paramToken.rawType) + + return paramToken.isSupertypeOf(getterReturnType) + || paramToken.isSupertypeOf(rawGetterReturnType) + // cope with the case where the constructor parameter is a generic type (T etc) but we + // can discover it's raw type. When bounded this wil be the bounding type, unbounded + // generics this will be object + || rawParamType.isSupertypeOf(getterReturnType) + || rawParamType.isSupertypeOf(rawGetterReturnType) } private fun propertiesForSerializationFromAbstract( diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt index ff20b8ab68..94b17f6efd 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt @@ -86,15 +86,15 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory data.putObject(transformsSchema) } - internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type, offset: Int) { + internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type, debugIndent: Int) { if (obj == null) { data.putNull() } else { - writeObject(obj, data, if (type == SerializerFactory.AnyType) obj.javaClass else type, offset) + writeObject(obj, data, if (type == SerializerFactory.AnyType) obj.javaClass else type, debugIndent) } } - internal fun writeObject(obj: Any, data: Data, type: Type, offset: Int = 0) { + internal fun writeObject(obj: Any, data: Data, type: Type, debugIndent: Int = 0) { val serializer = serializerFactory.get(obj.javaClass, type) if (serializer !in serializerHistory) { serializerHistory.add(serializer) @@ -103,7 +103,7 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory val retrievedRefCount = objectHistory[obj] if (retrievedRefCount == null) { - serializer.writeObject(obj, data, type, this, offset) + serializer.writeObject(obj, data, type, this, debugIndent) // Important to do it after serialization such that dependent object will have preceding reference numbers // assigned to them first as they will be first read from the stream on receiving end. // Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt index 7a25baf7cd..0c70a18935 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SingletonSerializer.kt @@ -22,7 +22,7 @@ class SingletonSerializer(override val type: Class<*>, val singleton: Any, facto output.writeTypeNotations(typeNotation) } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { data.withDescribed(typeNotation.descriptor) { data.putBoolean(false) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt index b2d3b2d598..1608a22e83 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt @@ -126,9 +126,9 @@ fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type "*" -> if (requires.isEmpty()) Any::class.java else classloader.loadClass(requires[0]) else -> { classloader.loadClass( - if (type.endsWith("")) { - type.substring(0, type.length-3) - } else type) + if (type.endsWith("?>")) { + type.substring(0, type.indexOf('<')) + } else type) } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt index d12a5c4056..60830cda88 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt @@ -27,7 +27,7 @@ class TestAttachmentConstraint : AttachmentConstraint { class GenericsTests { companion object { - val VERBOSE = false + val VERBOSE = true @Suppress("UNUSED") var localPath = projectRootDir.toUri().resolve( @@ -365,7 +365,7 @@ class GenericsTests { } @Test - fun anotherTry() { + fun nestedGenericsWithBound() { open class BaseState(val a : Int) class DState(a: Int) : BaseState(a) data class LTransactionState constructor(val data: T) @@ -385,4 +385,120 @@ class GenericsTests { assertEquals(state.data.a, des1.obj.state.data.a) } + @Test + fun nestedMultiGenericsWithBound() { + open class BaseState(val a : Int) + class DState(a: Int) : BaseState(a) + class EState(a: Int, val msg: String) : BaseState(a) + + data class LTransactionState (val data: T1, val context: T2) + data class StateWrapper(val state: LTransactionState) + + val factory1 = testDefaultFactoryNoEvolution() + + val state = LTransactionState(DState(1020304), EState(5060708, msg = "thigns")) + val stateAndString = StateWrapper(state) + + val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString) + + //val factory2 = testDefaultFactoryNoEvolution() + val factory2 = testDefaultFactory() + val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj) + + assertEquals(state.data.a, des1.obj.state.data.a) + assertEquals(state.context.a, des1.obj.state.context.a) + } + + @Test + fun nestedMultiGenericsNoBound() { + open class BaseState(val a : Int) + class DState(a: Int) : BaseState(a) + class EState(a: Int, val msg: String) : BaseState(a) + + data class LTransactionState (val data: T1, val context: T2) + data class StateWrapper(val state: LTransactionState) + + val factory1 = testDefaultFactoryNoEvolution() + + val state = LTransactionState(DState(1020304), EState(5060708, msg = "things")) + val stateAndString = StateWrapper(state) + + val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString) + + //val factory2 = testDefaultFactoryNoEvolution() + val factory2 = testDefaultFactory() + val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj) + + assertEquals(state.data.a, des1.obj.state.data.a) + assertEquals(state.context.a, des1.obj.state.context.a) + assertEquals(state.context.msg, des1.obj.state.context.msg) + } + + @Test + fun baseClassInheritedButNotOverriden() { + val factory1 = testDefaultFactoryNoEvolution() + val factory2 = testDefaultFactory() + + open class BaseState(open val a : T1, open val b: T2) + class DState(a: T1, b: T2) : BaseState(a, b) + + val state = DState(100, "hello") + val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state) + val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj) + + assertEquals(state.a, des1.obj.a) + assertEquals(state.b, des1.obj.b) + + class DState2(a: T1, b: T2, val c: T3) : BaseState(a, b) + + val state2 = DState2(100, "hello", 100L) + val ser2 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state2) + val des2 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser2.obj) + + assertEquals(state2.a, des2.obj.a) + assertEquals(state2.b, des2.obj.b) + assertEquals(state2.c, des2.obj.c) + } + + @Test + fun baseClassInheritedButNotOverridenBounded() { + val factory1 = testDefaultFactoryNoEvolution() + val factory2 = testDefaultFactory() + + open class Bound(val a: Int) + + open class BaseState(open val a: T1) + class DState(a: T1) : BaseState(a) + + val state = DState(Bound(100)) + val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state) + val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj) + + assertEquals(state.a.a, des1.obj.a.a) + } + + @Test + fun nestedMultiGenericsAtBottomWithBound() { + open class BaseState(val a : T1, val b: T2) + class DState(a: T1, b: T2) : BaseState(a, b) + class EState(a: T1, b: T2, val c: Long) : BaseState(a, b) + + data class LTransactionState, out T4: BaseState> (val data: T3, val context: T4) + data class StateWrapper, out T4: BaseState>(val state: LTransactionState) + + val factory1 = testDefaultFactoryNoEvolution() + + val state = LTransactionState(DState(1020304, "Hello"), EState(5060708, "thins", 100L)) + val stateAndString = StateWrapper(state) + + val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString) + + //val factory2 = testDefaultFactoryNoEvolution() + val factory2 = testDefaultFactory() + val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj) + + assertEquals(state.data.a, des1.obj.state.data.a) + assertEquals(state.context.a, des1.obj.state.context.a) + } + } From 258b562e161834a64a88a40dc022cbf4e12ec181 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Thu, 1 Feb 2018 15:18:53 +0000 Subject: [PATCH 007/114] set network registration poll interval via http cache control header (#434) (#2445) * set network registration poll interval via http cache control header from the server side * default poll interval to 10 seconds if cache header not found * address PR issues * address PR issues (cherry picked from commit dca8699) --- .../node/services/network/NetworkMapClient.kt | 3 ++- .../registration/HTTPNetworkRegistrationService.kt | 14 +++++++++++--- .../registration/NetworkRegistrationHelper.kt | 13 ++++++------- .../registration/NetworkRegistrationService.kt | 5 ++++- .../registration/NetworkRegistrationHelperTest.kt | 3 ++- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt index 9c12056315..39b4a2eb31 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -14,6 +14,7 @@ import net.corda.core.utilities.seconds import net.corda.core.utilities.trace import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.utilities.NamedThreadFactory +import net.corda.node.utilities.registration.cacheControl import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.NetworkParameters @@ -54,7 +55,7 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509C val connection = networkMapUrl.openHttpConnection() val signedNetworkMap = connection.responseAs>() val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustedRoot) - val timeout = CacheControl.parse(Headers.of(connection.headerFields.filterKeys { it != null }.mapValues { it.value[0] })).maxAgeSeconds().seconds + val timeout = connection.cacheControl().maxAgeSeconds().seconds logger.trace { "Fetched network map update from $networkMapUrl successfully, retrieved ${networkMap.nodeInfoHashes.size} node info hashes. Node Info hashes: ${networkMap.nodeInfoHashes.joinToString("\n")}" } return NetworkMapResponse(networkMap, timeout) } diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt index 201cab875d..7f3d0ddf24 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/HTTPNetworkRegistrationService.kt @@ -2,7 +2,10 @@ package net.corda.node.utilities.registration import com.google.common.net.MediaType import net.corda.core.internal.openHttpConnection +import net.corda.core.utilities.seconds import net.corda.nodeapi.internal.crypto.X509CertificateFactory +import okhttp3.CacheControl +import okhttp3.Headers import org.apache.commons.io.IOUtils import org.bouncycastle.pkcs.PKCS10CertificationRequest import java.io.IOException @@ -22,10 +25,13 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr } @Throws(CertificateRequestException::class) - override fun retrieveCertificates(requestId: String): List? { + override fun retrieveCertificates(requestId: String): CertificateResponse { // Poll server to download the signed certificate once request has been approved. val conn = URL("$registrationURL/$requestId").openHttpConnection() conn.requestMethod = "GET" + val maxAge = conn.cacheControl().maxAgeSeconds() + // Default poll interval to 10 seconds if not specified by the server, for backward compatibility. + val pollInterval = if (maxAge == -1) 10.seconds else maxAge.seconds return when (conn.responseCode) { HTTP_OK -> ZipInputStream(conn.inputStream).use { @@ -34,9 +40,9 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr while (it.nextEntry != null) { certificates += factory.generateCertificate(it) } - certificates + CertificateResponse(pollInterval, certificates) } - HTTP_NO_CONTENT -> null + HTTP_NO_CONTENT -> CertificateResponse(pollInterval, null) HTTP_UNAUTHORIZED -> throw CertificateRequestException("Certificate signing request has been rejected: ${conn.errorMessage}") else -> throwUnexpectedResponseCode(conn) } @@ -66,3 +72,5 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL) : NetworkRegistr private val HttpURLConnection.errorMessage: String get() = IOUtils.toString(errorStream, charset) } + +fun HttpURLConnection.cacheControl(): CacheControl = CacheControl.parse(Headers.of(headerFields.filterKeys { it != null }.mapValues { it.value[0] })) diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index a940a28a05..287f4b41c9 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -3,7 +3,6 @@ package net.corda.node.utilities.registration import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* -import net.corda.core.utilities.seconds import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore @@ -28,7 +27,6 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, networkRootTrustStorePath: Path, networkRootTruststorePassword: String) { private companion object { - val pollInterval = 10.seconds const val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key" } @@ -148,17 +146,18 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, * Poll Certificate Signing Server for approved certificate, * enter a slow polling loop if server return null. * @param requestId Certificate signing request ID. - * @return Map of certificate chain. + * @return List of certificate chain. */ private fun pollServerForCertificates(requestId: String): List { println("Start polling server for certificate signing approval.") // Poll server to download the signed certificate once request has been approved. - var certificates = certService.retrieveCertificates(requestId) - while (certificates == null) { + while (true) { + val (pollInterval, certificates) = certService.retrieveCertificates(requestId) + if (certificates != null) { + return certificates + } Thread.sleep(pollInterval.toMillis()) - certificates = certService.retrieveCertificates(requestId) } - return certificates } /** diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationService.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationService.kt index beea4635d5..cafc16dd45 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationService.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationService.kt @@ -4,6 +4,7 @@ import net.corda.core.CordaException import net.corda.core.serialization.CordaSerializable import org.bouncycastle.pkcs.PKCS10CertificationRequest import java.security.cert.X509Certificate +import java.time.Duration interface NetworkRegistrationService { /** Submits a CSR to the signing service and returns an opaque request ID. */ @@ -11,8 +12,10 @@ interface NetworkRegistrationService { /** Poll Certificate Signing Server for the request and returns a chain of certificates if request has been approved, null otherwise. */ @Throws(CertificateRequestException::class) - fun retrieveCertificates(requestId: String): List? + fun retrieveCertificates(requestId: String): CertificateResponse } +data class CertificateResponse(val pollInterval: Duration, val certificates: List?) + @CordaSerializable class CertificateRequestException(message: String) : CordaException(message) diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt index e7e6775a5e..2371ab4437 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt @@ -12,6 +12,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.x500Name +import net.corda.core.utilities.seconds import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore @@ -158,7 +159,7 @@ class NetworkRegistrationHelperTest { private fun createRegistrationHelper(response: List): NetworkRegistrationHelper { val certService = rigorousMock().also { doReturn(requestId).whenever(it).submitRequest(any()) - doReturn(response).whenever(it).retrieveCertificates(eq(requestId)) + doReturn(CertificateResponse(5.seconds, response)).whenever(it).retrieveCertificates(eq(requestId)) } return NetworkRegistrationHelper(config, certService, config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword) } From eb99a7d775b4d8fa156ee21cbd685d3011331de0 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 1 Feb 2018 17:26:54 +0100 Subject: [PATCH 008/114] Refresh API/ABI definition file (#2449) --- .ci/api-current.txt | 1130 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1130 insertions(+) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 54472637a1..0fd2a15fc9 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -14,6 +14,12 @@ public void setMessage(String) public void setOriginalExceptionClassName(String) ## +public final class net.corda.core.CordaOID extends java.lang.Object + @org.jetbrains.annotations.NotNull public static final String CORDA_PLATFORM = "1.3.6.1.4.1.50530.1" + public static final net.corda.core.CordaOID INSTANCE + @org.jetbrains.annotations.NotNull public static final String R3_ROOT = "1.3.6.1.4.1.50530" + @org.jetbrains.annotations.NotNull public static final String X509_EXTENSION_CORDA_ROLE = "1.3.6.1.4.1.50530.1.1" +## @net.corda.core.serialization.CordaSerializable public class net.corda.core.CordaRuntimeException extends java.lang.RuntimeException implements net.corda.core.CordaThrowable public (String) public (String, String, Throwable) @@ -348,6 +354,8 @@ public final class net.corda.core.contracts.ComponentGroupEnum extends java.lang public final class net.corda.core.contracts.ContractsDSL extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.CommandWithParties requireSingleCommand(Collection, Class) public static final Object requireThat(kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public static final List select(Collection, Class, java.security.PublicKey, net.corda.core.identity.AbstractParty) + @org.jetbrains.annotations.NotNull public static final List select(Collection, Class, Collection, Collection) ## @net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.FungibleAsset extends net.corda.core.contracts.OwnableState @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.Amount getAmount() @@ -491,6 +499,7 @@ public final class net.corda.core.contracts.StateAndContract extends java.lang.O public final class net.corda.core.contracts.Structures extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash hash(net.corda.core.contracts.ContractState) @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.Amount withoutIssuer(net.corda.core.contracts.Amount) + public static final int MAX_ISSUER_REF_SIZE = 512 ## @net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TimeWindow extends java.lang.Object public () @@ -616,6 +625,7 @@ public static final class net.corda.core.contracts.UniqueIdentifier$Companion ex @org.jetbrains.annotations.NotNull public abstract String getName() @org.jetbrains.annotations.NotNull public abstract List getRpcFlows() @org.jetbrains.annotations.NotNull public abstract List getSchedulableFlows() + @org.jetbrains.annotations.NotNull public abstract List getSerializationCustomSerializers() @org.jetbrains.annotations.NotNull public abstract List getSerializationWhitelists() @org.jetbrains.annotations.NotNull public abstract List getServiceFlows() @org.jetbrains.annotations.NotNull public abstract List getServices() @@ -1229,6 +1239,8 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object @co.paralleluniverse.fibers.Suspendable @kotlin.jvm.JvmStatic public static final void sleep(java.time.Duration) @co.paralleluniverse.fibers.Suspendable public Object subFlow(net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed track() + @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed trackStepsTree() + @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed trackStepsTreeIndex() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction waitForLedgerCommit(net.corda.core.crypto.SecureHash) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction waitForLedgerCommit(net.corda.core.crypto.SecureHash, boolean) public static final net.corda.core.flows.FlowLogic$Companion Companion @@ -1240,6 +1252,7 @@ public static final class net.corda.core.flows.FlowLogic$Companion extends java. @net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public interface net.corda.core.flows.FlowLogicRef ## @net.corda.core.DoNotImplement public interface net.corda.core.flows.FlowLogicRefFactory + @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowLogic toFlowLogic(net.corda.core.flows.FlowLogicRef) ## @net.corda.core.DoNotImplement public abstract class net.corda.core.flows.FlowSession extends java.lang.Object public () @@ -1303,9 +1316,30 @@ public @interface net.corda.core.flows.InitiatingFlow public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$General extends net.corda.core.flows.NotaryError + public (String) + @org.jetbrains.annotations.NotNull public final String component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$General copy(String) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final String getCause() + public int hashCode() + @org.jetbrains.annotations.NotNull public String toString() +## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid extends net.corda.core.flows.NotaryError + public (java.time.Instant, net.corda.core.contracts.TimeWindow) + @org.jetbrains.annotations.NotNull public final java.time.Instant component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.TimeWindow component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$TimeWindowInvalid copy(java.time.Instant, net.corda.core.contracts.TimeWindow) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final java.time.Instant getCurrentTime() + @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.TimeWindow getTxTimeWindow() + public int hashCode() + @org.jetbrains.annotations.NotNull public String toString() + public static final net.corda.core.flows.NotaryError$TimeWindowInvalid$Companion Companion @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.flows.NotaryError$TimeWindowInvalid INSTANCE ## +public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid$Companion extends java.lang.Object +## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TransactionInvalid extends net.corda.core.flows.NotaryError public (Throwable) @org.jetbrains.annotations.NotNull public final Throwable component1() @@ -1593,18 +1627,27 @@ public final class net.corda.core.messaging.CordaRPCOpsKt extends java.lang.Obje @net.corda.core.DoNotImplement public interface net.corda.core.messaging.FlowProgressHandle extends net.corda.core.messaging.FlowHandle public abstract void close() @org.jetbrains.annotations.NotNull public abstract rx.Observable getProgress() + @org.jetbrains.annotations.Nullable public abstract net.corda.core.messaging.DataFeed getStepsTreeFeed() + @org.jetbrains.annotations.Nullable public abstract net.corda.core.messaging.DataFeed getStepsTreeIndexFeed() ## @net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.messaging.FlowProgressHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowProgressHandle public (net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture, rx.Observable) + public (net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture, rx.Observable, net.corda.core.messaging.DataFeed) + public (net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture, rx.Observable, net.corda.core.messaging.DataFeed, net.corda.core.messaging.DataFeed) public void close() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final net.corda.core.concurrent.CordaFuture component2() @org.jetbrains.annotations.NotNull public final rx.Observable component3() + @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed component4() + @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed component5() @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.FlowProgressHandleImpl copy(net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture, rx.Observable) + @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.FlowProgressHandleImpl copy(net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture, rx.Observable, net.corda.core.messaging.DataFeed, net.corda.core.messaging.DataFeed) public boolean equals(Object) @org.jetbrains.annotations.NotNull public net.corda.core.flows.StateMachineRunId getId() @org.jetbrains.annotations.NotNull public rx.Observable getProgress() @org.jetbrains.annotations.NotNull public net.corda.core.concurrent.CordaFuture getReturnValue() + @org.jetbrains.annotations.Nullable public net.corda.core.messaging.DataFeed getStepsTreeFeed() + @org.jetbrains.annotations.Nullable public net.corda.core.messaging.DataFeed getStepsTreeIndexFeed() public int hashCode() public String toString() ## @@ -1692,6 +1735,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables public final int getPlatformVersion() public final long getSerial() public int hashCode() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.PartyAndCertificate identityAndCertFromX500Name(net.corda.core.identity.CordaX500Name) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party identityFromX500Name(net.corda.core.identity.CordaX500Name) public final boolean isLegalIdentity(net.corda.core.identity.Party) public String toString() @@ -1716,6 +1760,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables public abstract void recordTransactions(Iterable) public abstract void recordTransactions(net.corda.core.node.StatesToRecord, Iterable) public abstract void recordTransactions(boolean, Iterable) + public abstract void registerUnloadHandler(kotlin.jvm.functions.Function0) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, Iterable) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey) @@ -1728,6 +1773,7 @@ public @interface net.corda.core.messaging.RPCReturnsObservables ## @net.corda.core.DoNotImplement public interface net.corda.core.node.StateLoader @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef) + @org.jetbrains.annotations.NotNull public abstract Set loadStates(Set) ## public final class net.corda.core.node.StatesToRecord extends java.lang.Enum protected (String, int) @@ -1735,8 +1781,10 @@ public final class net.corda.core.node.StatesToRecord extends java.lang.Enum public static net.corda.core.node.StatesToRecord[] values() ## @net.corda.core.DoNotImplement public interface net.corda.core.node.services.AttachmentStorage + public abstract boolean hasAttachment(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream) @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream, String, String) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importOrGetAttachment(java.io.InputStream) @org.jetbrains.annotations.Nullable public abstract net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public abstract List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort) ## @@ -1832,11 +1880,13 @@ public @interface net.corda.core.node.services.CordaService @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.ServiceHub getServices() public abstract void start() public abstract void stop() + @kotlin.jvm.JvmStatic public static final void validateTimeWindow(java.time.Clock, net.corda.core.contracts.TimeWindow) public static final net.corda.core.node.services.NotaryService$Companion Companion @org.jetbrains.annotations.NotNull public static final String ID_PREFIX = "corda.notary." ## public static final class net.corda.core.node.services.NotaryService$Companion extends java.lang.Object @kotlin.Deprecated @org.jetbrains.annotations.NotNull public final String constructId(boolean, boolean, boolean, boolean) + @kotlin.jvm.JvmStatic public final void validateTimeWindow(java.time.Clock, net.corda.core.contracts.TimeWindow) ## public abstract class net.corda.core.node.services.PartyInfo extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.Party getParty() @@ -1876,6 +1926,7 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l @net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage extends net.corda.core.node.StateLoader @org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates() + @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() ## @net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionVerifierService @@ -1890,6 +1941,9 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.TransactionSignature sign(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey sign(byte[]) public final void validateTimeWindow(net.corda.core.contracts.TimeWindow) + public static final net.corda.core.node.services.TrustedAuthorityNotaryService$Companion Companion +## +public static final class net.corda.core.node.services.TrustedAuthorityNotaryService$Companion extends java.lang.Object ## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.UniquenessException extends net.corda.core.CordaException public (net.corda.core.node.services.UniquenessProvider$Conflict) @@ -2609,6 +2663,7 @@ public final class net.corda.core.schemas.CommonSchemaV1 extends net.corda.core. public static final net.corda.core.schemas.CommonSchemaV1 INSTANCE ## @javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends net.corda.core.schemas.PersistentState + public () public (Set, net.corda.core.identity.AbstractParty, long, net.corda.core.identity.AbstractParty, byte[]) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AbstractParty getIssuer() @org.jetbrains.annotations.NotNull public final byte[] getIssuerRef() @@ -2622,6 +2677,7 @@ public final class net.corda.core.schemas.CommonSchemaV1 extends net.corda.core. public final void setQuantity(long) ## @javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$LinearState extends net.corda.core.schemas.PersistentState + public () public (Set, String, UUID) public (net.corda.core.contracts.UniqueIdentifier, Set) @org.jetbrains.annotations.Nullable public final String getExternalId() @@ -3145,6 +3201,7 @@ public static final class net.corda.core.utilities.Id$Companion extends java.lan @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Id newInstance(Object, String, java.time.Instant) ## public final class net.corda.core.utilities.KotlinUtilsKt extends java.lang.Object + @org.jetbrains.annotations.NotNull public static final org.slf4j.Logger contextLogger(Object) public static final void debug(org.slf4j.Logger, kotlin.jvm.functions.Function0) public static final int exactAdd(int, int) public static final long exactAdd(long, long) @@ -3230,6 +3287,7 @@ public static final class net.corda.core.utilities.OpaqueBytes$Companion extends @net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.ProgressTracker extends java.lang.Object public final void endWithError(Throwable) @org.jetbrains.annotations.NotNull public final List getAllSteps() + @org.jetbrains.annotations.NotNull public final List getAllStepsLabels() @org.jetbrains.annotations.NotNull public final rx.Observable getChanges() @org.jetbrains.annotations.Nullable public final net.corda.core.utilities.ProgressTracker getChildProgressTracker(net.corda.core.utilities.ProgressTracker$Step) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step getCurrentStep() @@ -3238,12 +3296,16 @@ public static final class net.corda.core.utilities.OpaqueBytes$Companion extends @org.jetbrains.annotations.Nullable public final net.corda.core.utilities.ProgressTracker getParent() public final int getStepIndex() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step[] getSteps() + @org.jetbrains.annotations.NotNull public final rx.Observable getStepsTreeChanges() + public final int getStepsTreeIndex() + @org.jetbrains.annotations.NotNull public final rx.Observable getStepsTreeIndexChanges() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker getTopLevelTracker() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step nextStep() public final void setChildProgressTracker(net.corda.core.utilities.ProgressTracker$Step, net.corda.core.utilities.ProgressTracker) public final void setCurrentStep(net.corda.core.utilities.ProgressTracker$Step) ## @net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.utilities.ProgressTracker$Change extends java.lang.Object + @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker getProgressTracker() ## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$Change$Position extends net.corda.core.utilities.ProgressTracker$Change public (net.corda.core.utilities.ProgressTracker, net.corda.core.utilities.ProgressTracker$Step) @@ -3522,12 +3584,620 @@ public static final class net.corda.client.jackson.StringToMethodCallParser$Unpa public (String) @org.jetbrains.annotations.NotNull public final String getMethodName() ## +public final class net.corda.testing.driver.Driver extends java.lang.Object + public static final Object driver(net.corda.testing.driver.DriverParameters, kotlin.jvm.functions.Function1) + public static final Object driver(net.corda.testing.driver.DriverParameters, boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, kotlin.jvm.functions.Function1) +## +@net.corda.core.DoNotImplement public interface net.corda.testing.driver.DriverDSL + @org.jetbrains.annotations.NotNull public abstract java.nio.file.Path baseDirectory(net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public abstract net.corda.testing.driver.NotaryHandle getDefaultNotaryHandle() + @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.Party getDefaultNotaryIdentity() + @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture getDefaultNotaryNode() + @org.jetbrains.annotations.NotNull public abstract List getNotaryHandles() + @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture startNode(net.corda.testing.driver.NodeParameters) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, List, net.corda.node.services.config.VerifierType, Map, Boolean, String) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture startWebserver(net.corda.testing.driver.NodeHandle) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture startWebserver(net.corda.testing.driver.NodeHandle, String) +## +public final class net.corda.testing.driver.DriverParameters extends java.lang.Object + public () + public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy) + public final boolean component1() + @org.jetbrains.annotations.NotNull public final List component10() + @org.jetbrains.annotations.NotNull public final List component11() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy component12() + @org.jetbrains.annotations.NotNull public final java.nio.file.Path component2() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation component3() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation component4() + @org.jetbrains.annotations.NotNull public final Map component5() + public final boolean component6() + public final boolean component7() + public final boolean component8() + public final boolean component9() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation getDebugPortAllocation() + @org.jetbrains.annotations.NotNull public final java.nio.file.Path getDriverDirectory() + @org.jetbrains.annotations.NotNull public final List getExtraCordappPackagesToScan() + public final boolean getInitialiseSerialization() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy getJmxPolicy() + @org.jetbrains.annotations.NotNull public final List getNotarySpecs() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation getPortAllocation() + public final boolean getStartNodesInProcess() + @org.jetbrains.annotations.NotNull public final Map getSystemProperties() + public final boolean getUseTestClock() + public final boolean getWaitForAllNodesToFinish() + public int hashCode() + public final boolean isDebug() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setDebugPortAllocation(net.corda.testing.driver.PortAllocation) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setDriverDirectory(java.nio.file.Path) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setExtraCordappPackagesToScan(List) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setInitialiseSerialization(boolean) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setIsDebug(boolean) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setJmxPolicy(net.corda.testing.driver.JmxPolicy) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setNotarySpecs(List) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setPortAllocation(net.corda.testing.driver.PortAllocation) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setStartNodesInProcess(boolean) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setSystemProperties(Map) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setUseTestClock(boolean) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters setWaitForAllNodesToFinish(boolean) + public String toString() +## +public final class net.corda.testing.driver.JmxPolicy extends java.lang.Object + public () + public (boolean, net.corda.testing.driver.PortAllocation) + public final boolean component1() + @org.jetbrains.annotations.Nullable public final net.corda.testing.driver.PortAllocation component2() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy copy(boolean, net.corda.testing.driver.PortAllocation) + public boolean equals(Object) + @org.jetbrains.annotations.Nullable public final net.corda.testing.driver.PortAllocation getJmxHttpServerPortAllocation() + public final boolean getStartJmxHttpServer() + public int hashCode() + public String toString() +## +@net.corda.core.DoNotImplement public abstract class net.corda.testing.driver.NodeHandle extends java.lang.Object implements java.lang.AutoCloseable + public void close() + @org.jetbrains.annotations.NotNull public abstract net.corda.node.services.config.NodeConfiguration getConfiguration() + @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getNodeInfo() + @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.CordaRPCOps getRpc() + public abstract boolean getUseHTTPS() + @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.NetworkHostAndPort getWebAddress() + @org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCClient rpcClientToNode() + @org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCClient rpcClientToNode(net.corda.nodeapi.internal.config.SSLConfiguration) + public abstract void stop() +## +public static final class net.corda.testing.driver.NodeHandle$InProcess extends net.corda.testing.driver.NodeHandle + public (net.corda.core.node.NodeInfo, net.corda.core.messaging.CordaRPCOps, net.corda.node.services.config.NodeConfiguration, net.corda.core.utilities.NetworkHostAndPort, boolean, net.corda.node.internal.StartedNode, Thread, kotlin.jvm.functions.Function0) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.CordaRPCOps component2() + @org.jetbrains.annotations.NotNull public final net.corda.node.services.config.NodeConfiguration component3() + @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.NetworkHostAndPort component4() + public final boolean component5() + @org.jetbrains.annotations.NotNull public final net.corda.node.internal.StartedNode component6() + @org.jetbrains.annotations.NotNull public final Thread component7() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeHandle$InProcess copy(net.corda.core.node.NodeInfo, net.corda.core.messaging.CordaRPCOps, net.corda.node.services.config.NodeConfiguration, net.corda.core.utilities.NetworkHostAndPort, boolean, net.corda.node.internal.StartedNode, Thread, kotlin.jvm.functions.Function0) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public net.corda.node.services.config.NodeConfiguration getConfiguration() + @org.jetbrains.annotations.NotNull public final net.corda.node.internal.StartedNode getNode() + @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNodeInfo() + @org.jetbrains.annotations.NotNull public final Thread getNodeThread() + @org.jetbrains.annotations.NotNull public net.corda.core.messaging.CordaRPCOps getRpc() + public boolean getUseHTTPS() + @org.jetbrains.annotations.NotNull public net.corda.core.utilities.NetworkHostAndPort getWebAddress() + public int hashCode() + public void stop() + public String toString() +## +public static final class net.corda.testing.driver.NodeHandle$OutOfProcess extends net.corda.testing.driver.NodeHandle + public (net.corda.core.node.NodeInfo, net.corda.core.messaging.CordaRPCOps, net.corda.node.services.config.NodeConfiguration, net.corda.core.utilities.NetworkHostAndPort, boolean, Integer, Process, kotlin.jvm.functions.Function0) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.CordaRPCOps component2() + @org.jetbrains.annotations.NotNull public final net.corda.node.services.config.NodeConfiguration component3() + @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.NetworkHostAndPort component4() + public final boolean component5() + @org.jetbrains.annotations.Nullable public final Integer component6() + @org.jetbrains.annotations.NotNull public final Process component7() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeHandle$OutOfProcess copy(net.corda.core.node.NodeInfo, net.corda.core.messaging.CordaRPCOps, net.corda.node.services.config.NodeConfiguration, net.corda.core.utilities.NetworkHostAndPort, boolean, Integer, Process, kotlin.jvm.functions.Function0) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public net.corda.node.services.config.NodeConfiguration getConfiguration() + @org.jetbrains.annotations.Nullable public final Integer getDebugPort() + @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getNodeInfo() + @org.jetbrains.annotations.NotNull public final Process getProcess() + @org.jetbrains.annotations.NotNull public net.corda.core.messaging.CordaRPCOps getRpc() + public boolean getUseHTTPS() + @org.jetbrains.annotations.NotNull public net.corda.core.utilities.NetworkHostAndPort getWebAddress() + public int hashCode() + public void stop() + public String toString() +## +public final class net.corda.testing.driver.NodeParameters extends java.lang.Object + public () + public (net.corda.core.identity.CordaX500Name, List, net.corda.node.services.config.VerifierType, Map, Boolean, String) + @org.jetbrains.annotations.Nullable public final net.corda.core.identity.CordaX500Name component1() + @org.jetbrains.annotations.NotNull public final List component2() + @org.jetbrains.annotations.NotNull public final net.corda.node.services.config.VerifierType component3() + @org.jetbrains.annotations.NotNull public final Map component4() + @org.jetbrains.annotations.Nullable public final Boolean component5() + @org.jetbrains.annotations.NotNull public final String component6() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters copy(net.corda.core.identity.CordaX500Name, List, net.corda.node.services.config.VerifierType, Map, Boolean, String) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final Map getCustomOverrides() + @org.jetbrains.annotations.NotNull public final String getMaximumHeapSize() + @org.jetbrains.annotations.Nullable public final net.corda.core.identity.CordaX500Name getProvidedName() + @org.jetbrains.annotations.NotNull public final List getRpcUsers() + @org.jetbrains.annotations.Nullable public final Boolean getStartInSameProcess() + @org.jetbrains.annotations.NotNull public final net.corda.node.services.config.VerifierType getVerifierType() + public int hashCode() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setCustomerOverrides(Map) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setMaximumHeapSize(String) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setProvidedName(net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setRpcUsers(List) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setStartInSameProcess(Boolean) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NodeParameters setVerifierType(net.corda.node.services.config.VerifierType) + public String toString() +## +public final class net.corda.testing.driver.NotaryHandle extends java.lang.Object + public (net.corda.core.identity.Party, boolean, net.corda.core.concurrent.CordaFuture) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() + public final boolean component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.concurrent.CordaFuture component3() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.NotaryHandle copy(net.corda.core.identity.Party, boolean, net.corda.core.concurrent.CordaFuture) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getIdentity() + @org.jetbrains.annotations.NotNull public final net.corda.core.concurrent.CordaFuture getNodeHandles() + public final boolean getValidating() + public int hashCode() + public String toString() +## +@net.corda.core.DoNotImplement public abstract class net.corda.testing.driver.PortAllocation extends java.lang.Object + @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.NetworkHostAndPort nextHostAndPort() + public abstract int nextPort() +## +public static final class net.corda.testing.driver.PortAllocation$Incremental extends net.corda.testing.driver.PortAllocation + public (int) + @org.jetbrains.annotations.NotNull public final concurrent.atomic.AtomicInteger getPortCounter() + public int nextPort() +## +public static final class net.corda.testing.driver.PortAllocation$RandomFree extends net.corda.testing.driver.PortAllocation + public int nextPort() + public static final net.corda.testing.driver.PortAllocation$RandomFree INSTANCE +## +public final class net.corda.testing.driver.WebserverHandle extends java.lang.Object + public (net.corda.core.utilities.NetworkHostAndPort, Process) + @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.NetworkHostAndPort component1() + @org.jetbrains.annotations.NotNull public final Process component2() + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.WebserverHandle copy(net.corda.core.utilities.NetworkHostAndPort, Process) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.NetworkHostAndPort getListenAddress() + @org.jetbrains.annotations.NotNull public final Process getProcess() + public int hashCode() + public String toString() +## +@net.corda.core.DoNotImplement public abstract class net.corda.testing.node.ClusterSpec extends java.lang.Object + public () + public abstract int getClusterSize() +## +public static final class net.corda.testing.node.ClusterSpec$Raft extends net.corda.testing.node.ClusterSpec + public (int) + public final int component1() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.ClusterSpec$Raft copy(int) + public boolean equals(Object) + public int getClusterSize() + public int hashCode() + public String toString() +## +@javax.annotation.concurrent.ThreadSafe public final class net.corda.testing.node.InMemoryMessagingNetwork extends net.corda.core.serialization.SingletonSerializeAsToken + public (boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, org.apache.activemq.artemis.utils.ReusableLatch) + @org.jetbrains.annotations.NotNull public synchronized final List getEndpoints() + @org.jetbrains.annotations.NotNull public final rx.Observable getReceivedMessages() + @org.jetbrains.annotations.NotNull public final rx.Observable getSentMessages() + @org.jetbrains.annotations.Nullable public final net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer pumpSend(boolean) + public final void stop() + public static final net.corda.testing.node.InMemoryMessagingNetwork$Companion Companion +## +public static final class net.corda.testing.node.InMemoryMessagingNetwork$Companion extends java.lang.Object +## +public static final class net.corda.testing.node.InMemoryMessagingNetwork$InMemoryMessage extends java.lang.Object implements net.corda.node.services.messaging.Message + public (net.corda.node.services.messaging.TopicSession, byte[], UUID, java.time.Instant) + @org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.TopicSession component1() + @org.jetbrains.annotations.NotNull public final byte[] component2() + @org.jetbrains.annotations.NotNull public final UUID component3() + @org.jetbrains.annotations.NotNull public final java.time.Instant component4() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$InMemoryMessage copy(net.corda.node.services.messaging.TopicSession, byte[], UUID, java.time.Instant) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public byte[] getData() + @org.jetbrains.annotations.NotNull public java.time.Instant getDebugTimestamp() + @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.TopicSession getTopicSession() + @org.jetbrains.annotations.NotNull public UUID getUniqueMessageId() + public int hashCode() + @org.jetbrains.annotations.NotNull public String toString() +## public static interface net.corda.testing.node.InMemoryMessagingNetwork$LatencyCalculator @org.jetbrains.annotations.NotNull public abstract java.time.Duration between(net.corda.core.messaging.SingleMessageRecipient, net.corda.core.messaging.SingleMessageRecipient) ## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer extends java.lang.Object + public (net.corda.testing.node.InMemoryMessagingNetwork$PeerHandle, net.corda.node.services.messaging.Message, net.corda.core.messaging.MessageRecipients) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$PeerHandle component1() + @org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.Message component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.MessageRecipients component3() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer copy(net.corda.testing.node.InMemoryMessagingNetwork$PeerHandle, net.corda.node.services.messaging.Message, net.corda.core.messaging.MessageRecipients) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.Message getMessage() + @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.MessageRecipients getRecipients() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$PeerHandle getSender() + public int hashCode() + @org.jetbrains.annotations.NotNull public String toString() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.testing.node.InMemoryMessagingNetwork$PeerHandle extends java.lang.Object implements net.corda.core.messaging.SingleMessageRecipient + public (int, net.corda.core.identity.CordaX500Name) + public final int component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name component2() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$PeerHandle copy(int, net.corda.core.identity.CordaX500Name) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name getDescription() + public final int getId() + public int hashCode() + @org.jetbrains.annotations.NotNull public String toString() +## +@net.corda.core.serialization.CordaSerializable public static final class net.corda.testing.node.InMemoryMessagingNetwork$ServiceHandle extends java.lang.Object implements net.corda.core.messaging.MessageRecipientGroup + public (net.corda.core.identity.Party) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServiceHandle copy(net.corda.core.identity.Party) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getParty() + public int hashCode() + @org.jetbrains.annotations.NotNull public String toString() +## +@net.corda.core.DoNotImplement public abstract static class net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy extends java.lang.Object + public abstract Object pickNext(net.corda.testing.node.InMemoryMessagingNetwork$ServiceHandle, List) +## +public static final class net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy$Random extends net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy + public () + public (SplittableRandom) + @org.jetbrains.annotations.NotNull public final SplittableRandom getRandom() + public Object pickNext(net.corda.testing.node.InMemoryMessagingNetwork$ServiceHandle, List) +## +public static final class net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy$RoundRobin extends net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy + public () + public Object pickNext(net.corda.testing.node.InMemoryMessagingNetwork$ServiceHandle, List) +## +@net.corda.core.DoNotImplement public static interface net.corda.testing.node.InMemoryMessagingNetwork$TestMessagingService extends net.corda.node.services.messaging.MessagingService + @org.jetbrains.annotations.Nullable public abstract net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer pumpReceive(boolean) + public abstract void stop() +## +public static final class net.corda.testing.node.InMemoryMessagingNetwork$pumpSend$$inlined$schedule$1 extends java.util.TimerTask + public (net.corda.testing.node.InMemoryMessagingNetwork, net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer, net.corda.core.internal.concurrent.OpenFuture) + public void run() +## +public class net.corda.testing.node.MessagingServiceSpy extends java.lang.Object implements net.corda.node.services.messaging.MessagingService + public (net.corda.node.services.messaging.MessagingService) + @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.MessageHandlerRegistration addMessageHandler(String, long, kotlin.jvm.functions.Function2) + @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.MessageHandlerRegistration addMessageHandler(net.corda.node.services.messaging.TopicSession, kotlin.jvm.functions.Function2) + public void cancelRedelivery(long) + @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.Message createMessage(net.corda.node.services.messaging.TopicSession, byte[], UUID) + @org.jetbrains.annotations.NotNull public net.corda.core.messaging.MessageRecipients getAddressOfParty(net.corda.core.node.services.PartyInfo) + @org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.MessagingService getMessagingService() + @org.jetbrains.annotations.NotNull public net.corda.core.messaging.SingleMessageRecipient getMyAddress() + public void removeMessageHandler(net.corda.node.services.messaging.MessageHandlerRegistration) + public void send(List, kotlin.jvm.functions.Function0) + public void send(net.corda.node.services.messaging.Message, net.corda.core.messaging.MessageRecipients, Long, Object, kotlin.jvm.functions.Function0) +## +public final class net.corda.testing.node.MockKeyManagementService extends net.corda.core.serialization.SingletonSerializeAsToken implements net.corda.core.node.services.KeyManagementService + @org.jetbrains.annotations.NotNull public Iterable filterMyKeys(Iterable) + @org.jetbrains.annotations.NotNull public java.security.PublicKey freshKey() + @org.jetbrains.annotations.NotNull public net.corda.core.identity.PartyAndCertificate freshKeyAndCert(net.corda.core.identity.PartyAndCertificate, boolean) + @org.jetbrains.annotations.NotNull public final net.corda.node.services.api.IdentityServiceInternal getIdentityService() + @org.jetbrains.annotations.NotNull public Set getKeys() + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature sign(net.corda.core.crypto.SignableData, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.DigitalSignature$WithKey sign(byte[], java.security.PublicKey) +## +public class net.corda.testing.node.MockNetwork extends java.lang.Object + public (List) + public (List, net.corda.testing.node.MockNetworkParameters) + public (List, net.corda.testing.node.MockNetworkParameters, boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, kotlin.jvm.functions.Function1, boolean, List) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetwork$MockNode addressToNode(net.corda.core.messaging.MessageRecipients) + @org.jetbrains.annotations.NotNull public final java.nio.file.Path baseDirectory(int) + @org.jetbrains.annotations.NotNull public final net.corda.node.internal.StartedNode createNode(net.corda.testing.node.MockNodeParameters) + @org.jetbrains.annotations.NotNull public final net.corda.node.internal.StartedNode createNode(net.corda.testing.node.MockNodeParameters, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public final net.corda.node.internal.StartedNode createPartyNode() + @org.jetbrains.annotations.NotNull public final net.corda.node.internal.StartedNode createPartyNode(net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetwork$MockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetwork$MockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getDefaultNotaryIdentity() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.PartyAndCertificate getDefaultNotaryIdentityAndCert() + @org.jetbrains.annotations.NotNull public final net.corda.node.internal.StartedNode getDefaultNotaryNode() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork getMessagingNetwork() + public final int getNextNodeId() + @org.jetbrains.annotations.NotNull public final List getNodes() + @org.jetbrains.annotations.NotNull public final List getNotaryNodes() + public final void runNetwork() + public final void runNetwork(int) + public final void startNodes() + public final void stopNodes() + public final void waitQuiescent() +## +public static class net.corda.testing.node.MockNetwork$MockNode extends net.corda.node.internal.AbstractNode + public (net.corda.testing.node.MockNodeArgs) + protected int acceptableLiveFiberCountOnStop() + protected void checkNetworkMapIsInitialized() + public final void disableDBCloseOnStop() + @org.jetbrains.annotations.NotNull protected java.security.KeyPair generateKeyPair() + public final int getAcceptableLiveFiberCountOnStop() + @org.jetbrains.annotations.NotNull public final java.math.BigInteger getCounter() + public final int getId() + @org.jetbrains.annotations.NotNull protected org.slf4j.Logger getLog() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetwork getMockNet() + @org.jetbrains.annotations.NotNull protected rx.internal.schedulers.CachedThreadScheduler getRxIoScheduler() + @org.jetbrains.annotations.NotNull public List getSerializationWhitelists() + @org.jetbrains.annotations.NotNull protected net.corda.node.utilities.AffinityExecutor getServerThread() + @org.jetbrains.annotations.Nullable public net.corda.node.internal.StartedNode getStarted() + @org.jetbrains.annotations.NotNull public final List getTestSerializationWhitelists() + protected Object initialiseDatabasePersistence(net.corda.node.services.api.SchemaService, net.corda.core.node.services.IdentityService, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull protected net.corda.node.services.transactions.BFTSMaRt$Cluster makeBFTCluster(java.security.PublicKey, net.corda.node.services.config.BFTSMaRtConfiguration) + @org.jetbrains.annotations.NotNull protected net.corda.core.node.services.KeyManagementService makeKeyManagementService(net.corda.node.services.api.IdentityServiceInternal, Set) + @org.jetbrains.annotations.NotNull protected net.corda.node.services.messaging.MessagingService makeMessagingService(net.corda.nodeapi.internal.persistence.CordaPersistence, net.corda.core.node.NodeInfo) + @org.jetbrains.annotations.NotNull protected net.corda.node.services.transactions.InMemoryTransactionVerifierService makeTransactionVerifierService() + public final void manuallyCloseDB() + @org.jetbrains.annotations.NotNull protected List myAddresses() + public final void setAcceptableLiveFiberCountOnStop(int) + public final void setCounter(java.math.BigInteger) + public final void setMessagingServiceSpy(net.corda.testing.node.MessagingServiceSpy) + @org.jetbrains.annotations.NotNull public net.corda.node.internal.StartedNode start() + protected void startMessagingService(net.corda.core.messaging.RPCOps) + public void startShell(net.corda.core.messaging.CordaRPCOps) + public static final net.corda.testing.node.MockNetwork$MockNode$Companion Companion +## +public static final class net.corda.testing.node.MockNetwork$MockNode$Companion extends java.lang.Object +## +public static final class net.corda.testing.node.MockNetwork$MockNode$makeBFTCluster$1 extends java.lang.Object implements net.corda.node.services.transactions.BFTSMaRt$Cluster + public void waitUntilAllReplicasHaveInitialized() +## +public static final class net.corda.testing.node.MockNetwork$NotarySpec extends java.lang.Object + public (net.corda.core.identity.CordaX500Name) + public (net.corda.core.identity.CordaX500Name, boolean) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name component1() + public final boolean component2() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetwork$NotarySpec copy(net.corda.core.identity.CordaX500Name, boolean) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name getName() + public final boolean getValidating() + public int hashCode() + public String toString() +## +public static final class net.corda.testing.node.MockNetwork$sharedServerThread$1 extends net.corda.node.utilities.AffinityExecutor$ServiceAffinityExecutor + public boolean awaitTermination(long, concurrent.TimeUnit) + public void shutdown() +## +public final class net.corda.testing.node.MockNetworkParameters extends java.lang.Object + public () + public (boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, kotlin.jvm.functions.Function1, boolean, List) + public final boolean component1() + public final boolean component2() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy component3() + @org.jetbrains.annotations.NotNull public final kotlin.jvm.functions.Function1 component4() + public final boolean component5() + @org.jetbrains.annotations.NotNull public final List component6() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters copy(boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, kotlin.jvm.functions.Function1, boolean, List) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final kotlin.jvm.functions.Function1 getDefaultFactory() + public final boolean getInitialiseSerialization() + public final boolean getNetworkSendManuallyPumped() + @org.jetbrains.annotations.NotNull public final List getNotarySpecs() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy getServicePeerAllocationStrategy() + public final boolean getThreadPerNode() + public int hashCode() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setDefaultFactory(kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setInitialiseSerialization(boolean) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setNetworkSendManuallyPumped(boolean) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setNotarySpecs(List) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setServicePeerAllocationStrategy(net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetworkParameters setThreadPerNode(boolean) + public String toString() +## +public final class net.corda.testing.node.MockNodeArgs extends java.lang.Object + public (net.corda.node.services.config.NodeConfiguration, net.corda.testing.node.MockNetwork, int, java.math.BigInteger, net.corda.node.VersionInfo) + @org.jetbrains.annotations.NotNull public final net.corda.node.services.config.NodeConfiguration component1() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetwork component2() + public final int component3() + @org.jetbrains.annotations.NotNull public final java.math.BigInteger component4() + @org.jetbrains.annotations.NotNull public final net.corda.node.VersionInfo component5() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeArgs copy(net.corda.node.services.config.NodeConfiguration, net.corda.testing.node.MockNetwork, int, java.math.BigInteger, net.corda.node.VersionInfo) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.node.services.config.NodeConfiguration getConfig() + @org.jetbrains.annotations.NotNull public final java.math.BigInteger getEntropyRoot() + public final int getId() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetwork getNetwork() + @org.jetbrains.annotations.NotNull public final net.corda.node.VersionInfo getVersion() + public int hashCode() + public String toString() +## +public final class net.corda.testing.node.MockNodeKt extends java.lang.Object + @org.jetbrains.annotations.Nullable public static final net.corda.testing.node.InMemoryMessagingNetwork$MessageTransfer pumpReceive(net.corda.node.internal.StartedNode, boolean) + public static final void setMessagingServiceSpy(net.corda.node.internal.StartedNode, net.corda.testing.node.MessagingServiceSpy) +## +@net.corda.core.DoNotImplement public abstract static class net.corda.testing.node.MockNodeKt$mockNodeConfiguration$AbstractNodeConfiguration extends java.lang.Object implements net.corda.node.services.config.NodeConfiguration + public () + public long getAttachmentCacheBound() + public long getAttachmentContentCacheSizeBytes() + @org.jetbrains.annotations.NotNull public java.nio.file.Path getCertificatesDirectory() + public boolean getDetectPublicIp() + @org.jetbrains.annotations.NotNull public java.nio.file.Path getNodeKeystore() + @org.jetbrains.annotations.NotNull public java.nio.file.Path getSslKeystore() + public long getTransactionCacheSizeBytes() + @org.jetbrains.annotations.NotNull public java.nio.file.Path getTrustStoreFile() + public boolean getUseTestClock() + @org.jetbrains.annotations.NotNull public net.corda.nodeapi.internal.crypto.X509KeyStore loadNodeKeyStore(boolean) + @org.jetbrains.annotations.NotNull public net.corda.nodeapi.internal.crypto.X509KeyStore loadSslKeyStore(boolean) + @org.jetbrains.annotations.NotNull public net.corda.nodeapi.internal.crypto.X509KeyStore loadTrustStore(boolean) +## +public final class net.corda.testing.node.MockNodeParameters extends java.lang.Object + public () + public (Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, net.corda.node.VersionInfo) + @org.jetbrains.annotations.Nullable public final Integer component1() + @org.jetbrains.annotations.Nullable public final net.corda.core.identity.CordaX500Name component2() + @org.jetbrains.annotations.NotNull public final java.math.BigInteger component3() + @org.jetbrains.annotations.NotNull public final kotlin.jvm.functions.Function1 component4() + @org.jetbrains.annotations.NotNull public final net.corda.node.VersionInfo component5() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, net.corda.node.VersionInfo) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final kotlin.jvm.functions.Function1 getConfigOverrides() + @org.jetbrains.annotations.NotNull public final java.math.BigInteger getEntropyRoot() + @org.jetbrains.annotations.Nullable public final Integer getForcedID() + @org.jetbrains.annotations.Nullable public final net.corda.core.identity.CordaX500Name getLegalName() + @org.jetbrains.annotations.NotNull public final net.corda.node.VersionInfo getVersion() + public int hashCode() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setConfigOverrides(kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setEntropyRoot(java.math.BigInteger) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setForcedID(Integer) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNodeParameters setLegalName(net.corda.core.identity.CordaX500Name) + public String toString() +## +public class net.corda.testing.node.MockServices extends java.lang.Object implements net.corda.core.node.StateLoader, net.corda.core.node.ServiceHub + public (List, net.corda.core.identity.CordaX500Name) + public (List, net.corda.node.services.api.IdentityServiceInternal, net.corda.core.identity.CordaX500Name) + public (net.corda.core.identity.CordaX500Name) + public (net.corda.node.services.api.IdentityServiceInternal, net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializeAsToken cordaService(Class) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public final net.corda.testing.services.MockAttachmentStorage getAttachments() + @org.jetbrains.annotations.NotNull public java.time.Clock getClock() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService() + @org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappProvider getCordappProvider() + @org.jetbrains.annotations.NotNull public final com.google.common.collect.MutableClassToInstanceMap getCordappServices() + @org.jetbrains.annotations.NotNull public net.corda.node.services.api.IdentityServiceInternal getIdentityService() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.KeyManagementService getKeyManagementService() + @org.jetbrains.annotations.NotNull public static final net.corda.node.VersionInfo getMOCK_VERSION_INFO() + @org.jetbrains.annotations.NotNull public final net.corda.testing.services.MockCordappProvider getMockCordappProvider() + @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getMyInfo() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.NetworkMapCache getNetworkMapCache() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService() + @org.jetbrains.annotations.NotNull public net.corda.node.services.api.WritableTransactionStorage getValidatedTransactions() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.VaultService getVaultService() + @org.jetbrains.annotations.NotNull public java.sql.Connection jdbcSession() + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef) + @org.jetbrains.annotations.NotNull public Set loadStates(Set) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final Properties makeTestDataSourceProperties(String) + public void recordTransactions(Iterable) + public void recordTransactions(net.corda.core.node.StatesToRecord, Iterable) + public void recordTransactions(boolean, Iterable) + @org.jetbrains.annotations.NotNull public Void registerUnloadHandler(kotlin.jvm.functions.Function0) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, Iterable) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.StateAndRef toStateAndRef(net.corda.core.contracts.StateRef) + public static final net.corda.testing.node.MockServices$Companion Companion +## +public static final class net.corda.testing.node.MockServices$Companion extends java.lang.Object + @org.jetbrains.annotations.NotNull public final net.corda.node.VersionInfo getMOCK_VERSION_INFO() + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final Properties makeTestDataSourceProperties(String) +## +public static final class net.corda.testing.node.MockServices$Companion$makeTestDatabaseAndMockServices$mockService$1$1 extends net.corda.testing.node.MockServices + @org.jetbrains.annotations.NotNull public net.corda.node.services.api.VaultServiceInternal getVaultService() + @org.jetbrains.annotations.NotNull public java.sql.Connection jdbcSession() + public void recordTransactions(net.corda.core.node.StatesToRecord, Iterable) +## +public final class net.corda.testing.node.MockServicesKt extends java.lang.Object + @org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializeAsToken createMockCordaService(net.corda.testing.node.MockServices, kotlin.jvm.functions.Function1) +## +public static final class net.corda.testing.node.MockServicesKt$createMockCordaService$MockAppServiceHubImpl extends java.lang.Object implements net.corda.core.node.AppServiceHub, net.corda.core.node.ServiceHub + public (net.corda.testing.node.MockServices, net.corda.testing.node.MockServices, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializeAsToken cordaService(Class) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.FilteredTransaction, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.TransactionSignature createSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments() + @org.jetbrains.annotations.NotNull public java.time.Clock getClock() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.ContractUpgradeService getContractUpgradeService() + @org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappProvider getCordappProvider() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.IdentityService getIdentityService() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.KeyManagementService getKeyManagementService() + @org.jetbrains.annotations.NotNull public net.corda.core.node.NodeInfo getMyInfo() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.NetworkMapCache getNetworkMapCache() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockServices getServiceHub() + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializeAsToken getServiceInstance() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.TransactionStorage getValidatedTransactions() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.VaultService getVaultService() + @org.jetbrains.annotations.NotNull public java.sql.Connection jdbcSession() + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef) + @org.jetbrains.annotations.NotNull public Set loadStates(Set) + public void recordTransactions(Iterable) + public void recordTransactions(net.corda.core.node.StatesToRecord, Iterable) + public void recordTransactions(boolean, Iterable) + public void registerUnloadHandler(kotlin.jvm.functions.Function0) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, Iterable) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic) + @org.jetbrains.annotations.NotNull public net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic) + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.StateAndRef toStateAndRef(net.corda.core.contracts.StateRef) +## +public class net.corda.testing.node.MockTransactionStorage extends net.corda.core.serialization.SingletonSerializeAsToken implements net.corda.node.services.api.WritableTransactionStorage + public () + public boolean addTransaction(net.corda.core.transactions.SignedTransaction) + @org.jetbrains.annotations.Nullable public net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash) + @org.jetbrains.annotations.NotNull public rx.Observable getUpdates() + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef) + @org.jetbrains.annotations.NotNull public Set loadStates(Set) + @org.jetbrains.annotations.NotNull public net.corda.core.messaging.DataFeed track() +## +public final class net.corda.testing.node.NodeTestUtils extends java.lang.Object + @org.jetbrains.annotations.NotNull public static final net.corda.testing.dsl.LedgerDSL ledger(net.corda.core.node.ServiceHub, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public static final net.corda.testing.dsl.LedgerDSL ledger(net.corda.core.node.ServiceHub, net.corda.core.identity.Party, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext newContext(net.corda.node.services.api.StartedNodeServices) + @org.jetbrains.annotations.NotNull public static final net.corda.core.internal.FlowStateMachine startFlow(net.corda.node.services.api.StartedNodeServices, net.corda.core.flows.FlowLogic) + @org.jetbrains.annotations.NotNull public static final net.corda.core.context.Actor testActor(net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext testContext(net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public static final net.corda.testing.dsl.LedgerDSL transaction(net.corda.core.node.ServiceHub, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public static final net.corda.testing.dsl.LedgerDSL transaction(net.corda.core.node.ServiceHub, net.corda.core.identity.Party, kotlin.jvm.functions.Function1) +## +public final class net.corda.testing.node.NotarySpec extends java.lang.Object + public (net.corda.core.identity.CordaX500Name, boolean, List, net.corda.node.services.config.VerifierType, net.corda.testing.node.ClusterSpec) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name component1() + public final boolean component2() + @org.jetbrains.annotations.NotNull public final List component3() + @org.jetbrains.annotations.NotNull public final net.corda.node.services.config.VerifierType component4() + @org.jetbrains.annotations.Nullable public final net.corda.testing.node.ClusterSpec component5() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.NotarySpec copy(net.corda.core.identity.CordaX500Name, boolean, List, net.corda.node.services.config.VerifierType, net.corda.testing.node.ClusterSpec) + public boolean equals(Object) + @org.jetbrains.annotations.Nullable public final net.corda.testing.node.ClusterSpec getCluster() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name getName() + @org.jetbrains.annotations.NotNull public final List getRpcUsers() + public final boolean getValidating() + @org.jetbrains.annotations.NotNull public final net.corda.node.services.config.VerifierType getVerifierType() + public int hashCode() + public String toString() +## +@javax.annotation.concurrent.ThreadSafe public final class net.corda.testing.node.TestClock extends net.corda.node.internal.MutableClock + public (java.time.Clock) + public synchronized final void advanceBy(java.time.Duration) + public synchronized final void setTo(java.time.Instant) +## +public final class net.corda.testing.node.User extends java.lang.Object + public (String, String, Set) + @org.jetbrains.annotations.NotNull public final String component1() + @org.jetbrains.annotations.NotNull public final String component2() + @org.jetbrains.annotations.NotNull public final Set component3() + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.User copy(String, String, Set) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final String getPassword() + @org.jetbrains.annotations.NotNull public final Set getPermissions() + @org.jetbrains.annotations.NotNull public final String getUsername() + public int hashCode() + public String toString() +## public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object public (net.corda.core.utilities.NetworkHostAndPort) public (net.corda.core.utilities.NetworkHostAndPort, net.corda.client.rpc.CordaRPCClientConfiguration) + public (net.corda.core.utilities.NetworkHostAndPort, net.corda.client.rpc.CordaRPCClientConfiguration, net.corda.nodeapi.internal.config.SSLConfiguration) @org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCConnection start(String, String) @org.jetbrains.annotations.NotNull public final net.corda.client.rpc.CordaRPCConnection start(String, String, net.corda.core.context.Trace, net.corda.core.context.Actor) public final Object use(String, String, kotlin.jvm.functions.Function1) @@ -3572,3 +4242,463 @@ public @interface net.corda.client.rpc.RPCSinceVersion public final class net.corda.client.rpc.UtilsKt extends java.lang.Object public static final void notUsed(rx.Observable) ## +public final class net.corda.testing.contracts.DummyContract extends java.lang.Object implements net.corda.core.contracts.Contract + public () + public (Object) + @org.jetbrains.annotations.Nullable public final Object component1() + @org.jetbrains.annotations.NotNull public final net.corda.testing.contracts.DummyContract copy(Object) + public boolean equals(Object) + @org.jetbrains.annotations.Nullable public final Object getBlank() + @org.jetbrains.annotations.NotNull public final String getPROGRAM_ID() + public int hashCode() + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.transactions.TransactionBuilder move(List, net.corda.core.identity.AbstractParty) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.transactions.TransactionBuilder move(net.corda.core.contracts.StateAndRef, net.corda.core.identity.AbstractParty) + public String toString() + public void verify(net.corda.core.transactions.LedgerTransaction) + public static final net.corda.testing.contracts.DummyContract$Companion Companion + @org.jetbrains.annotations.NotNull public static final String PROGRAM_ID = "net.corda.testing.contracts.DummyContract" +## +public static interface net.corda.testing.contracts.DummyContract$Commands extends net.corda.core.contracts.CommandData +## +public static final class net.corda.testing.contracts.DummyContract$Commands$Create extends net.corda.core.contracts.TypeOnlyCommandData implements net.corda.testing.contracts.DummyContract$Commands + public () +## +public static final class net.corda.testing.contracts.DummyContract$Commands$Move extends net.corda.core.contracts.TypeOnlyCommandData implements net.corda.testing.contracts.DummyContract$Commands + public () +## +public static final class net.corda.testing.contracts.DummyContract$Companion extends java.lang.Object + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.TransactionBuilder move(List, net.corda.core.identity.AbstractParty) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.TransactionBuilder move(net.corda.core.contracts.StateAndRef, net.corda.core.identity.AbstractParty) +## +public static final class net.corda.testing.contracts.DummyContract$MultiOwnerState extends java.lang.Object implements net.corda.testing.contracts.DummyContract$State + public (int, List) + public final int component1() + @org.jetbrains.annotations.NotNull public final List component2() + @org.jetbrains.annotations.NotNull public final net.corda.testing.contracts.DummyContract$MultiOwnerState copy(int, List) + public boolean equals(Object) + public int getMagicNumber() + @org.jetbrains.annotations.NotNull public final List getOwners() + @org.jetbrains.annotations.NotNull public List getParticipants() + public int hashCode() + public String toString() +## +public static final class net.corda.testing.contracts.DummyContract$SingleOwnerState extends java.lang.Object implements net.corda.testing.contracts.DummyContract$State, net.corda.core.contracts.OwnableState + public (int, net.corda.core.identity.AbstractParty) + public final int component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AbstractParty component2() + @org.jetbrains.annotations.NotNull public final net.corda.testing.contracts.DummyContract$SingleOwnerState copy(int, net.corda.core.identity.AbstractParty) + public boolean equals(Object) + public int getMagicNumber() + @org.jetbrains.annotations.NotNull public net.corda.core.identity.AbstractParty getOwner() + @org.jetbrains.annotations.NotNull public List getParticipants() + public int hashCode() + public String toString() + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.CommandAndState withNewOwner(net.corda.core.identity.AbstractParty) +## +@net.corda.core.DoNotImplement public static interface net.corda.testing.contracts.DummyContract$State extends net.corda.core.contracts.ContractState + public abstract int getMagicNumber() +## +public final class net.corda.testing.contracts.DummyContractV2 extends java.lang.Object implements net.corda.core.contracts.UpgradedContract + public () + @org.jetbrains.annotations.NotNull public String getLegacyContract() + @org.jetbrains.annotations.NotNull public net.corda.testing.contracts.DummyContractV2$State upgrade(net.corda.testing.contracts.DummyContract$State) + public void verify(net.corda.core.transactions.LedgerTransaction) + public static final net.corda.testing.contracts.DummyContractV2$Companion Companion + @org.jetbrains.annotations.NotNull public static final String PROGRAM_ID = "net.corda.testing.contracts.DummyContractV2" +## +public static interface net.corda.testing.contracts.DummyContractV2$Commands extends net.corda.core.contracts.CommandData +## +public static final class net.corda.testing.contracts.DummyContractV2$Commands$Create extends net.corda.core.contracts.TypeOnlyCommandData implements net.corda.testing.contracts.DummyContractV2$Commands + public () +## +public static final class net.corda.testing.contracts.DummyContractV2$Commands$Move extends net.corda.core.contracts.TypeOnlyCommandData implements net.corda.testing.contracts.DummyContractV2$Commands + public () +## +public static final class net.corda.testing.contracts.DummyContractV2$Companion extends java.lang.Object +## +public static final class net.corda.testing.contracts.DummyContractV2$State extends java.lang.Object implements net.corda.core.contracts.ContractState + public (int, List) + public final int component1() + @org.jetbrains.annotations.NotNull public final List component2() + @org.jetbrains.annotations.NotNull public final net.corda.testing.contracts.DummyContractV2$State copy(int, List) + public boolean equals(Object) + public final int getMagicNumber() + @org.jetbrains.annotations.NotNull public final List getOwners() + @org.jetbrains.annotations.NotNull public List getParticipants() + public int hashCode() + public String toString() +## +public final class net.corda.testing.contracts.DummyState extends java.lang.Object implements net.corda.core.contracts.ContractState + public () + public (int) + public final int component1() + @org.jetbrains.annotations.NotNull public final net.corda.testing.contracts.DummyState copy(int) + public boolean equals(Object) + public final int getMagicNumber() + @org.jetbrains.annotations.NotNull public List getParticipants() + public int hashCode() + public String toString() +## +public final class net.corda.testing.core.DummyCommandData extends net.corda.core.contracts.TypeOnlyCommandData + public static final net.corda.testing.core.DummyCommandData INSTANCE +## +public final class net.corda.testing.core.Expect extends java.lang.Object + public (Class, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public final Class component1() + @org.jetbrains.annotations.NotNull public final kotlin.jvm.functions.Function1 component2() + @org.jetbrains.annotations.NotNull public final kotlin.jvm.functions.Function1 component3() + @org.jetbrains.annotations.NotNull public final net.corda.testing.core.Expect copy(Class, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final Class getClazz() + @org.jetbrains.annotations.NotNull public final kotlin.jvm.functions.Function1 getExpectClosure() + @org.jetbrains.annotations.NotNull public final kotlin.jvm.functions.Function1 getMatch() + public int hashCode() + public String toString() +## +@net.corda.core.DoNotImplement public abstract class net.corda.testing.core.ExpectCompose extends java.lang.Object +## +public static final class net.corda.testing.core.ExpectCompose$Parallel extends net.corda.testing.core.ExpectCompose + public (List) + @org.jetbrains.annotations.NotNull public final List getParallel() +## +public static final class net.corda.testing.core.ExpectCompose$Sequential extends net.corda.testing.core.ExpectCompose + public (List) + @org.jetbrains.annotations.NotNull public final List getSequence() +## +public static final class net.corda.testing.core.ExpectCompose$Single extends net.corda.testing.core.ExpectCompose + public (net.corda.testing.core.Expect) + @org.jetbrains.annotations.NotNull public final net.corda.testing.core.Expect getExpect() +## +public static final class net.corda.testing.core.ExpectComposeState$Companion extends java.lang.Object + @org.jetbrains.annotations.NotNull public final net.corda.testing.core.ExpectComposeState fromExpectCompose(net.corda.testing.core.ExpectCompose) +## +public static final class net.corda.testing.core.ExpectComposeState$Finished extends net.corda.testing.core.ExpectComposeState + public () + @org.jetbrains.annotations.NotNull public List getExpectedEvents() + @org.jetbrains.annotations.Nullable public Void nextState(Object) +## +public static final class net.corda.testing.core.ExpectComposeState$Parallel extends net.corda.testing.core.ExpectComposeState + public (net.corda.testing.core.ExpectCompose$Parallel, List) + @org.jetbrains.annotations.NotNull public List getExpectedEvents() + @org.jetbrains.annotations.NotNull public final net.corda.testing.core.ExpectCompose$Parallel getParallel() + @org.jetbrains.annotations.NotNull public final List getStates() + @org.jetbrains.annotations.Nullable public kotlin.Pair nextState(Object) +## +public static final class net.corda.testing.core.ExpectComposeState$Sequential extends net.corda.testing.core.ExpectComposeState + public (net.corda.testing.core.ExpectCompose$Sequential, int, net.corda.testing.core.ExpectComposeState) + @org.jetbrains.annotations.NotNull public List getExpectedEvents() + public final int getIndex() + @org.jetbrains.annotations.NotNull public final net.corda.testing.core.ExpectCompose$Sequential getSequential() + @org.jetbrains.annotations.NotNull public final net.corda.testing.core.ExpectComposeState getState() + @org.jetbrains.annotations.Nullable public kotlin.Pair nextState(Object) +## +public static final class net.corda.testing.core.ExpectComposeState$Single extends net.corda.testing.core.ExpectComposeState + public (net.corda.testing.core.ExpectCompose$Single) + @org.jetbrains.annotations.NotNull public List getExpectedEvents() + @org.jetbrains.annotations.NotNull public final net.corda.testing.core.ExpectCompose$Single getSingle() + @org.jetbrains.annotations.Nullable public kotlin.Pair nextState(Object) +## +public final class net.corda.testing.core.ExpectKt extends java.lang.Object + @org.jetbrains.annotations.NotNull public static final net.corda.testing.core.ExpectCompose expect(Class, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function1) + public static final void expectEvents(Iterable, boolean, kotlin.jvm.functions.Function0) + public static final void expectEvents(rx.Observable, boolean, kotlin.jvm.functions.Function0) + public static final void genericExpectEvents(Object, boolean, kotlin.jvm.functions.Function2, kotlin.jvm.functions.Function0) + @org.jetbrains.annotations.NotNull public static final net.corda.testing.core.ExpectCompose parallel(List) + @org.jetbrains.annotations.NotNull public static final net.corda.testing.core.ExpectCompose replicate(int, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public static final net.corda.testing.core.ExpectCompose sequence(List) +## +public static final class net.corda.testing.core.ExpectKt$expectEvents$1$lock$1 extends java.lang.Object +## +@net.corda.core.DoNotImplement public interface net.corda.testing.core.GlobalSerializationEnvironment extends net.corda.core.serialization.internal.SerializationEnvironment + public abstract void unset() +## +public final class net.corda.testing.core.SerializationEnvironmentRule extends java.lang.Object implements org.junit.rules.TestRule + public () + public (boolean) + @org.jetbrains.annotations.NotNull public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement, org.junit.runner.Description) + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getCheckpointContext() + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationFactory getSerializationFactory() + public static final net.corda.testing.core.SerializationEnvironmentRule$Companion Companion +## +public static final class net.corda.testing.core.SerializationEnvironmentRule$Companion extends java.lang.Object + public final Object run(String, kotlin.jvm.functions.Function1) +## +public static final class net.corda.testing.core.SerializationEnvironmentRule$apply$1 extends org.junit.runners.model.Statement + public void evaluate() +## +public final class net.corda.testing.core.SerializationTestHelpersKt extends java.lang.Object + @org.jetbrains.annotations.NotNull public static final net.corda.testing.core.GlobalSerializationEnvironment setGlobalSerialization(boolean) +## +public static final class net.corda.testing.core.SerializationTestHelpersKt$createTestSerializationEnv$1 extends net.corda.core.serialization.internal.SerializationEnvironmentImpl + @org.jetbrains.annotations.NotNull public String toString() +## +public static final class net.corda.testing.core.SerializationTestHelpersKt$setGlobalSerialization$1 extends java.lang.Object implements net.corda.testing.core.GlobalSerializationEnvironment, net.corda.core.serialization.internal.SerializationEnvironment + @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getCheckpointContext() + @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getP2pContext() + @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getRpcClientContext() + @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getRpcServerContext() + @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationFactory getSerializationFactory() + @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SerializationContext getStorageContext() + public void unset() +## +public final class net.corda.testing.core.TestConstants extends java.lang.Object + @org.jetbrains.annotations.NotNull public static final net.corda.nodeapi.internal.crypto.CertificateAndKeyPair getDEV_INTERMEDIATE_CA() + @org.jetbrains.annotations.NotNull public static final net.corda.nodeapi.internal.crypto.CertificateAndKeyPair getDEV_ROOT_CA() + @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name ALICE_NAME + @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name BOB_NAME + @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name BOC_NAME + @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name CHARLIE_NAME + @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name DUMMY_BANK_A_NAME + @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name DUMMY_BANK_B_NAME + @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name DUMMY_BANK_C_NAME + @kotlin.jvm.JvmField @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.CordaX500Name DUMMY_NOTARY_NAME + public static final int MAX_MESSAGE_SIZE = 10485760 + @kotlin.jvm.JvmField public static final java.time.Instant TEST_TX_TIME +## +public final class net.corda.testing.core.TestIdentity extends java.lang.Object + public (net.corda.core.identity.CordaX500Name) + public (net.corda.core.identity.CordaX500Name, long) + public (net.corda.core.identity.CordaX500Name, java.security.KeyPair) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.PartyAndCertificate getIdentity() + @org.jetbrains.annotations.NotNull public final java.security.KeyPair getKeyPair() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name getName() + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getParty() + @org.jetbrains.annotations.NotNull public final java.security.PublicKey getPublicKey() + public static final net.corda.testing.core.TestIdentity$Companion Companion +## +public static final class net.corda.testing.core.TestIdentity$Companion extends java.lang.Object + @org.jetbrains.annotations.NotNull public final net.corda.testing.core.TestIdentity fresh(String) +## +public final class net.corda.testing.core.TestUtils extends java.lang.Object + @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.Party chooseIdentity(net.corda.core.node.NodeInfo) + @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.PartyAndCertificate chooseIdentityAndCert(net.corda.core.node.NodeInfo) + @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.NetworkHostAndPort freeLocalHostAndPort() + public static final int freePort() + @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.StateRef generateStateRef() + @org.jetbrains.annotations.NotNull public static final List getFreeLocalPorts(String, int) + @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.PartyAndCertificate getTestPartyAndCertificate(net.corda.core.identity.CordaX500Name, java.security.PublicKey) + @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.PartyAndCertificate getTestPartyAndCertificate(net.corda.core.identity.Party) + @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.Party singleIdentity(net.corda.core.node.NodeInfo) + @org.jetbrains.annotations.NotNull public static final net.corda.core.identity.PartyAndCertificate singleIdentityAndCert(net.corda.core.node.NodeInfo) +## +public final class net.corda.testing.dsl.AttachmentResolutionException extends net.corda.core.flows.FlowException + public (net.corda.core.crypto.SecureHash) +## +public final class net.corda.testing.dsl.DoubleSpentInputs extends net.corda.core.flows.FlowException + public (List) +## +public final class net.corda.testing.dsl.DuplicateOutputLabel extends net.corda.core.flows.FlowException + public (String) +## +@net.corda.core.DoNotImplement public abstract class net.corda.testing.dsl.EnforceVerifyOrFail extends java.lang.Object +## +public static final class net.corda.testing.dsl.EnforceVerifyOrFail$Token extends net.corda.testing.dsl.EnforceVerifyOrFail + public static final net.corda.testing.dsl.EnforceVerifyOrFail$Token INSTANCE +## +public final class net.corda.testing.dsl.LedgerDSL extends java.lang.Object implements net.corda.testing.dsl.LedgerDSLInterpreter + public (net.corda.testing.dsl.LedgerDSLInterpreter, net.corda.core.identity.Party) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.WireTransaction _transaction(String, net.corda.core.transactions.TransactionBuilder, kotlin.jvm.functions.Function1) + public void _tweak(kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.WireTransaction _unverifiedTransaction(String, net.corda.core.transactions.TransactionBuilder, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash attachment(java.io.InputStream) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail fails() + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail fails with(String) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail failsWith(String) + @org.jetbrains.annotations.NotNull public final net.corda.testing.dsl.LedgerDSLInterpreter getInterpreter() + @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.ContractState retrieveOutput(Class, String) + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.StateAndRef retrieveOutputStateAndRef(Class, String) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction transaction(String, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction transaction(String, net.corda.core.transactions.TransactionBuilder, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction transaction(kotlin.jvm.functions.Function1) + public final void tweak(kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction unverifiedTransaction(String, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction unverifiedTransaction(String, net.corda.core.transactions.TransactionBuilder, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction unverifiedTransaction(kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail verifies() +## +@net.corda.core.DoNotImplement public interface net.corda.testing.dsl.LedgerDSLInterpreter extends net.corda.testing.dsl.OutputStateLookup, net.corda.testing.dsl.Verifies + @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.WireTransaction _transaction(String, net.corda.core.transactions.TransactionBuilder, kotlin.jvm.functions.Function1) + public abstract void _tweak(kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.WireTransaction _unverifiedTransaction(String, net.corda.core.transactions.TransactionBuilder, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash attachment(java.io.InputStream) +## +@net.corda.core.DoNotImplement public interface net.corda.testing.dsl.OutputStateLookup + @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.StateAndRef retrieveOutputStateAndRef(Class, String) +## +public final class net.corda.testing.dsl.TestLedgerDSLInterpreter extends java.lang.Object implements net.corda.testing.dsl.LedgerDSLInterpreter + public (net.corda.core.node.ServiceHub) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.WireTransaction _transaction(String, net.corda.core.transactions.TransactionBuilder, kotlin.jvm.functions.Function1) + public void _tweak(kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public net.corda.core.transactions.WireTransaction _unverifiedTransaction(String, net.corda.core.transactions.TransactionBuilder, kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash attachment(java.io.InputStream) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.ServiceHub component1() + @org.jetbrains.annotations.NotNull public final net.corda.testing.dsl.TestLedgerDSLInterpreter copy(net.corda.core.node.ServiceHub, HashMap, HashMap, HashMap) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail fails() + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail fails with(String) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail failsWith(String) + @org.jetbrains.annotations.NotNull public final net.corda.core.node.ServiceHub getServices() + @org.jetbrains.annotations.NotNull public final List getTransactionsToVerify() + @org.jetbrains.annotations.NotNull public final List getTransactionsUnverified() + @org.jetbrains.annotations.NotNull public final List getWireTransactions() + public int hashCode() + @org.jetbrains.annotations.Nullable public final String outputToLabel(net.corda.core.contracts.ContractState) + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.StateAndRef retrieveOutputStateAndRef(Class, String) + public String toString() + @org.jetbrains.annotations.Nullable public final String transactionName(net.corda.core.crypto.SecureHash) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail verifies() + public static final net.corda.testing.dsl.TestLedgerDSLInterpreter$Companion Companion +## +public static final class net.corda.testing.dsl.TestLedgerDSLInterpreter$Companion extends java.lang.Object +## +public static final class net.corda.testing.dsl.TestLedgerDSLInterpreter$TypeMismatch extends java.lang.Exception + public (Class, Class) +## +public static final class net.corda.testing.dsl.TestLedgerDSLInterpreter$VerifiesFailed extends java.lang.Exception + public (String, Throwable) +## +public static final class net.corda.testing.dsl.TestLedgerDSLInterpreter$WireTransactionWithLocation extends java.lang.Object + public (String, net.corda.core.transactions.WireTransaction, String) + @org.jetbrains.annotations.Nullable public final String component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction component2() + @org.jetbrains.annotations.Nullable public final String component3() + @org.jetbrains.annotations.NotNull public final net.corda.testing.dsl.TestLedgerDSLInterpreter$WireTransactionWithLocation copy(String, net.corda.core.transactions.WireTransaction, String) + public boolean equals(Object) + @org.jetbrains.annotations.Nullable public final String getLabel() + @org.jetbrains.annotations.Nullable public final String getLocation() + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction getTransaction() + public int hashCode() + public String toString() +## +public final class net.corda.testing.dsl.TestTransactionDSLInterpreter extends java.lang.Object implements net.corda.testing.dsl.TransactionDSLInterpreter, net.corda.testing.dsl.OutputStateLookup + public (net.corda.testing.dsl.TestLedgerDSLInterpreter, net.corda.core.transactions.TransactionBuilder) + public void _attachment(String) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail _tweak(kotlin.jvm.functions.Function1) + public void attachment(net.corda.core.crypto.SecureHash) + public void command(List, net.corda.core.contracts.CommandData) + @org.jetbrains.annotations.NotNull public final net.corda.testing.dsl.TestLedgerDSLInterpreter component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.TransactionBuilder component2() + @org.jetbrains.annotations.NotNull public final net.corda.testing.dsl.TestTransactionDSLInterpreter copy(net.corda.testing.dsl.TestLedgerDSLInterpreter, net.corda.core.transactions.TransactionBuilder, HashMap) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail fails() + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail fails with(String) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail failsWith(String) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.TestLedgerDSLInterpreter getLedgerInterpreter() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.ServicesForResolution getServices() + @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.TransactionBuilder getTransactionBuilder() + public int hashCode() + public void input(net.corda.core.contracts.StateRef) + public void output(String, String, net.corda.core.identity.Party, Integer, net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractState) + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.StateAndRef retrieveOutputStateAndRef(Class, String) + public void timeWindow(net.corda.core.contracts.TimeWindow) + public String toString() + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail verifies() +## +public static final class net.corda.testing.dsl.TestTransactionDSLInterpreter$services$1 extends java.lang.Object implements net.corda.core.node.ServicesForResolution + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.AttachmentStorage getAttachments() + @org.jetbrains.annotations.NotNull public net.corda.core.cordapp.CordappProvider getCordappProvider() + @org.jetbrains.annotations.NotNull public net.corda.core.node.services.IdentityService getIdentityService() + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef) + @org.jetbrains.annotations.NotNull public Set loadStates(Set) +## +public final class net.corda.testing.dsl.TransactionDSL extends java.lang.Object implements net.corda.testing.dsl.TransactionDSLInterpreter + public (net.corda.testing.dsl.TransactionDSLInterpreter, net.corda.core.identity.Party) + public void _attachment(String) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail _tweak(kotlin.jvm.functions.Function1) + public final void attachment(String) + public void attachment(net.corda.core.crypto.SecureHash) + public final void command(java.security.PublicKey, net.corda.core.contracts.CommandData) + public void command(List, net.corda.core.contracts.CommandData) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail fails() + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail fails with(String) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail failsWith(String) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.LedgerDSLInterpreter getLedgerInterpreter() + public final void input(String) + public final void input(String, net.corda.core.contracts.ContractState) + public void input(net.corda.core.contracts.StateRef) + public final void output(String, int, net.corda.core.contracts.ContractState) + public final void output(String, String, int, net.corda.core.contracts.ContractState) + public final void output(String, String, net.corda.core.contracts.ContractState) + public void output(String, String, net.corda.core.identity.Party, Integer, net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractState) + public final void output(String, String, net.corda.core.identity.Party, net.corda.core.contracts.ContractState) + public final void output(String, net.corda.core.contracts.ContractState) + public final void output(String, net.corda.core.identity.Party, net.corda.core.contracts.ContractState) + @org.jetbrains.annotations.NotNull public net.corda.core.contracts.StateAndRef retrieveOutputStateAndRef(Class, String) + public final void timeWindow(java.time.Instant) + public final void timeWindow(java.time.Instant, java.time.Duration) + public void timeWindow(net.corda.core.contracts.TimeWindow) + @org.jetbrains.annotations.NotNull public final net.corda.testing.dsl.EnforceVerifyOrFail tweak(kotlin.jvm.functions.Function1) + @org.jetbrains.annotations.NotNull public net.corda.testing.dsl.EnforceVerifyOrFail verifies() +## +@net.corda.core.DoNotImplement public interface net.corda.testing.dsl.TransactionDSLInterpreter extends net.corda.testing.dsl.OutputStateLookup, net.corda.testing.dsl.Verifies + public abstract void _attachment(String) + @org.jetbrains.annotations.NotNull public abstract net.corda.testing.dsl.EnforceVerifyOrFail _tweak(kotlin.jvm.functions.Function1) + public abstract void attachment(net.corda.core.crypto.SecureHash) + public abstract void command(List, net.corda.core.contracts.CommandData) + @org.jetbrains.annotations.NotNull public abstract net.corda.testing.dsl.LedgerDSLInterpreter getLedgerInterpreter() + public abstract void input(net.corda.core.contracts.StateRef) + public abstract void output(String, String, net.corda.core.identity.Party, Integer, net.corda.core.contracts.AttachmentConstraint, net.corda.core.contracts.ContractState) + public abstract void timeWindow(net.corda.core.contracts.TimeWindow) +## +@net.corda.core.DoNotImplement public interface net.corda.testing.dsl.Verifies + @org.jetbrains.annotations.NotNull public abstract net.corda.testing.dsl.EnforceVerifyOrFail fails() + @org.jetbrains.annotations.NotNull public abstract net.corda.testing.dsl.EnforceVerifyOrFail fails with(String) + @org.jetbrains.annotations.NotNull public abstract net.corda.testing.dsl.EnforceVerifyOrFail failsWith(String) + @org.jetbrains.annotations.NotNull public abstract net.corda.testing.dsl.EnforceVerifyOrFail verifies() +## +public final class net.corda.testing.http.HttpApi extends java.lang.Object + public (java.net.URL, com.fasterxml.jackson.databind.ObjectMapper) + @org.jetbrains.annotations.NotNull public final com.fasterxml.jackson.databind.ObjectMapper getMapper() + @org.jetbrains.annotations.NotNull public final java.net.URL getRoot() + public final void postJson(String, Object) + public final void postPlain(String, String) + public final void putJson(String, Object) + public static final net.corda.testing.http.HttpApi$Companion Companion +## +public static final class net.corda.testing.http.HttpApi$Companion extends java.lang.Object + @org.jetbrains.annotations.NotNull public final net.corda.testing.http.HttpApi fromHostAndPort(net.corda.core.utilities.NetworkHostAndPort, String, String, com.fasterxml.jackson.databind.ObjectMapper) +## +public final class net.corda.testing.http.HttpUtils extends java.lang.Object + @org.jetbrains.annotations.NotNull public final com.fasterxml.jackson.databind.ObjectMapper getDefaultMapper() + public final void postJson(java.net.URL, String) + public final void postPlain(java.net.URL, String) + public final void putJson(java.net.URL, String) + public static final net.corda.testing.http.HttpUtils INSTANCE +## +public final class net.corda.testing.services.FlowStackSnapshotFactoryImpl extends java.lang.Object implements net.corda.node.services.statemachine.FlowStackSnapshotFactory + public () + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public net.corda.core.flows.FlowStackSnapshot getFlowStackSnapshot(Class) + public void persistAsJsonFile(Class, java.nio.file.Path, net.corda.core.flows.StateMachineRunId) +## +public static final class net.corda.testing.services.FlowStackSnapshotFactoryImpl$ThreadLocalIndex$Companion extends java.lang.Object + @org.jetbrains.annotations.NotNull public final ThreadLocal getCurrentIndex() +## +public static final class net.corda.testing.services.FlowStackSnapshotFactoryImpl$ThreadLocalIndex$Companion$currentIndex$1 extends java.lang.ThreadLocal + @org.jetbrains.annotations.NotNull protected Integer initialValue() +## +public final class net.corda.testing.services.FlowStackSnapshotKt extends java.lang.Object +## +public final class net.corda.testing.services.MockAttachmentStorage extends net.corda.core.serialization.SingletonSerializeAsToken implements net.corda.core.node.services.AttachmentStorage + public () + @org.jetbrains.annotations.NotNull public final kotlin.Pair getAttachmentIdAndBytes(java.io.InputStream) + @org.jetbrains.annotations.NotNull public final HashMap getFiles() + public boolean hasAttachment(net.corda.core.crypto.SecureHash) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream, String, String) + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash importOrGetAttachment(java.io.InputStream) + @org.jetbrains.annotations.Nullable public net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash) + @org.jetbrains.annotations.NotNull public List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort) + public static final net.corda.testing.services.MockAttachmentStorage$Companion Companion +## +public static final class net.corda.testing.services.MockAttachmentStorage$Companion extends java.lang.Object + public final byte[] getBytes(java.io.InputStream) +## +public static final class net.corda.testing.services.MockAttachmentStorage$openAttachment$1 extends net.corda.core.internal.AbstractAttachment + @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() +## +public final class net.corda.testing.services.MockCordappProvider extends net.corda.node.internal.cordapp.CordappProviderImpl + public (net.corda.node.internal.cordapp.CordappLoader, net.corda.core.node.services.AttachmentStorage) + public final void addMockCordapp(String, net.corda.testing.services.MockAttachmentStorage) + @org.jetbrains.annotations.Nullable public net.corda.core.crypto.SecureHash getContractAttachmentID(String) + @org.jetbrains.annotations.NotNull public final List getCordappRegistry() +## From 4ac2c59623d340a2060a4454e2db56a4914c6c9c Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 1 Feb 2018 18:34:44 +0000 Subject: [PATCH 009/114] Adds links to referenced code files. --- docs/source/tutorial-building-transactions.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/source/tutorial-building-transactions.rst b/docs/source/tutorial-building-transactions.rst index d7011e57f4..9c38580a70 100644 --- a/docs/source/tutorial-building-transactions.rst +++ b/docs/source/tutorial-building-transactions.rst @@ -80,10 +80,14 @@ searching the vault via the ``VaultService`` interface on the To give a few more specific details consider two simplified real world scenarios. First, a basic foreign exchange cash transaction. This transaction needs to locate a set of funds to exchange. A flow -modelling this is implemented in ``FxTransactionBuildTutorial.kt``. +modelling this is implemented in ``FxTransactionBuildTutorial.kt`` +(see ``docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt`` in the +`main Corda repo `_). Second, a simple business model in which parties manually accept or reject each other's trade proposals, which is implemented in -``WorkflowTransactionBuildTutorial.kt``. To run and explore these +``WorkflowTransactionBuildTutorial.kt`` (see +``docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt`` in the +`main Corda repo `_). To run and explore these examples using the IntelliJ IDE one can run/step through the respective unit tests in ``FxTransactionBuildTutorialTest.kt`` and ``WorkflowTransactionBuildTutorialTest.kt``, which drive the flows as From f3d2a7674cc22476d162347d76cef3225afb3026 Mon Sep 17 00:00:00 2001 From: Tommy Lillehagen Date: Thu, 1 Feb 2018 16:06:12 +0000 Subject: [PATCH 010/114] Add module for end-to-end testing library --- .idea/compiler.xml | 5 +- experimental/behave/build.gradle | 107 ++++++++++++++++++ .../main/kotlin/net/corda/behave/Utility.kt | 7 ++ .../behave/src/scenario/kotlin/Scenarios.kt | 12 ++ .../corda/behave/scenarios/ScenarioHooks.kt | 17 +++ .../corda/behave/scenarios/ScenarioState.kt | 7 ++ .../net/corda/behave/scenarios/StepsBlock.kt | 3 + .../corda/behave/scenarios/StepsContainer.kt | 25 ++++ .../behave/scenarios/steps/DummySteps.kt | 18 +++ .../scenario/resources/features/dummy.feature | 6 + .../behave/src/scenario/resources/log4j2.xml | 14 +++ .../kotlin/net/corda/behave/UtilityTests.kt | 13 +++ settings.gradle | 1 + 13 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 experimental/behave/build.gradle create mode 100644 experimental/behave/src/main/kotlin/net/corda/behave/Utility.kt create mode 100644 experimental/behave/src/scenario/kotlin/Scenarios.kt create mode 100644 experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioHooks.kt create mode 100644 experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt create mode 100644 experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsBlock.kt create mode 100644 experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt create mode 100644 experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/DummySteps.kt create mode 100644 experimental/behave/src/scenario/resources/features/dummy.feature create mode 100644 experimental/behave/src/scenario/resources/log4j2.xml create mode 100644 experimental/behave/src/test/kotlin/net/corda/behave/UtilityTests.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml index a33ce1e0f0..4f9145d5f4 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -10,6 +10,9 @@ + + + @@ -159,4 +162,4 @@ - \ No newline at end of file + diff --git a/experimental/behave/build.gradle b/experimental/behave/build.gradle new file mode 100644 index 0000000000..1c420488ce --- /dev/null +++ b/experimental/behave/build.gradle @@ -0,0 +1,107 @@ +buildscript { + ext.kotlin_version = '1.2.21' + ext.commonsio_version = '2.6' + ext.cucumber_version = '1.2.5' + ext.crash_version = 'cce5a00f114343c1145c1d7756e1dd6df3ea984e' + ext.docker_client_version = '8.11.0' + + repositories { + maven { + jcenter() + url 'https://jitpack.io' + } + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +group 'net.corda.behave' + +apply plugin: 'java' +apply plugin: 'kotlin' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +sourceSets { + scenario { + java { + compileClasspath += main.output + runtimeClasspath += main.output + srcDir file('src/scenario/kotlin') + } + resources.srcDir file('src/scenario/resources') + } +} + +configurations { + scenarioCompile.extendsFrom testCompile + scenarioRuntime.extendsFrom testRuntime +} + +dependencies { + + // Library + + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + + compile("com.github.corda.crash:crash.shell:$crash_version") { + exclude group: "org.slf4j", module: "slf4j-jdk14" + exclude group: "org.bouncycastle" + } + + compile("com.github.corda.crash:crash.connectors.ssh:$crash_version") { + exclude group: "org.slf4j", module: "slf4j-jdk14" + exclude group: "org.bouncycastle" + } + + compile "com.spotify:docker-client:$docker_client_version" + + // Unit Tests + + testCompile "junit:junit:$junit_version" + + // Scenarios / End-to-End Tests + + scenarioCompile "info.cukes:cucumber-java8:$cucumber_version" + scenarioCompile "info.cukes:cucumber-junit:$cucumber_version" + scenarioCompile "info.cukes:cucumber-picocontainer:$cucumber_version" + scenarioCompile "org.assertj:assertj-core:$assertj_version" + scenarioCompile "org.slf4j:log4j-over-slf4j:$slf4j_version" + scenarioCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + scenarioCompile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" + scenarioCompile "org.apache.logging.log4j:log4j-core:$log4j_version" + scenarioCompile "commons-io:commons-io:$commonsio_version" + +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +compileScenarioKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +test { + testLogging.showStandardStreams = true +} + +task scenarios(type: Test) { + setTestClassesDirs sourceSets.scenario.output.getClassesDirs() + classpath = sourceSets.scenario.runtimeClasspath + outputs.upToDateWhen { false } +} + +scenarios.mustRunAfter test +scenarios.dependsOn test \ No newline at end of file diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/Utility.kt b/experimental/behave/src/main/kotlin/net/corda/behave/Utility.kt new file mode 100644 index 0000000000..b3dd28f73c --- /dev/null +++ b/experimental/behave/src/main/kotlin/net/corda/behave/Utility.kt @@ -0,0 +1,7 @@ +package net.corda.behave + +object Utility { + + fun dummy() = true + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/Scenarios.kt b/experimental/behave/src/scenario/kotlin/Scenarios.kt new file mode 100644 index 0000000000..b0c96a98ee --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/Scenarios.kt @@ -0,0 +1,12 @@ +import cucumber.api.CucumberOptions +import cucumber.api.junit.Cucumber +import org.junit.runner.RunWith + +@RunWith(Cucumber::class) +@CucumberOptions( + features = arrayOf("src/scenario/resources/features"), + glue = arrayOf("net.corda.behave.scenarios"), + plugin = arrayOf("pretty") +) +@Suppress("KDocMissingDocumentation") +class CucumberTest diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioHooks.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioHooks.kt new file mode 100644 index 0000000000..745ef851b6 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioHooks.kt @@ -0,0 +1,17 @@ +package net.corda.behave.scenarios + +import cucumber.api.java.After +import cucumber.api.java.Before + +@Suppress("KDocMissingDocumentation") +class ScenarioHooks(private val state: ScenarioState) { + + @Before + fun beforeScenario() { + } + + @After + fun afterScenario() { + } + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt new file mode 100644 index 0000000000..a21ab59a10 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt @@ -0,0 +1,7 @@ +package net.corda.behave.scenarios + +class ScenarioState { + + var count: Int = 0 + +} \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsBlock.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsBlock.kt new file mode 100644 index 0000000000..5880c939a3 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsBlock.kt @@ -0,0 +1,3 @@ +package net.corda.behave.scenarios + +typealias StepsBlock = (StepsContainer.() -> Unit) -> Unit \ No newline at end of file diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt new file mode 100644 index 0000000000..69ef293853 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt @@ -0,0 +1,25 @@ +package net.corda.behave.scenarios + +import cucumber.api.java8.En +import net.corda.behave.scenarios.steps.dummySteps +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +@Suppress("KDocMissingDocumentation") +class StepsContainer(val state: ScenarioState) : En { + + val log: Logger = LoggerFactory.getLogger(StepsContainer::class.java) + + private val stepDefinitions: List<(StepsBlock) -> Unit> = listOf( + ::dummySteps + ) + + init { + stepDefinitions.forEach { it({ this.steps(it) }) } + } + + private fun steps(action: (StepsContainer.() -> Unit)) { + action(this) + } + +} diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/DummySteps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/DummySteps.kt new file mode 100644 index 0000000000..ce86fa5186 --- /dev/null +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/DummySteps.kt @@ -0,0 +1,18 @@ +package net.corda.behave.scenarios.steps + +import net.corda.behave.scenarios.StepsBlock +import org.assertj.core.api.Assertions.assertThat + +fun dummySteps(steps: StepsBlock) = steps { + + When("^(\\d+) dumm(y|ies) exists?$") { count, _ -> + state.count = count + log.info("Checking pre-condition $count") + } + + Then("^there is a dummy$") { + assertThat(state.count).isGreaterThan(0) + log.info("Checking outcome ${state.count}") + } + +} diff --git a/experimental/behave/src/scenario/resources/features/dummy.feature b/experimental/behave/src/scenario/resources/features/dummy.feature new file mode 100644 index 0000000000..6ff1613bd5 --- /dev/null +++ b/experimental/behave/src/scenario/resources/features/dummy.feature @@ -0,0 +1,6 @@ +Feature: Dummy + Lorem ipsum + + Scenario: Noop + Given 15 dummies exist + Then there is a dummy \ No newline at end of file diff --git a/experimental/behave/src/scenario/resources/log4j2.xml b/experimental/behave/src/scenario/resources/log4j2.xml new file mode 100644 index 0000000000..43fcf63c3d --- /dev/null +++ b/experimental/behave/src/scenario/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/experimental/behave/src/test/kotlin/net/corda/behave/UtilityTests.kt b/experimental/behave/src/test/kotlin/net/corda/behave/UtilityTests.kt new file mode 100644 index 0000000000..c956cc67e1 --- /dev/null +++ b/experimental/behave/src/test/kotlin/net/corda/behave/UtilityTests.kt @@ -0,0 +1,13 @@ +package net.corda.behave + +import org.junit.Assert +import org.junit.Test + +class UtilityTests { + + @Test + fun `dummy`() { + Assert.assertEquals(true, Utility.dummy()) + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index a4f87dee28..4b0a92c815 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,6 +16,7 @@ include 'client:rpc' include 'webserver' include 'webserver:webcapsule' include 'experimental' +include 'experimental:behave' include 'experimental:sandbox' include 'experimental:quasar-hook' include 'experimental:kryo-hook' From 82ece34ac827dbee1479e3fc5c5f1ce378f049ac Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 2 Feb 2018 17:09:08 +0100 Subject: [PATCH 011/114] Tweak the way invocation context is integrated to reduce the pain for devs using the old API (#2447) Minor tweaks to the invocation context code. 1) Un-deprecate FlowInitiator, move the deprecation to the field. This eliminates large numbers of warnings and means developers are warned only once in the place where they obtain one. 2) Add documentation for StateMachineInfo and create a type alias to give it a better name in an ABI compatible way. 3) Improve markup on InvocationContext 4) Rename field from just "context" to "invocationContext" (Context is vague) --- .ci/api-current.txt | 40 ++++++------ .../corda/client/jfx/NodeMonitorModelTest.kt | 14 ++-- .../corda/client/rpc/CordaRPCClientTest.kt | 14 ++-- .../net/corda/core/context/AuthServiceId.kt | 9 --- .../corda/core/context/InvocationContext.kt | 64 +++++++++---------- .../net/corda/core/flows/FlowInitiator.kt | 42 +++++++++--- .../kotlin/net/corda/core/flows/FlowLogic.kt | 12 ++-- .../net/corda/core/messaging/CordaRPCOps.kt | 44 ++++++------- .../corda/node/internal/CordaRPCOpsImpl.kt | 12 ++-- .../services/events/NodeSchedulerService.kt | 4 +- .../node/shell/FlowWatchPrintingSubscriber.kt | 2 +- .../corda/node/internal/CordaServiceTest.kt | 4 +- .../services/events/ScheduledFlowTests.kt | 4 +- .../net/corda/testing/node/NodeTestUtils.kt | 4 +- 14 files changed, 139 insertions(+), 130 deletions(-) delete mode 100644 core/src/main/kotlin/net/corda/core/context/AuthServiceId.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 0fd2a15fc9..8d485ec906 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -96,21 +96,21 @@ public static final class net.corda.core.context.Actor$Companion extends java.la public String toString() ## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.context.InvocationContext extends java.lang.Object - public (net.corda.core.context.Origin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) - @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin component1() + public (net.corda.core.context.InvocationOrigin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationOrigin component1() @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace component2() @org.jetbrains.annotations.Nullable public final net.corda.core.context.Actor component3() @org.jetbrains.annotations.Nullable public final net.corda.core.context.Trace component4() @org.jetbrains.annotations.Nullable public final net.corda.core.context.Actor component5() - @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext copy(net.corda.core.context.Origin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext copy(net.corda.core.context.InvocationOrigin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) public boolean equals(Object) @org.jetbrains.annotations.Nullable public final net.corda.core.context.Actor getActor() @org.jetbrains.annotations.Nullable public final net.corda.core.context.Trace getExternalTrace() @org.jetbrains.annotations.Nullable public final net.corda.core.context.Actor getImpersonatedActor() - @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin getOrigin() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationOrigin getOrigin() @org.jetbrains.annotations.NotNull public final net.corda.core.context.Trace getTrace() public int hashCode() - @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext newInstance(net.corda.core.context.Origin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext newInstance(net.corda.core.context.InvocationOrigin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext peer(net.corda.core.identity.CordaX500Name, net.corda.core.context.Trace, net.corda.core.context.Trace, net.corda.core.context.Actor) @org.jetbrains.annotations.NotNull public final java.security.Principal principal() @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext rpc(net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Trace, net.corda.core.context.Actor) @@ -121,49 +121,49 @@ public static final class net.corda.core.context.Actor$Companion extends java.la public static final net.corda.core.context.InvocationContext$Companion Companion ## public static final class net.corda.core.context.InvocationContext$Companion extends java.lang.Object - @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext newInstance(net.corda.core.context.Origin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) + @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext newInstance(net.corda.core.context.InvocationOrigin, net.corda.core.context.Trace, net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Actor) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext peer(net.corda.core.identity.CordaX500Name, net.corda.core.context.Trace, net.corda.core.context.Trace, net.corda.core.context.Actor) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext rpc(net.corda.core.context.Actor, net.corda.core.context.Trace, net.corda.core.context.Trace, net.corda.core.context.Actor) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext scheduled(net.corda.core.contracts.ScheduledStateRef, net.corda.core.context.Trace, net.corda.core.context.Trace) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext service(String, net.corda.core.identity.CordaX500Name, net.corda.core.context.Trace, net.corda.core.context.Trace) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext shell(net.corda.core.context.Trace, net.corda.core.context.Trace) ## -@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.context.Origin extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.context.InvocationOrigin extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract java.security.Principal principal() ## -@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Origin$Peer extends net.corda.core.context.Origin +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.InvocationOrigin$Peer extends net.corda.core.context.InvocationOrigin public (net.corda.core.identity.CordaX500Name) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin$Peer copy(net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationOrigin$Peer copy(net.corda.core.identity.CordaX500Name) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name getParty() public int hashCode() @org.jetbrains.annotations.NotNull public java.security.Principal principal() public String toString() ## -@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Origin$RPC extends net.corda.core.context.Origin +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.InvocationOrigin$RPC extends net.corda.core.context.InvocationOrigin public (net.corda.core.context.Actor) - @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin$RPC copy(net.corda.core.context.Actor) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationOrigin$RPC copy(net.corda.core.context.Actor) public boolean equals(Object) public int hashCode() @org.jetbrains.annotations.NotNull public java.security.Principal principal() public String toString() ## -@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Origin$Scheduled extends net.corda.core.context.Origin +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.InvocationOrigin$Scheduled extends net.corda.core.context.InvocationOrigin public (net.corda.core.contracts.ScheduledStateRef) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.ScheduledStateRef component1() - @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin$Scheduled copy(net.corda.core.contracts.ScheduledStateRef) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationOrigin$Scheduled copy(net.corda.core.contracts.ScheduledStateRef) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.ScheduledStateRef getScheduledState() public int hashCode() @org.jetbrains.annotations.NotNull public java.security.Principal principal() public String toString() ## -@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Origin$Service extends net.corda.core.context.Origin +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.InvocationOrigin$Service extends net.corda.core.context.InvocationOrigin public (String, net.corda.core.identity.CordaX500Name) @org.jetbrains.annotations.NotNull public final String component1() @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name component2() - @org.jetbrains.annotations.NotNull public final net.corda.core.context.Origin$Service copy(String, net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationOrigin$Service copy(String, net.corda.core.identity.CordaX500Name) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.CordaX500Name getOwningLegalIdentity() @org.jetbrains.annotations.NotNull public final String getServiceClassName() @@ -171,9 +171,9 @@ public static final class net.corda.core.context.InvocationContext$Companion ext @org.jetbrains.annotations.NotNull public java.security.Principal principal() public String toString() ## -@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.Origin$Shell extends net.corda.core.context.Origin +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.context.InvocationOrigin$Shell extends net.corda.core.context.InvocationOrigin @org.jetbrains.annotations.NotNull public java.security.Principal principal() - public static final net.corda.core.context.Origin$Shell INSTANCE + public static final net.corda.core.context.InvocationOrigin$Shell INSTANCE ## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.context.Trace extends java.lang.Object public (net.corda.core.context.Trace$InvocationId, net.corda.core.context.Trace$SessionId) @@ -1168,6 +1168,7 @@ public static final class net.corda.core.flows.FinalityFlow$Companion extends ja public String toString() ## @net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.FlowInitiator extends java.lang.Object implements java.security.Principal + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext getInvocationContext() ## @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Peer extends net.corda.core.flows.FlowInitiator public (net.corda.core.identity.Party) @@ -1669,15 +1670,14 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public final String component2() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator component3() @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed component4() - @org.jetbrains.annotations.Nullable public final net.corda.core.context.InvocationContext component5() - @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext context() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext component5() @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.StateMachineInfo copy(net.corda.core.flows.StateMachineRunId, String, net.corda.core.flows.FlowInitiator, net.corda.core.messaging.DataFeed) @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.StateMachineInfo copy(net.corda.core.flows.StateMachineRunId, String, net.corda.core.flows.FlowInitiator, net.corda.core.messaging.DataFeed, net.corda.core.context.InvocationContext) public boolean equals(Object) - @org.jetbrains.annotations.Nullable public final net.corda.core.context.InvocationContext getContext() @org.jetbrains.annotations.NotNull public final String getFlowLogicClassName() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId getId() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator getInitiator() + @org.jetbrains.annotations.NotNull public final net.corda.core.context.InvocationContext getInvocationContext() @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed getProgressTrackerStepAndUpdates() public int hashCode() @org.jetbrains.annotations.NotNull public String toString() diff --git a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt index f8f5c58dcf..81006e9614 100644 --- a/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt +++ b/client/jfx/src/integration-test/kotlin/net/corda/client/jfx/NodeMonitorModelTest.kt @@ -2,7 +2,7 @@ package net.corda.client.jfx import net.corda.client.jfx.model.NodeMonitorModel import net.corda.client.jfx.model.ProgressTrackingEvent -import net.corda.core.context.Origin +import net.corda.core.context.InvocationOrigin import net.corda.core.contracts.Amount import net.corda.core.contracts.ContractState import net.corda.core.crypto.isFulfilledBy @@ -148,8 +148,8 @@ class NodeMonitorModelTest { // ISSUE expect { add: StateMachineUpdate.Added -> issueSmId = add.id - val context = add.stateMachineInfo.context() - require(context.origin is Origin.RPC && context.principal().name == "user1") + val context = add.stateMachineInfo.invocationContext + require(context.origin is InvocationOrigin.RPC && context.principal().name == "user1") }, expect { remove: StateMachineUpdate.Removed -> require(remove.id == issueSmId) @@ -157,8 +157,8 @@ class NodeMonitorModelTest { // MOVE - N.B. There are other framework flows that happen in parallel for the remote resolve transactions flow expect(match = { it.stateMachineInfo.flowLogicClassName == CashPaymentFlow::class.java.name }) { add: StateMachineUpdate.Added -> moveSmId = add.id - val context = add.stateMachineInfo.context() - require(context.origin is Origin.RPC && context.principal().name == "user1") + val context = add.stateMachineInfo.invocationContext + require(context.origin is InvocationOrigin.RPC && context.principal().name == "user1") }, expect(match = { it is StateMachineUpdate.Removed && it.id == moveSmId }) { } @@ -169,8 +169,8 @@ class NodeMonitorModelTest { sequence( // MOVE expect { add: StateMachineUpdate.Added -> - val context = add.stateMachineInfo.context() - require(context.origin is Origin.Peer && aliceNode.isLegalIdentity(aliceNode.identityFromX500Name((context.origin as Origin.Peer).party))) + val context = add.stateMachineInfo.invocationContext + require(context.origin is InvocationOrigin.Peer && aliceNode.isLegalIdentity(aliceNode.identityFromX500Name((context.origin as InvocationOrigin.Peer).party))) } ) } diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index bfb77b4aeb..b95480b95b 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -154,11 +154,11 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C }, expect { update: StateMachineUpdate.Added -> checkRpcNotification(update.stateMachineInfo, rpcUser.username, historicalIds, externalTrace, impersonatedActor) - sessionId = update.stateMachineInfo.context().trace.sessionId + sessionId = update.stateMachineInfo.invocationContext.trace.sessionId }, expect { update: StateMachineUpdate.Added -> checkRpcNotification(update.stateMachineInfo, rpcUser.username, historicalIds, externalTrace, impersonatedActor) - assertThat(update.stateMachineInfo.context().trace.sessionId).isEqualTo(sessionId) + assertThat(update.stateMachineInfo.invocationContext.trace.sessionId).isEqualTo(sessionId) } ) } @@ -166,15 +166,13 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C } private fun checkShellNotification(info: StateMachineInfo) { - - val context = info.context() - assertThat(context.origin).isInstanceOf(Origin.Shell::class.java) + val context = info.invocationContext + assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java) } private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, historicalIds: MutableSet, externalTrace: Trace?, impersonatedActor: Actor?) { - - val context = info.context() - assertThat(context.origin).isInstanceOf(Origin.RPC::class.java) + val context = info.invocationContext + assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java) assertThat(context.externalTrace).isEqualTo(externalTrace) assertThat(context.impersonatedActor).isEqualTo(impersonatedActor) assertThat(context.actor?.id?.value).isEqualTo(rpcUsername) diff --git a/core/src/main/kotlin/net/corda/core/context/AuthServiceId.kt b/core/src/main/kotlin/net/corda/core/context/AuthServiceId.kt deleted file mode 100644 index 9edc2680ca..0000000000 --- a/core/src/main/kotlin/net/corda/core/context/AuthServiceId.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.corda.core.context - -import net.corda.core.serialization.CordaSerializable - -/** - * Authentication / Authorisation Service ID. - */ -@CordaSerializable -data class AuthServiceId(val value: String) \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt b/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt index 9ca93033c0..58c1544c21 100644 --- a/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt +++ b/core/src/main/kotlin/net/corda/core/context/InvocationContext.kt @@ -9,52 +9,50 @@ import java.security.Principal * Models the information needed to trace an invocation in Corda. * Includes initiating actor, origin, trace information, and optional external trace information to correlate clients' IDs. * - * @param origin origin of the invocation. - * @param trace Corda invocation trace. - * @param actor acting agent of the invocation, used to derive the security principal. - * @param externalTrace optional external invocation trace for cross-system logs correlation. - * @param impersonatedActor optional impersonated actor, used for logging but not for authorisation. + * @property origin Origin of the invocation. + * @property trace Corda invocation trace. + * @property actor Acting agent of the invocation, used to derive the security principal. + * @property externalTrace Optional external invocation trace for cross-system logs correlation. + * @property impersonatedActor Optional impersonated actor, used for logging but not for authorisation. */ @CordaSerializable -data class InvocationContext(val origin: Origin, val trace: Trace, val actor: Actor?, val externalTrace: Trace? = null, val impersonatedActor: Actor? = null) { - +data class InvocationContext(val origin: InvocationOrigin, val trace: Trace, val actor: Actor?, val externalTrace: Trace? = null, val impersonatedActor: Actor? = null) { companion object { - /** * Creates an [InvocationContext] with a [Trace] that defaults to a [java.util.UUID] as value and [java.time.Instant.now] timestamp. */ @JvmStatic - fun newInstance(origin: Origin, trace: Trace = Trace.newInstance(), actor: Actor? = null, externalTrace: Trace? = null, impersonatedActor: Actor? = null) = InvocationContext(origin, trace, actor, externalTrace, impersonatedActor) + fun newInstance(origin: InvocationOrigin, trace: Trace = Trace.newInstance(), actor: Actor? = null, externalTrace: Trace? = null, impersonatedActor: Actor? = null) = InvocationContext(origin, trace, actor, externalTrace, impersonatedActor) /** - * Creates an [InvocationContext] with [Origin.RPC] origin. + * Creates an [InvocationContext] with [InvocationOrigin.RPC] origin. */ @JvmStatic - fun rpc(actor: Actor, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(Origin.RPC(actor), trace, actor, externalTrace, impersonatedActor) + fun rpc(actor: Actor, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(InvocationOrigin.RPC(actor), trace, actor, externalTrace, impersonatedActor) /** - * Creates an [InvocationContext] with [Origin.Peer] origin. + * Creates an [InvocationContext] with [InvocationOrigin.Peer] origin. */ @JvmStatic - fun peer(party: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(Origin.Peer(party), trace, null, externalTrace, impersonatedActor) + fun peer(party: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(InvocationOrigin.Peer(party), trace, null, externalTrace, impersonatedActor) /** - * Creates an [InvocationContext] with [Origin.Service] origin. + * Creates an [InvocationContext] with [InvocationOrigin.Service] origin. */ @JvmStatic - fun service(serviceClassName: String, owningLegalIdentity: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(Origin.Service(serviceClassName, owningLegalIdentity), trace, null, externalTrace) + fun service(serviceClassName: String, owningLegalIdentity: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(InvocationOrigin.Service(serviceClassName, owningLegalIdentity), trace, null, externalTrace) /** - * Creates an [InvocationContext] with [Origin.Scheduled] origin. + * Creates an [InvocationContext] with [InvocationOrigin.Scheduled] origin. */ @JvmStatic - fun scheduled(scheduledState: ScheduledStateRef, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(Origin.Scheduled(scheduledState), trace, null, externalTrace) + fun scheduled(scheduledState: ScheduledStateRef, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(InvocationOrigin.Scheduled(scheduledState), trace, null, externalTrace) /** - * Creates an [InvocationContext] with [Origin.Shell] origin. + * Creates an [InvocationContext] with [InvocationOrigin.Shell] origin. */ @JvmStatic - fun shell(trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = InvocationContext(Origin.Shell, trace, null, externalTrace) + fun shell(trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = InvocationContext(InvocationOrigin.Shell, trace, null, externalTrace) } /** @@ -83,11 +81,10 @@ data class Actor(val id: Id, val serviceId: AuthServiceId, val owningLegalIdenti } /** - * Invocation origin for tracing purposes. + * Represents the source of an action such as a flow start, an RPC, a shell command etc. */ @CordaSerializable -sealed class Origin { - +sealed class InvocationOrigin { /** * Returns the [Principal] for a given [Actor]. */ @@ -96,32 +93,28 @@ sealed class Origin { /** * Origin was an RPC call. */ - data class RPC(private val actor: Actor) : Origin() { - + data class RPC(private val actor: Actor) : InvocationOrigin() { override fun principal() = Principal { actor.id.value } } /** * Origin was a message sent by a [Peer]. */ - data class Peer(val party: CordaX500Name) : Origin() { - + data class Peer(val party: CordaX500Name) : InvocationOrigin() { override fun principal() = Principal { party.toString() } } /** * Origin was a Corda Service. */ - data class Service(val serviceClassName: String, val owningLegalIdentity: CordaX500Name) : Origin() { - + data class Service(val serviceClassName: String, val owningLegalIdentity: CordaX500Name) : InvocationOrigin() { override fun principal() = Principal { serviceClassName } } /** * Origin was a scheduled activity. */ - data class Scheduled(val scheduledState: ScheduledStateRef) : Origin() { - + data class Scheduled(val scheduledState: ScheduledStateRef) : InvocationOrigin() { override fun principal() = Principal { "Scheduler" } } @@ -129,8 +122,13 @@ sealed class Origin { /** * Origin was the Shell. */ - object Shell : Origin() { - + object Shell : InvocationOrigin() { override fun principal() = Principal { "Shell User" } } -} \ No newline at end of file +} + +/** + * Authentication / Authorisation Service ID. + */ +@CordaSerializable +data class AuthServiceId(val value: String) \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowInitiator.kt b/core/src/main/kotlin/net/corda/core/flows/FlowInitiator.kt index 8623169da8..206862d2ec 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowInitiator.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowInitiator.kt @@ -1,45 +1,69 @@ package net.corda.core.flows +import net.corda.core.context.Actor +import net.corda.core.context.AuthServiceId +import net.corda.core.context.InvocationContext +import net.corda.core.context.InvocationOrigin import net.corda.core.contracts.ScheduledStateRef +import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import java.security.Principal /** - * FlowInitiator holds information on who started the flow. We have different ways of doing that: via RPC [FlowInitiator.RPC], - * communication started by peer node [FlowInitiator.Peer], scheduled flows [FlowInitiator.Scheduled] - * or via the Corda Shell [FlowInitiator.Shell]. + * Please note that [FlowInitiator] has been superceded by [net.corda.core.context.InvocationContext], which offers + * more detail for the same event. + * + * FlowInitiator holds information on who started the flow. We have different ways of doing that: via [FlowInitiator.RPC], + * communication started by peer nodes ([FlowInitiator.Peer]), scheduled flows ([FlowInitiator.Scheduled]) + * or via the Corda Shell ([FlowInitiator.Shell]). */ -@Deprecated("Do not use these types. Future releases might remove them.") @CordaSerializable sealed class FlowInitiator : Principal { /** Started using [net.corda.core.messaging.CordaRPCOps.startFlowDynamic]. */ - @Deprecated("Do not use this type. Future releases might remove it.") data class RPC(val username: String) : FlowInitiator() { override fun getName(): String = username } /** Started when we get new session initiation request. */ - @Deprecated("Do not use this type. Future releases might remove it.") data class Peer(val party: Party) : FlowInitiator() { override fun getName(): String = party.name.toString() } /** Started by a CordaService. */ - @Deprecated("Do not use this type. Future releases might remove it.") data class Service(val serviceClassName: String) : FlowInitiator() { override fun getName(): String = serviceClassName } /** Started as scheduled activity. */ - @Deprecated("Do not use this type. Future releases might remove it.") data class Scheduled(val scheduledState: ScheduledStateRef) : FlowInitiator() { override fun getName(): String = "Scheduler" } // TODO When proper ssh access enabled, add username/use RPC? - @Deprecated("Do not use this type. Future releases might remove it.") object Shell : FlowInitiator() { override fun getName(): String = "Shell User" } + + /** + * Returns an [InvocationContext], which is equivalent to this object but expressed using the successor to this + * class hierarchy (which is now deprecated). The returned object has less information than it could have, so + * prefer to use fetch an invocation context directly if you can (e.g. in [net.corda.core.messaging.StateMachineInfo]) + */ + val invocationContext: InvocationContext get() { + val unknownName = CordaX500Name("UNKNOWN", "UNKNOWN", "GB") + var actor: Actor? = null + val origin: InvocationOrigin + when (this) { + is FlowInitiator.RPC -> { + actor = Actor(Actor.Id(this.username), AuthServiceId("UNKNOWN"), unknownName) + origin = InvocationOrigin.RPC(actor) + } + is FlowInitiator.Peer -> origin = InvocationOrigin.Peer(this.party.name) + is FlowInitiator.Service -> origin = InvocationOrigin.Service(this.serviceClassName, unknownName) + FlowInitiator.Shell -> origin = InvocationOrigin.Shell + is FlowInitiator.Scheduled -> origin = InvocationOrigin.Scheduled(this.scheduledState) + } + return InvocationContext.newInstance(origin = origin, actor = actor) + } } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index a10a3d3fe2..476521f0ec 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -42,7 +42,7 @@ import java.time.Instant * also has a version property to allow you to version your flow and enables a node to restrict support for the flow to * that particular version. * - * Functions that suspend the flow (including all functions on [FlowSession]) accept a [maySkipCheckpoint] parameter + * Functions that suspend the flow (including all functions on [FlowSession]) accept a maySkipCheckpoint parameter * defaulting to false, false meaning a checkpoint should always be created on suspend. This parameter may be set to * true which allows the implementation to potentially optimise away the checkpoint, saving a roundtrip to the database. * @@ -52,6 +52,7 @@ import java.time.Instant * parameter the flow must be prepared for scenarios where a previous running of the flow *already committed its * relevant database transactions*. Only set this option to true if you know what you're doing. */ +@Suppress("DEPRECATION", "DeprecatedCallableAddReplaceWith") abstract class FlowLogic { /** This is where you should log things to. */ val logger: Logger get() = stateMachine.logger @@ -60,14 +61,14 @@ abstract class FlowLogic { /** * Return the outermost [FlowLogic] instance, or null if not in a flow. */ - @JvmStatic + @Suppress("unused") @JvmStatic val currentTopLevel: FlowLogic<*>? get() = (Strand.currentStrand() as? FlowStateMachine<*>)?.logic /** * If on a flow, suspends the flow and only wakes it up after at least [duration] time has passed. Otherwise, * just sleep for [duration]. This sleep function is not designed to aid scheduling, for which you should - * consider using [SchedulableState]. It is designed to aid with managing contention for which you have not - * managed via another means. + * consider using [net.corda.core.contracts.SchedulableState]. It is designed to aid with managing contention + * for which you have not managed via another means. * * Warning: long sleeps and in general long running flows are highly discouraged, as there is currently no * support for flow migration! This method will throw an exception if you attempt to sleep for longer than @@ -77,7 +78,7 @@ abstract class FlowLogic { @JvmStatic @Throws(FlowException::class) fun sleep(duration: Duration) { - if (duration.compareTo(Duration.ofMinutes(5)) > 0) { + if (duration > Duration.ofMinutes(5)) { throw FlowException("Attempt to sleep for longer than 5 minutes is not supported. Consider using SchedulableState.") } (Strand.currentStrand() as? FlowStateMachine<*>)?.sleepUntil(Instant.now() + duration) ?: Strand.sleep(duration.toMillis()) @@ -425,6 +426,7 @@ abstract class FlowLogic { // This is the flow used for managing sessions. It defaults to the current flow but if this is an inlined sub-flow // then it will point to the flow it's been inlined to. + @Suppress("LeakingThis") private var flowUsedForSessions: FlowLogic<*> = this private fun maybeWireUpProgressTracking(subLogic: FlowLogic<*>) { diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index b6880a8103..5d56f9d638 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -4,7 +4,7 @@ import net.corda.core.concurrent.CordaFuture import net.corda.core.context.Actor import net.corda.core.context.AuthServiceId import net.corda.core.context.InvocationContext -import net.corda.core.context.Origin +import net.corda.core.context.InvocationOrigin import net.corda.core.contracts.ContractState import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowInitiator @@ -27,44 +27,40 @@ import java.io.InputStream import java.security.PublicKey import java.time.Instant -private val unknownName = CordaX500Name("UNKNOWN", "UNKNOWN", "GB") - +/** + * Represents information about a flow (the name "state machine" is legacy, Kotlin users can use the [FlowInfo] type + * alias). You can access progress tracking, information about why the flow was started and so on. + */ @CordaSerializable data class StateMachineInfo @JvmOverloads constructor( + /** A univerally unique ID ([java.util.UUID]) representing this particular instance of the named flow. */ val id: StateMachineRunId, + /** The JVM class name of the flow code. */ val flowLogicClassName: String, - val initiator: FlowInitiator, + /** + * An object representing information about the initiator of the flow. Note that this field is + * superceded by the [invocationContext] property, which has more detail. + */ + @Deprecated("There is more info available using 'context'") val initiator: FlowInitiator, + /** A [DataFeed] of the current progress step as a human readable string, and updates to that string. */ val progressTrackerStepAndUpdates: DataFeed?, - val context: InvocationContext? = null + /** An [InvocationContext] describing why and by whom the flow was started. */ + val invocationContext: InvocationContext = initiator.invocationContext ) { - fun context(): InvocationContext = context ?: contextFrom(initiator) - - private fun contextFrom(initiator: FlowInitiator): InvocationContext { - var actor: Actor? = null - val origin: Origin - when (initiator) { - is FlowInitiator.RPC -> { - actor = Actor(Actor.Id(initiator.username), AuthServiceId("UNKNOWN"), unknownName) - origin = Origin.RPC(actor) - } - is FlowInitiator.Peer -> origin = Origin.Peer(initiator.party.name) - is FlowInitiator.Service -> origin = Origin.Service(initiator.serviceClassName, unknownName) - is FlowInitiator.Shell -> origin = Origin.Shell - is FlowInitiator.Scheduled -> origin = Origin.Scheduled(initiator.scheduledState) - } - return InvocationContext.newInstance(origin = origin, actor = actor) - } - + @Suppress("DEPRECATION") fun copy(id: StateMachineRunId = this.id, flowLogicClassName: String = this.flowLogicClassName, initiator: FlowInitiator = this.initiator, progressTrackerStepAndUpdates: DataFeed? = this.progressTrackerStepAndUpdates): StateMachineInfo { - return copy(id = id, flowLogicClassName = flowLogicClassName, initiator = initiator, progressTrackerStepAndUpdates = progressTrackerStepAndUpdates, context = context) + return copy(id = id, flowLogicClassName = flowLogicClassName, initiator = initiator, progressTrackerStepAndUpdates = progressTrackerStepAndUpdates, invocationContext = invocationContext) } override fun toString(): String = "${javaClass.simpleName}($id, $flowLogicClassName)" } +/** An alias for [StateMachineInfo] which uses more modern terminology. */ +typealias FlowInfo = StateMachineInfo + @CordaSerializable sealed class StateMachineUpdate { abstract val id: StateMachineRunId diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index b3f7ebbec3..7b8d3b5754 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -3,7 +3,7 @@ package net.corda.node.internal import net.corda.client.rpc.notUsed import net.corda.core.concurrent.CordaFuture import net.corda.core.context.InvocationContext -import net.corda.core.context.Origin +import net.corda.core.context.InvocationOrigin import net.corda.core.contracts.ContractState import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowInitiator @@ -285,11 +285,11 @@ internal class CordaRPCOpsImpl( val principal = origin.principal().name return when (origin) { - is Origin.RPC -> FlowInitiator.RPC(principal) - is Origin.Peer -> services.identityService.wellKnownPartyFromX500Name((origin as Origin.Peer).party)?.let { FlowInitiator.Peer(it) } ?: throw IllegalStateException("Unknown peer with name ${(origin as Origin.Peer).party}.") - is Origin.Service -> FlowInitiator.Service(principal) - is Origin.Shell -> FlowInitiator.Shell - is Origin.Scheduled -> FlowInitiator.Scheduled((origin as Origin.Scheduled).scheduledState) + is InvocationOrigin.RPC -> FlowInitiator.RPC(principal) + is InvocationOrigin.Peer -> services.identityService.wellKnownPartyFromX500Name((origin as InvocationOrigin.Peer).party)?.let { FlowInitiator.Peer(it) } ?: throw IllegalStateException("Unknown peer with name ${(origin as InvocationOrigin.Peer).party}.") + is InvocationOrigin.Service -> FlowInitiator.Service(principal) + is InvocationOrigin.Shell -> FlowInitiator.Shell + is InvocationOrigin.Scheduled -> FlowInitiator.Scheduled((origin as InvocationOrigin.Scheduled).scheduledState) } } diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index 72ef0fe6a6..fab75d470c 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -3,7 +3,7 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable import com.google.common.util.concurrent.ListenableFuture import net.corda.core.context.InvocationContext -import net.corda.core.context.Origin +import net.corda.core.context.InvocationOrigin import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledActivity import net.corda.core.contracts.ScheduledStateRef @@ -252,7 +252,7 @@ class NodeSchedulerService(private val clock: CordaClock, if (scheduledFlow != null) { flowName = scheduledFlow.javaClass.name // TODO refactor the scheduler to store and propagate the original invocation context - val context = InvocationContext.newInstance(Origin.Scheduled(scheduledState)) + val context = InvocationContext.newInstance(InvocationOrigin.Scheduled(scheduledState)) val future = flowStarter.startFlow(scheduledFlow, context).flatMap { it.resultFuture } future.then { unfinishedSchedules.countDown() diff --git a/node/src/main/kotlin/net/corda/node/shell/FlowWatchPrintingSubscriber.kt b/node/src/main/kotlin/net/corda/node/shell/FlowWatchPrintingSubscriber.kt index 7f52b430f2..cb48f1b829 100644 --- a/node/src/main/kotlin/net/corda/node/shell/FlowWatchPrintingSubscriber.kt +++ b/node/src/main/kotlin/net/corda/node/shell/FlowWatchPrintingSubscriber.kt @@ -72,7 +72,7 @@ class FlowWatchPrintingSubscriber(private val toStream: RenderPrintWriter) : Sub table.add(RowElement().add( LabelElement(formatFlowId(smmUpdate.id)), LabelElement(formatFlowName(smmUpdate.stateMachineInfo.flowLogicClassName)), - LabelElement(formatInvocationContext(smmUpdate.stateMachineInfo.context())), + LabelElement(formatInvocationContext(smmUpdate.stateMachineInfo.invocationContext)), LabelElement("In progress") ).style(stateColor(smmUpdate).fg())) indexMap[smmUpdate.id] = table.rows.size - 1 diff --git a/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt b/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt index b7b47d9165..2842e9bab3 100644 --- a/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt @@ -4,7 +4,7 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByService import net.corda.core.context.InvocationContext -import net.corda.core.context.Origin +import net.corda.core.context.InvocationOrigin import net.corda.core.node.AppServiceHub import net.corda.core.node.ServiceHub import net.corda.core.node.services.CordaService @@ -45,7 +45,7 @@ class TestCordaService(val appServiceHub: AppServiceHub): SingletonSerializeAsTo fun startServiceFlow() { val handle = appServiceHub.startFlow(DummyServiceFlow()) val context = handle.returnValue.get() - assertEquals(this.javaClass.name, (context.origin as Origin.Service).serviceClassName) + assertEquals(this.javaClass.name, (context.origin as InvocationOrigin.Service).serviceClassName) } fun startServiceFlowAndTrack() { diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index 4ff69ccbb2..f40782a3e7 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -2,7 +2,7 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable import net.corda.core.concurrent.CordaFuture -import net.corda.core.context.Origin +import net.corda.core.context.InvocationOrigin import net.corda.core.contracts.* import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowLogic @@ -121,7 +121,7 @@ class ScheduledFlowTests { aliceNode.smm.track().updates.subscribe { if (it is StateMachineManager.Change.Add) { val context = it.logic.stateMachine.context - if (context.origin is Origin.Scheduled) + if (context.origin is InvocationOrigin.Scheduled) countScheduledFlows++ } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt index 0ce2359249..85248cab2d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt @@ -5,7 +5,7 @@ package net.corda.testing.node import net.corda.core.context.Actor import net.corda.core.context.AuthServiceId import net.corda.core.context.InvocationContext -import net.corda.core.context.Origin +import net.corda.core.context.InvocationOrigin import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -62,7 +62,7 @@ fun testContext(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company /** * Starts an already constructed flow. Note that you must be on the server thread to call this method. [InvocationContext] - * has origin [Origin.RPC] and actor with id "Only For Testing". + * has origin [InvocationOrigin.RPC] and actor with id "Only For Testing". */ fun StartedNodeServices.startFlow(logic: FlowLogic): FlowStateMachine = startFlow(logic, newContext()).getOrThrow() From 38ccd0572c3ff186a039631b84df54dcf2e54e6d Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 2 Feb 2018 17:17:23 +0100 Subject: [PATCH 012/114] Use Java reflection for determining if a type is a Kotlin singleton. (#2446) Kotlin's own reflection has a habit of throwing weird errors and does not work for private objects - thus the unit test which tests this feature has actually never worked properly, but somehow works by accident. An attempt to upgrade to the latest Kotlin revealed the issue so it must have always been unstable. --- .../serialization/CordaClassResolver.kt | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt index c41d3fe3ef..682b2f85b3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolver.kt @@ -6,14 +6,15 @@ import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.serializers.FieldSerializer import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.Util -import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationContext import net.corda.core.utilities.contextLogger +import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.nodeapi.internal.serialization.amqp.hasAnnotationInHierarchy import net.corda.nodeapi.internal.serialization.kryo.ThrowableSerializer import java.io.PrintWriter +import java.lang.reflect.Modifier import java.lang.reflect.Modifier.isAbstract import java.nio.charset.StandardCharsets import java.nio.file.Files @@ -74,10 +75,21 @@ class CordaClassResolver(serializationContext: SerializationContext) : DefaultCl override fun registerImplicit(type: Class<*>): Registration { val targetType = typeForSerializationOf(type) + // Is this a Kotlin object? We use our own reflection here rather than .kotlin.objectInstance because Kotlin + // reflection won't work for private objects, and can throw exceptions in other circumstances as well. val objectInstance = try { - targetType.kotlin.objectInstance + targetType.declaredFields.singleOrNull { + it.name == "INSTANCE" && + it.type == type && + Modifier.isStatic(it.modifiers) && + Modifier.isFinal(it.modifiers) && + Modifier.isPublic(it.modifiers) + }?.let { + it.isAccessible = true + type.cast(it.get(null)!!) + } } catch (t: Throwable) { - null // objectInstance will throw if the type is something like a lambda + null } // We have to set reference to true, since the flag influences how String fields are treated and we want it to be consistent. From 57ba9cdf06f1ef13afbf42ac7a0e3fa6a306f681 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 2 Feb 2018 16:58:43 +0000 Subject: [PATCH 013/114] CORDA-915 - Replace BEANS introspector with standard reflection (#2400) * CORDA-915 - Replace BEANS introspector with standard reflection Removes lib dependency and puts something in place we can better control * CORDA-915 - Review comment corrections * Review Comments --- .../serialization/amqp/SerializationHelper.kt | 239 +++++++++++++----- .../amqp/JavaPrivatePropertyTests.java | 109 ++++++++ .../serialization/amqp/GenericsTests.kt | 25 ++ .../amqp/PrivatePropertyTests.kt | 67 ++++- 4 files changed, 374 insertions(+), 66 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 9dd0fb30cf..b65ec08f58 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -6,11 +6,9 @@ import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializationContext import org.apache.qpid.proton.codec.Data -import java.beans.IndexedPropertyDescriptor -import java.beans.Introspector -import java.beans.PropertyDescriptor import java.io.NotSerializableException import java.lang.reflect.* +import java.lang.reflect.Field import java.util.* import kotlin.reflect.KClass import kotlin.reflect.KFunction @@ -72,62 +70,167 @@ internal fun constructorForDeserialization(type: Type): KFunction? { internal fun propertiesForSerialization( kotlinConstructor: KFunction?, type: Type, - factory: SerializerFactory) = PropertySerializers.make ( - if (kotlinConstructor != null) { - propertiesForSerializationFromConstructor(kotlinConstructor, type, factory) - } else { - propertiesForSerializationFromAbstract(type.asClass()!!, type, factory) - }.sortedWith(PropertyAccessor)) + factory: SerializerFactory) = PropertySerializers.make( + if (kotlinConstructor != null) { + propertiesForSerializationFromConstructor(kotlinConstructor, type, factory) + } else { + propertiesForSerializationFromAbstract(type.asClass()!!, type, factory) + }.sortedWith(PropertyAccessor)) + fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers)) +/** + * Encapsulates the property of a class and its potential getter and setter methods. + * + * @property field a property of a class. + * @property setter the method of a class that sets the field. Determined by locating + * a function called setXyz on the class for the property named in field as xyz. + * @property getter the method of a class that returns a fields value. Determined by + * locating a function named getXyz for the property named in field as xyz. + */ +data class PropertyDescriptor(var field: Field?, var setter: Method?, var getter: Method?) { + override fun toString() = StringBuilder("").apply { + appendln("Property - ${field?.name ?: "null field"}\n") + appendln(" getter - ${getter?.name ?: "no getter"}") + append(" setter - ${setter?.name ?: "no setter"}") + }.toString() +} + +object PropertyDescriptorsRegex { + // match an uppercase letter that also has a corresponding lower case equivalent + val re = Regex("(?get|set|is)(?\\p{Lu}.*)") +} + +/** + * Collate the properties of a class and match them with their getter and setter + * methods as per a JavaBean. + * + * for a property + * exampleProperty + * + * We look for methods + * setExampleProperty + * getExampleProperty + * isExampleProperty + * + * Where setExampleProperty must return a type compatible with exampleProperty, getExampleProperty must + * take a single parameter of a type compatible with exampleProperty and isExampleProperty must + * return a boolean + */ +fun Class.propertyDescriptors(): Map { + val classProperties = mutableMapOf() + + var clazz: Class? = this + do { + // get the properties declared on this instance of class + clazz!!.declaredFields.forEach { classProperties.put(it.name, PropertyDescriptor(it, null, null)) } + + // then pair them up with the declared getter and setter + // Note: It is possible for a class to have multipleinstancess of a function where the types + // differ. For example: + // interface I { val a: T } + // class D(override val a: String) : I + // instances of D will have both + // getA - returning a String (java.lang.String) and + // getA - returning an Object (java.lang.Object) + // In this instance we take the most derived object + clazz.declaredMethods?.map { + PropertyDescriptorsRegex.re.find(it.name)?.apply { + try { + classProperties.getValue(groups[2]!!.value.decapitalize()).apply { + when (groups[1]!!.value) { + "set" -> { + if (setter == null) setter = it + else if (TypeToken.of(setter!!.genericReturnType).isSupertypeOf(it.genericReturnType)) { + setter = it + } + } + "get" -> { + if (getter == null) getter = it + else if (TypeToken.of(getter!!.genericReturnType).isSupertypeOf(it.genericReturnType)) { + getter = it + } + } + "is" -> { + val rtnType = TypeToken.of(it.genericReturnType) + if ((rtnType == TypeToken.of(Boolean::class.java)) + || (rtnType == TypeToken.of(Boolean::class.javaObjectType))) { + if (getter == null) getter = it + } + } + } + } + } catch (e: NoSuchElementException) { + // handles the getClass case from java.lang.Object + return@apply + } + } + } + clazz = clazz?.superclass + } while (clazz != null) + + return classProperties +} + +/** + * From a constructor, determine which properties of a class are to be serialized. + * + * @param kotlinConstructor The constructor to be used to instantiate instances of the class + * @param type The class's [Type] + * @param factory The factory generating the serializer wrapping this function. + */ internal fun propertiesForSerializationFromConstructor( kotlinConstructor: KFunction, type: Type, factory: SerializerFactory): List { val clazz = (kotlinConstructor.returnType.classifier as KClass<*>).javaObjectType - // Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans. - val properties = Introspector.getBeanInfo(clazz).propertyDescriptors - .filter { it.name != "class" } - .groupBy { it.name } - .mapValues { it.value[0] } - if (properties.isNotEmpty() && kotlinConstructor.parameters.isEmpty()) { - return propertiesForSerializationFromSetters(properties, type, factory) + val classProperties = clazz.propertyDescriptors() + + if (classProperties.isNotEmpty() && kotlinConstructor.parameters.isEmpty()) { + return propertiesForSerializationFromSetters(classProperties, type, factory) } return mutableListOf().apply { kotlinConstructor.parameters.withIndex().forEach { param -> - val name = param.value.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.") + val name = param.value.name ?: throw NotSerializableException( + "Constructor parameter of $clazz has no name.") - val propertyReader = if (name in properties) { - // it's a publicly accessible property - val matchingProperty = properties[name]!! + val propertyReader = if (name in classProperties) { + if (classProperties[name]!!.getter != null) { + // it's a publicly accessible property + val matchingProperty = classProperties[name]!! - // Check that the method has a getter in java. - val getter = matchingProperty.readMethod ?: throw NotSerializableException( - "Property has no getter method for $name of $clazz. If using Java and the parameter name" - + "looks anonymous, check that you have the -parameters option specified in the Java compiler." - + "Alternately, provide a proxy serializer (SerializationCustomSerializer) if " - + "recompiling isn't an option.") + // Check that the method has a getter in java. + val getter = matchingProperty.getter ?: throw NotSerializableException( + "Property has no getter method for $name of $clazz. If using Java and the parameter name" + + "looks anonymous, check that you have the -parameters option specified in the " + + "Java compiler. Alternately, provide a proxy serializer " + + "(SerializationCustomSerializer) if recompiling isn't an option.") - val returnType = resolveTypeVariables(getter.genericReturnType, type) - if (!constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param.value)) { - throw NotSerializableException( - "Property type '$returnType' for '$name' of '$clazz' differs from constructor parameter " - + "type '${param.value.type.javaType}'") + val returnType = resolveTypeVariables(getter.genericReturnType, type) + if (!constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param.value)) { + throw NotSerializableException( + "Property '$name' has type '$returnType' on class '$clazz' but differs from constructor " + + "parameter type '${param.value.type.javaType}'") + } + + Pair(PublicPropertyReader(getter), returnType) + } else { + try { + val field = clazz.getDeclaredField(param.value.name) + Pair(PrivatePropertyReader(field, type), field.genericType) + } catch (e: NoSuchFieldException) { + throw NotSerializableException("No property matching constructor parameter named '$name' " + + "of '$clazz'. If using Java, check that you have the -parameters option specified " + + "in the Java compiler. Alternately, provide a proxy serializer " + + "(SerializationCustomSerializer) if recompiling isn't an option") + } } - - Pair(PublicPropertyReader(getter), returnType) } else { - try { - val field = clazz.getDeclaredField(param.value.name) - Pair(PrivatePropertyReader(field, type), field.genericType) - } catch (e: NoSuchFieldException) { - throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'. " + - "If using Java, check that you have the -parameters option specified in the Java compiler. " + - "Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option") - } + throw NotSerializableException( + "Constructor parameter $name doesn't refer to a property of class '$clazz'") } this += PropertyAccessorConstructor( @@ -148,18 +251,26 @@ private fun propertiesForSerializationFromSetters( return mutableListOf().apply { var idx = 0 properties.forEach { property -> - val getter: Method? = property.value.readMethod - val setter: Method? = property.value.writeMethod + val getter: Method? = property.value.getter + val setter: Method? = property.value.setter if (getter == null || setter == null) return@forEach - // NOTE: There is no need to check return and parameter types vs the underlying type for - // the getter / setter vs property as if there is a difference then that property isn't reported - // by the BEAN inspector and thus we don't consider that case here + if (setter.parameterCount != 1) { + throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " + + "takes too many arguments") + } - this += PropertyAccessorGetterSetter ( + val setterType = setter.parameterTypes.getOrNull(0)!! + if (!(TypeToken.of(property.value.field?.genericType!!).isSupertypeOf(setterType))) { + throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " + + "takes parameter of type $setterType yet underlying type is " + + "${property.value.field?.genericType!!}") + } + + this += PropertyAccessorGetterSetter( idx++, - PropertySerializer.make(property.key, PublicPropertyReader(getter), + PropertySerializer.make(property.value.field!!.name, PublicPropertyReader(getter), resolveTypeVariables(getter.genericReturnType, type), factory), setter) } @@ -186,23 +297,17 @@ private fun propertiesForSerializationFromAbstract( clazz: Class<*>, type: Type, factory: SerializerFactory): List { - // Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans. - val properties = Introspector.getBeanInfo(clazz).propertyDescriptors - .filter { it.name != "class" } - .sortedBy { it.name } - .filterNot { it is IndexedPropertyDescriptor } + val properties = clazz.propertyDescriptors() - return mutableListOf().apply { - properties.withIndex().forEach { property -> - // Check that the method has a getter in java. - val getter = property.value.readMethod ?: throw NotSerializableException( - "Property has no getter method for ${property.value.name} of $clazz.") - val returnType = resolveTypeVariables(getter.genericReturnType, type) - this += PropertyAccessorConstructor( - property.index, - PropertySerializer.make(property.value.name, PublicPropertyReader(getter), returnType, factory)) - } - } + return mutableListOf().apply { + properties.toList().withIndex().forEach { + val getter = it.value.second.getter ?: return@forEach + val returnType = resolveTypeVariables(getter.genericReturnType, type) + this += PropertyAccessorConstructor( + it.index, + PropertySerializer.make(it.value.first, PublicPropertyReader(getter), returnType, factory)) + } + } } internal fun interfacesForSerialization(type: Type, serializerFactory: SerializerFactory): List { @@ -270,7 +375,11 @@ private fun resolveTypeVariables(actualType: Type, contextType: Type?): Type { // TODO: surely we check it is concrete at this point with no TypeVariables return if (resolvedType is TypeVariable<*>) { val bounds = resolvedType.bounds - return if (bounds.isEmpty()) SerializerFactory.AnyType else if (bounds.size == 1) resolveTypeVariables(bounds[0], contextType) else throw NotSerializableException("Got bounded type $actualType but only support single bound.") + return if (bounds.isEmpty()) { + SerializerFactory.AnyType + } else if (bounds.size == 1) { + resolveTypeVariables(bounds[0], contextType) + } else throw NotSerializableException("Got bounded type $actualType but only support single bound.") } else { resolvedType } diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java index 59a1465710..b13cc2cc48 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/JavaPrivatePropertyTests.java @@ -23,6 +23,115 @@ public class JavaPrivatePropertyTests { public String getA() { return a; } } + static class B { + private Boolean b; + + B(Boolean b) { this.b = b; } + + public Boolean isB() { + return this.b; + } + } + + static class B2 { + private Boolean b; + + public Boolean isB() { + return this.b; + } + + public void setB(Boolean b) { + this.b = b; + } + } + + static class B3 { + private Boolean b; + + // break the BEAN format explicitly (i.e. it's not isB) + public Boolean isb() { + return this.b; + } + + public void setB(Boolean b) { + this.b = b; + } + } + + static class C3 { + private Integer a; + + public Integer getA() { + return this.a; + } + + public Boolean isA() { + return this.a > 0; + } + + public void setA(Integer a) { + this.a = a; + } + } + + @Test + public void singlePrivateBooleanWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException { + EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter(); + SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(), + evolutionSerializerGetter); + SerializationOutput ser = new SerializationOutput(factory); + DeserializationInput des = new DeserializationInput(factory); + + B b = new B(true); + B b2 = des.deserialize(ser.serialize(b), B.class); + assertEquals (b.b, b2.b); + } + + @Test + public void singlePrivateBooleanWithNoConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException { + EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter(); + SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(), + evolutionSerializerGetter); + SerializationOutput ser = new SerializationOutput(factory); + DeserializationInput des = new DeserializationInput(factory); + + B2 b = new B2(); + b.setB(false); + B2 b2 = des.deserialize(ser.serialize(b), B2.class); + assertEquals (b.b, b2.b); + } + + @Test + public void testCapitilsationOfIs() throws NotSerializableException, NoSuchFieldException, IllegalAccessException { + EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter(); + SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(), + evolutionSerializerGetter); + SerializationOutput ser = new SerializationOutput(factory); + DeserializationInput des = new DeserializationInput(factory); + + B3 b = new B3(); + b.setB(false); + B3 b2 = des.deserialize(ser.serialize(b), B3.class); + + // since we can't find a getter for b (isb != isB) then we won't serialize that parameter + assertEquals (null, b2.b); + } + + @Test + public void singlePrivateIntWithBoolean() throws NotSerializableException, NoSuchFieldException, IllegalAccessException { + EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter(); + SerializerFactory factory = new SerializerFactory(AllWhitelist.INSTANCE, ClassLoader.getSystemClassLoader(), + evolutionSerializerGetter); + SerializationOutput ser = new SerializationOutput(factory); + DeserializationInput des = new DeserializationInput(factory); + + C3 c = new C3(); + c.setA(12345); + C3 c2 = des.deserialize(ser.serialize(c), C3.class); + + assertEquals (c.a, c2.a); + } + @Test public void singlePrivateWithConstructor() throws NotSerializableException, NoSuchFieldException, IllegalAccessException { EvolutionSerializerGetterBase evolutionSerializerGetter = new EvolutionSerializerGetter(); diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt index 60830cda88..6490f48522 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/GenericsTests.kt @@ -501,4 +501,29 @@ class GenericsTests { assertEquals(state.context.a, des1.obj.state.context.a) } + fun implemntsGeneric() { + open class B(open val a: T) + class D(override val a: String) : B(a) + + val factory = testDefaultFactoryNoEvolution() + + val bytes = SerializationOutput(factory).serialize(D("Test")) + + DeserializationInput(factory).deserialize(bytes).apply { assertEquals("Test", this.a) } + } + + interface implementsGenericInterfaceI { + val a: T + } + + @Test + fun implemntsGenericInterface() { + class D(override val a: String) : implementsGenericInterfaceI + + val factory = testDefaultFactoryNoEvolution() + + val bytes = SerializationOutput(factory).serialize(D("Test")) + + DeserializationInput(factory).deserialize(bytes).apply { assertEquals("Test", this.a) } + } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt index 0ee20ab311..a79416ff6f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt @@ -6,6 +6,8 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.junit.Test import org.apache.qpid.proton.amqp.Symbol +import org.assertj.core.api.Assertions +import java.io.NotSerializableException import java.util.concurrent.ConcurrentHashMap class PrivatePropertyTests { @@ -29,6 +31,15 @@ class PrivatePropertyTests { assertEquals(c1, c2) } + @Test + fun testWithOnePrivatePropertyBoolean() { + data class C(private val b: Boolean) + + C(false).apply { + assertEquals(this, DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(this))) + } + } + @Test fun testWithOnePrivatePropertyNullableNotNull() { data class C(private val b: String?) @@ -56,6 +67,61 @@ class PrivatePropertyTests { assertEquals(c1, c2) } + @Test + fun testWithInheritance() { + open class B(val a: String, protected val b: String) + class D (a: String, b: String) : B (a, b) { + override fun equals(other: Any?): Boolean = when (other) { + is D -> other.a == a && other.b == b + else -> false + } + } + + val d1 = D("clump", "lump") + val d2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(d1)) + + assertEquals(d1, d2) + } + + @Test + fun testMultiArgSetter() { + @Suppress("UNUSED") + data class C(private var a: Int, val b: Int) { + // This will force the serialization engine to use getter / setter + // instantiation for the object rather than construction + @ConstructorForDeserialization + constructor() : this(0, 0) + + fun setA(a: Int, b: Int) { this.a = a } + fun getA() = a + } + + val c1 = C(33, 44) + Assertions.assertThatThrownBy { + SerializationOutput(factory).serialize(c1) + }.isInstanceOf(NotSerializableException::class.java).hasMessageContaining( + "Defined setter for parameter a takes too many arguments") + } + + @Test + fun testBadTypeArgSetter() { + @Suppress("UNUSED") + data class C(private var a: Int, val b: Int) { + @ConstructorForDeserialization + constructor() : this(0, 0) + + fun setA(a: String) { this.a = a.toInt() } + fun getA() = a + } + + val c1 = C(33, 44) + Assertions.assertThatThrownBy { + SerializationOutput(factory).serialize(c1) + }.isInstanceOf(NotSerializableException::class.java).hasMessageContaining( + "Defined setter for parameter a takes parameter of type class java.lang.String " + + "yet underlying type is int ") + } + @Test fun testWithOnePublicOnePrivateProperty2() { data class C(val a: Int, private val b: Int) @@ -127,7 +193,6 @@ class PrivatePropertyTests { // Inner and Outer assertEquals(2, serializersByDescriptor.size) - val schemaDescriptor = output.schema.types.first().descriptor.name val c2 = DeserializationInput(factory).deserialize(output.obj) assertEquals(c1, c2) From 2a3c4eb3cdc88375f217ddecbfb953179b008f99 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Fri, 2 Feb 2018 17:26:54 +0000 Subject: [PATCH 014/114] Documents test frameworks in quickstart guide for visibility. Small tweaks. --- docs/source/tutorial-cordapp.rst | 212 ++++++++++++++----------------- 1 file changed, 92 insertions(+), 120 deletions(-) diff --git a/docs/source/tutorial-cordapp.rst b/docs/source/tutorial-cordapp.rst index 240a9c2eb9..186f250d80 100644 --- a/docs/source/tutorial-cordapp.rst +++ b/docs/source/tutorial-cordapp.rst @@ -9,13 +9,12 @@ The example CorDapp .. contents:: -The example CorDapp allows nodes to agree IOUs with each other. Nodes will always agree to the creation of a new IOU -if: +The example CorDapp allows nodes to agree IOUs with each other, as long as they obey the following contract rules: -* Its value is strictly positive -* The node is not trying to issue the IOU to itself +* The IOU's value is strictly positive +* A node is not trying to issue an IOU to itself -We will deploy the CorDapp on 4 test nodes: +We will deploy and run the CorDapp on four test nodes: * **NetworkMapAndNotary**, which hosts a validating notary service * **PartyA** @@ -27,7 +26,7 @@ facts" between PartyA and PartyB only. PartyC won't be aware of these IOUs. Downloading the example CorDapp ------------------------------- -We need to download the example CorDapp from GitHub. +Start by downloading the example CorDapp from GitHub: * Set up your machine by following the :doc:`quickstart guide ` @@ -36,14 +35,11 @@ We need to download the example CorDapp from GitHub. * Change directories to the freshly cloned repo: ``cd cordapp-example`` -.. note:: If you wish to build off the latest, unstable version of the codebase, follow the instructions in - :doc:`building against Master ` instead. - Opening the example CorDapp in IntelliJ --------------------------------------- -Let's open the example CorDapp in IntelliJ IDEA. +Let's open the example CorDapp in IntelliJ IDEA: -**If opening a fresh IntelliJ instance** +**If opening a fresh IntelliJ instance**: * Open IntelliJ * A dialogue box will appear: @@ -60,12 +56,12 @@ Let's open the example CorDapp in IntelliJ IDEA. * Click the 'import gradle project' link. Press OK on the dialogue that pops up -* Gradle will now download all the project dependencies and perform some indexing. This usually takes a minute or so. +* Gradle will now download all the project dependencies and perform some indexing. This usually takes a minute or so * If the 'import gradle project' pop-up does not appear, click the small green speech bubble at the bottom-right of - the IDE, or simply close and re-open IntelliJ again to make it reappear. + the IDE, or simply close and re-open IntelliJ again to make it reappear -**If you already have IntelliJ open** +**If you already have IntelliJ open**: * Open the ``File`` menu @@ -76,8 +72,8 @@ Let's open the example CorDapp in IntelliJ IDEA. * Click OK Project structure ------------------ -The example CorDapp has the following directory structure: +~~~~~~~~~~~~~~~~~ +The example CorDapp has the following structure: .. sourcecode:: none @@ -175,11 +171,11 @@ There are two ways to run the example CorDapp: * Via the terminal * Via IntelliJ -In both cases, we will deploy a set of test nodes with our CorDapp installed, then run the nodes. You can read more -about how we define the nodes to be deployed :doc:`here `. +Both approaches will create a set of test nodes, install the CorDapp on these nodes, and then run the nodes. You can +read more about how we generate nodes :doc:`here `. -Terminal -~~~~~~~~ +Running the example CorDapp from the terminal +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Building the example CorDapp ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -191,29 +187,26 @@ Building the example CorDapp * Windows: ``gradlew.bat deployNodes`` - This will automatically build four pre-configured nodes with our CorDapp installed. These nodes are meant for local - testing only + This will automatically build four nodes with our CorDapp already installed .. note:: CorDapps can be written in any language targeting the JVM. In our case, we've provided the example source in - both Kotlin (``/kotlin-source/src``) and Java (``/java-source/src``) Since both sets of source files are - functionally identical, we will refer to the Kotlin build throughout the documentation. + both Kotlin (``/kotlin-source/src``) and Java (``/java-source/src``). Since both sets of source files are + functionally identical, we will refer to the Kotlin version throughout the documentation. -* After the build process has finished, you will see the newly-build nodes in the ``kotlin-source/build/nodes`` folder +* After the build finishes, you will see the generated nodes in the ``kotlin-source/build/nodes`` folder - * There will be one folder generated for each node you built, plus a ``runnodes`` shell script (or batch file on - Windows) to run all the nodes simultaneously + * There will be a folder for each generated node, plus a ``runnodes`` shell script (or batch file on Windows) to run + all the nodes simultaneously * Each node in the ``nodes`` folder has the following structure: .. sourcecode:: none . nodeName - ├── corda.jar - ├── node.conf - └── cordapps - - ``corda.jar`` is the Corda runtime, ``cordapps`` contains our node's CorDapps, and the node's configuration is - given by ``node.conf`` + ├── corda.jar // The Corda node runtime. + ├── corda-webserver.jar // The node development webserver. + ├── node.conf // The node configuration file. + └── cordapps // The node's CorDapps. Running the example CorDapp ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -239,7 +232,7 @@ For each node, the ``runnodes`` script creates a node tab/window: 📚 New! Training now available worldwide, see https://corda.net/corda-training/ - Logs can be found in : /Users/joeldudley/Desktop/cordapp-example/kotlin-source/build/nodes/PartyA/logs + Logs can be found in : /Users/username/Desktop/cordapp-example/kotlin-source/build/nodes/PartyA/logs Database connection url is : jdbc:h2:tcp://10.163.199.132:54763/node Listening on address : 127.0.0.1:10005 RPC service listening on address : localhost:10006 @@ -256,16 +249,16 @@ For every node except the network map/notary, the script also creates a webserve .. sourcecode:: none - Logs can be found in /Users/joeldudley/Desktop/cordapp-example/kotlin-source/build/nodes/PartyA/logs/web + Logs can be found in /Users/username/Desktop/cordapp-example/kotlin-source/build/nodes/PartyA/logs/web Starting as webserver: localhost:10007 Webserver started up in 42.02 sec -It usually takes around 60 seconds for the nodes to finish starting up. To ensure that all the nodes are running OK, -you can query the 'status' end-point located at ``http://localhost:[port]/api/status`` (e.g. +It usually takes around 60 seconds for the nodes to finish starting up. To ensure that all the nodes are running, you +can query the 'status' end-point located at ``http://localhost:[port]/api/status`` (e.g. ``http://localhost:10007/api/status`` for ``PartyA``). -IntelliJ -~~~~~~~~ +Running the example CorDapp from IntelliJ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Select the ``Run Example CorDapp - Kotlin`` run configuration from the drop-down menu at the top right-hand side of the IDE @@ -274,66 +267,38 @@ IntelliJ .. image:: resources/run-config-drop-down.png :width: 400 - The node driver defined in ``/src/test/kotlin/com/example/Main.kt`` allows you to specify how many nodes you would like - to run and the configuration settings for each node. For the example CorDapp, the driver starts up four nodes - and adds an RPC user for all but the network map/notary node: - - .. sourcecode:: kotlin - - fun main(args: Array) { - // No permissions required as we are not invoking flows. - val user = User("user1", "test", permissions = setOf()) - driver(isDebug = true, waitForNodesToFinish = true) { - startNode(getX500Name(O="NetworkMapAndNotary",L="London",C='GB"), setOf(ServiceInfo(ValidatingNotaryService.type))) - val (nodeA, nodeB, nodeC) = Futures.allAsList( - startNode(getX500Name(O="PartyA",L="London",C="GB"), rpcUsers = listOf(user)), - startNode(getX500Name(O="PartyB",L="New York",C="US"), rpcUsers = listOf(user)), - startNode(getX500Name(O="PartyC",L="Paris",C="FR"), rpcUsers = listOf(user))).getOrThrow() - - startWebserver(nodeA) - startWebserver(nodeB) - startWebserver(nodeC) - } - } - * To stop the nodes, press the red square button at the top right-hand side of the IDE, next to the run configurations -Later, we'll look at how the node driver can be useful for `debugging your CorDapp`_. - Interacting with the example CorDapp ------------------------------------ Via HTTP ~~~~~~~~ -The CorDapp defines several HTTP API end-points and a web front-end. The end-points allow you to list the IOUs a node -is involved in, agree new IOUs, and see who is on the network. +The nodes' webservers run locally on the following ports: -The nodes are running locally on the following ports: +* PartyA: ``localhost:10007`` +* PartyB: ``localhost:10010`` +* PartyC: ``localhost:10013`` -* PartyA: ``localhost:10007`` -* PartyB: ``localhost:10010`` -* PartyC: ``localhost:10013`` +These ports are defined in each node's node.conf file under ``kotlin-source/build/nodes/NodeX/node.conf``. -These ports are defined in build.gradle and in each node's node.conf file under ``kotlin-source/build/nodes/NodeX``. - -As the nodes start up, they should tell you which port their embedded web server is running on. The available API -endpoints are: +Each node webserver exposes the following endpoints: * ``/api/example/me`` * ``/api/example/peers`` * ``/api/example/ious`` * ``/api/example/create-iou`` with parameters ``iouValue`` and ``partyName`` which is CN name of a node -The web front-end is served from ``/web/example``. - -An IOU can be created by sending a PUT request to the ``api/example/create-iou`` end-point directly, or by using the -the web form hosted at ``/web/example``. +There is also a web front-end served from ``/web/example``. .. warning:: The content in ``web/example`` is only available for demonstration purposes and does not implement - anti-XSS, anti-XSRF or any other security techniques. Do not use this code in production. + anti-XSS, anti-XSRF or other security techniques. Do not use this code in production. Creating an IOU via the endpoint ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +An IOU can be created by sending a PUT request to the ``api/example/create-iou`` endpoint directly, or by using the +the web form served from ``/web/example``. + To create an IOU between PartyA and PartyB, run the following command from the command line: .. sourcecode:: bash @@ -356,8 +321,8 @@ of the page, and enter the IOU details into the web-form. The IOU must have a po And click submit. Upon clicking submit, the modal dialogue will close, and the nodes will agree the IOU. -Once an IOU has been submitted -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Checking the output +^^^^^^^^^^^^^^^^^^^ Assuming all went well, you should see some activity in PartyA's web-server terminal window: .. sourcecode:: none @@ -387,7 +352,7 @@ You can view the newly-created IOU by accessing the vault of PartyA or PartyB: * PartyA: Navigate to http://localhost:10007/web/example and hit the "refresh" button * PartyA: Navigate to http://localhost:10010/web/example and hit the "refresh" button -The vault and web front-end of PartyC (on ``localhost:10013``) will not display any IOUs. This is because PartyC was +The vault and web front-end of PartyC (at ``localhost:10013``) will not display any IOUs. This is because PartyC was not involved in this transaction. Via the interactive shell (terminal only) @@ -414,6 +379,8 @@ following list: net.corda.finance.flows.CashIssueFlow net.corda.finance.flows.CashPaymentFlow +Creating an IOU via the interactive shell +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ We can create a new IOU using the ``ExampleFlow$Initiator`` flow. For example, from the interactive shell of PartyA, you can agree an IOU of 50 with PartyB by running ``flow start ExampleFlow$Initiator iouValue: 50, otherParty: "O=PartyB,L=New York,C=US"``. @@ -435,9 +402,15 @@ This will print out the following progress steps: ✅ Broadcasting transaction to participants ✅ Done +Checking the output +^^^^^^^^^^^^^^^^^^^ We can also issue RPC operations to the node via the interactive shell. Type ``run`` to see the full list of available operations. +You can see the newly-created IOU by running ``run vaultQuery contractStateType: com.example.state.IOUState``. + +As before, the interactive shell of PartyC will not display any IOUs. + Via the h2 web console ~~~~~~~~~~~~~~~~~~~~~~ You can connect directly to your node's database to see its stored states, transactions and attachments. To do so, @@ -445,17 +418,17 @@ please follow the instructions in :doc:`node-database`. Using the example RPC client ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``/src/main/kotlin-source/com/example/client/ExampleClientRPC.kt`` file is a simple utility that uses the client -RPC library to connect to a node. It will log any existing IOUs and listen for any future IOUs. If you haven't created +``/src/main/kotlin-source/com/example/client/ExampleClientRPC.kt`` defines a simple RPC client that connects to a node, +logs any existing IOUs and listens for any future IOUs. If you haven't created any IOUs when you first connect to one of the nodes, the client will simply log any future IOUs that are agreed. -*Running the client via IntelliJ:* - -Select the 'Run Example RPC Client' run configuration which, by default, connects to PartyA. Click the green arrow to -run the client. You can edit the run configuration to connect on a different port. - -*Running the client via the command line:* +Running the client via IntelliJ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Run the 'Run Example RPC Client' run configuration. By default, this run configuration is configured to connect to +PartyA. You can edit the run configuration to connect on a different port. +Running the client via the command line +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Run the following gradle task: ``./gradlew runExampleClientRPCKotlin`` @@ -469,7 +442,7 @@ For more information on the client RPC interface and how to build an RPC client * :doc:`Client RPC documentation ` * :doc:`Client RPC tutorial ` -Running Nodes Across Machines +Running nodes across machines ----------------------------- The nodes can be split across machines and configured to communicate across the network. @@ -490,35 +463,32 @@ and make the following changes: After starting each node, the nodes will be able to see one another and agree IOUs among themselves. -Debugging your CorDapp ----------------------- +Testing and debugging +--------------------- + +Testing a CorDapp +~~~~~~~~~~~~~~~~~ +Corda provides several frameworks for writing unit and integration tests for CorDapps. + +Contract tests +^^^^^^^^^^^^^^ +You can run the CorDapp's contract tests by running the ``Run Contract Tests - Kotlin`` run configuration. + +Flow tests +^^^^^^^^^^ +You can run the CorDapp's flow tests by running the ``Run Flow Tests - Kotlin`` run configuration. + +Integration tests +^^^^^^^^^^^^^^^^^ +You can run the CorDapp's integration tests by running the ``Run Integration Tests - Kotlin`` run configuration. + +Debugging Corda nodes +~~~~~~~~~~~~~~~~~~~~~ Debugging is done via IntelliJ as follows: -1. Edit the node driver code in ``Main.kt`` based on the number of nodes you wish to start, along with any other - configuration options. For example, the code below starts 4 nodes, with one being the network map service and - notary. It also sets up RPC credentials for the three non-notary nodes +1. Start the nodes using the “Run Example CorDapp” run configuration in IntelliJ -.. sourcecode:: kotlin - - fun main(args: Array) { - // No permissions required as we are not invoking flows. - val user = User("user1", "test", permissions = setOf()) - driver(isDebug = true, waitForNodesToFinish = true) { - startNode(getX500Name(O="NetworkMapAndNotary",L="London",C="GB"), setOf(ServiceInfo(ValidatingNotaryService.type))) - val (nodeA, nodeB, nodeC) = Futures.allAsList( - startNode(getX500Name(O="PartyA",L=London,C=GB"), rpcUsers = listOf(user)), - startNode(getX500Name(O="PartyB",L=New York,C=US"), rpcUsers = listOf(user)), - startNode(getX500Name(O="PartyC",L=Paris,C=FR"), rpcUsers = listOf(user))).getOrThrow() - - startWebserver(nodeA) - startWebserver(nodeB) - startWebserver(nodeC) - } - } - -2. Select and run the “Run Example CorDapp” run configuration in IntelliJ - -3. IntelliJ will build and run the CorDapp. The remote debug ports for each node will be automatically generated and +2. IntelliJ will build and run the CorDapp. The remote debug ports for each node will be automatically generated and printed to the terminal. For example: .. sourcecode:: none @@ -526,9 +496,11 @@ Debugging is done via IntelliJ as follows: [INFO ] 15:27:59.533 [main] Node.logStartupInfo - Working Directory: /Users/joeldudley/cordapp-example/build/20170707142746/PartyA [INFO ] 15:27:59.533 [main] Node.logStartupInfo - Debug port: dt_socket:5007 -4. Edit the “Debug CorDapp” run configuration with the port of the node you wish to connect to +3. Edit the “Debug CorDapp” run configuration with the port of the node you wish to connect to -5. Run the “Debug CorDapp” run configuration +4. Run the “Debug CorDapp” run configuration -6. Set your breakpoints and start interacting with the node you wish to connect to. When the node hits a breakpoint, - execution will pause \ No newline at end of file +5. Set your breakpoints and interact with the node you've connected to. When the node hits a breakpoint, execution will + pause + + * The node webserver runs in a separate process, and is not attached to by the debugger \ No newline at end of file From 8e2524f35d4e9455e9e8f20cae8de6f0323a3070 Mon Sep 17 00:00:00 2001 From: josecoll Date: Fri, 2 Feb 2018 17:33:35 +0000 Subject: [PATCH 015/114] CORDA-977 PK constraint violation when up adding Mapping to DBTransactionMapping store (#2457) * Default jolokia version with optional override in CorDapp project gradle file. * Tolerate duplicates to prevent a PK violation. --- .../node/services/persistence/DBTransactionMappingStorage.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt index 3ed86c65af..e81c6767f8 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt @@ -55,7 +55,7 @@ class DBTransactionMappingStorage : StateMachineRecordedTransactionMappingStorag val updates: PublishSubject = PublishSubject.create() override fun addMapping(stateMachineRunId: StateMachineRunId, transactionId: SecureHash) { - stateMachineTransactionMap[transactionId] = stateMachineRunId + stateMachineTransactionMap.addWithDuplicatesAllowed(transactionId, stateMachineRunId) updates.bufferUntilDatabaseCommit().onNext(StateMachineTransactionMapping(stateMachineRunId, transactionId)) } From a08d333d5b2096e8f6573ed8afdf3f9a29633693 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 5 Feb 2018 09:48:41 +0000 Subject: [PATCH 016/114] CORDA-978 - Only consider getters that accpet zero parameters (#2462) * CORDA-978 - Only take getters with zero parameters * tidy up --- .../serialization/amqp/SerializationHelper.kt | 35 ++++++++++------- .../amqp/PrivatePropertyTests.kt | 9 ++--- .../amqp/SerializationOutputTests.kt | 38 +++++++++++++++++++ 3 files changed, 64 insertions(+), 18 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index b65ec08f58..b4c15a0110 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -127,7 +127,7 @@ fun Class.propertyDescriptors(): Map { clazz!!.declaredFields.forEach { classProperties.put(it.name, PropertyDescriptor(it, null, null)) } // then pair them up with the declared getter and setter - // Note: It is possible for a class to have multipleinstancess of a function where the types + // Note: It is possible for a class to have multiple instancess of a function where the types // differ. For example: // interface I { val a: T } // class D(override val a: String) : I @@ -135,28 +135,37 @@ fun Class.propertyDescriptors(): Map { // getA - returning a String (java.lang.String) and // getA - returning an Object (java.lang.Object) // In this instance we take the most derived object - clazz.declaredMethods?.map { - PropertyDescriptorsRegex.re.find(it.name)?.apply { + // + // In addition, only getters that take zero parameters and setters that take a single + // parameter will be considered + clazz.declaredMethods?.map { func -> + PropertyDescriptorsRegex.re.find(func.name)?.apply { try { classProperties.getValue(groups[2]!!.value.decapitalize()).apply { when (groups[1]!!.value) { "set" -> { - if (setter == null) setter = it - else if (TypeToken.of(setter!!.genericReturnType).isSupertypeOf(it.genericReturnType)) { - setter = it + if (func.parameterCount == 1) { + if (setter == null) setter = func + else if (TypeToken.of(setter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) { + setter = func + } } } "get" -> { - if (getter == null) getter = it - else if (TypeToken.of(getter!!.genericReturnType).isSupertypeOf(it.genericReturnType)) { - getter = it + if (func.parameterCount == 0) { + if (getter == null) getter = func + else if (TypeToken.of(getter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) { + getter = func + } } } "is" -> { - val rtnType = TypeToken.of(it.genericReturnType) - if ((rtnType == TypeToken.of(Boolean::class.java)) - || (rtnType == TypeToken.of(Boolean::class.javaObjectType))) { - if (getter == null) getter = it + if (func.parameterCount == 0) { + val rtnType = TypeToken.of(func.genericReturnType) + if ((rtnType == TypeToken.of(Boolean::class.java)) + || (rtnType == TypeToken.of(Boolean::class.javaObjectType))) { + if (getter == null) getter = func + } } } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt index a79416ff6f..ae35d46286 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/PrivatePropertyTests.kt @@ -86,7 +86,7 @@ class PrivatePropertyTests { @Test fun testMultiArgSetter() { @Suppress("UNUSED") - data class C(private var a: Int, val b: Int) { + data class C(private var a: Int, var b: Int) { // This will force the serialization engine to use getter / setter // instantiation for the object rather than construction @ConstructorForDeserialization @@ -97,10 +97,9 @@ class PrivatePropertyTests { } val c1 = C(33, 44) - Assertions.assertThatThrownBy { - SerializationOutput(factory).serialize(c1) - }.isInstanceOf(NotSerializableException::class.java).hasMessageContaining( - "Defined setter for parameter a takes too many arguments") + val c2 = DeserializationInput(factory).deserialize(SerializationOutput(factory).serialize(c1)) + assertEquals(0, c2.getA()) + assertEquals(44, c2.b) } @Test diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index d8c1db9cf9..89b2dcbad4 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -20,6 +20,8 @@ import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.EmptyWhitelist import net.corda.nodeapi.internal.serialization.GeneratedAttachment import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.isPrimitive +import net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer +import net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer import net.corda.testing.contracts.DummyContract import net.corda.testing.core.BOB_NAME import net.corda.testing.core.SerializationEnvironmentRule @@ -36,11 +38,13 @@ import org.junit.Test import java.io.ByteArrayInputStream import java.io.IOException import java.io.NotSerializableException +import java.lang.reflect.Type import java.math.BigDecimal import java.nio.ByteBuffer import java.time.* import java.time.temporal.ChronoUnit import java.util.* +import java.util.concurrent.ConcurrentHashMap import kotlin.reflect.full.superclasses import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -1080,4 +1084,38 @@ class SerializationOutputTests { serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false) }.isInstanceOf(MissingAttachmentsException::class.java) } + + // + // Example stacktrace that this test is tryint to reproduce + // + // java.lang.IllegalArgumentException: + // net.corda.core.contracts.TransactionState -> + // data(net.corda.core.contracts.ContractState) -> + // net.corda.finance.contracts.asset.Cash$State -> + // amount(net.corda.core.contracts.Amount>) -> + // net.corda.core.contracts.Amount> -> + // displayTokenSize(java.math.BigDecimal) -> + // wrong number of arguments + // + // So the actual problem was objects with multiple getters. The code wasn't looking for one with zero + // properties, just taking the first one it found with with the most applicable type, and the reflection + // ordering of the methods was random, thus occasionally we select the wrong one + // + @Test + fun reproduceWrongNumberOfArguments() { + val field = SerializerFactory::class.java.getDeclaredField("serializersByType").apply { + this.isAccessible = true + } + + data class C(val a: Amount) + + val factory = testDefaultFactoryNoEvolution() + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer) + factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer) + + val c = C(Amount(100, BigDecimal("1.5"), Currency.getInstance("USD"))) + + // were the issue not fixed we'd blow up here + SerializationOutput(factory).serialize(c) + } } \ No newline at end of file From a9856b9ce6c600941707fca7b60340701209c3de Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Mon, 5 Feb 2018 11:42:20 +0000 Subject: [PATCH 017/114] Docker system (end-to-end) test (#2437) * System test for IRS Demo utilizing docker, docker-compose and PhantomJS to automate full-stack testing --- build.gradle | 4 + constants.properties | 5 +- gradle-plugins/build.gradle | 1 + .../java/net/corda/cordform/CordformNode.java | 19 +- .../java/net/corda/cordform/RpcSettings.java | 26 ++- .../java/net/corda/cordform/SslOptions.java | 2 +- gradle-plugins/cordformation/build.gradle | 2 + .../main/kotlin/net/corda/plugins/Baseform.kt | 174 ++++++++++++++++++ .../main/kotlin/net/corda/plugins/Cordform.kt | 154 +--------------- .../kotlin/net/corda/plugins/Dockerform.kt | 66 +++++++ .../src/main/kotlin/net/corda/plugins/Node.kt | 51 ++++- .../kotlin/net/corda/plugins/RpcSettings.kt | 59 ------ .../kotlin/net/corda/plugins/SslOptions.kt | 50 ----- .../resources/net/corda/plugins/Dockerfile | 44 +++++ .../resources/net/corda/plugins/run-corda.sh | 10 + samples/irs-demo/README.md | 23 +++ samples/irs-demo/build.gradle | 37 ++++ samples/irs-demo/cordapp/build.gradle | 89 ++++++--- .../system-test/kotlin/IRSDemoDockerTest.kt | 80 ++++++++ samples/irs-demo/web/build.gradle | 83 ++++++++- .../corda/irs/web/IrsDemoWebApplication.kt | 20 +- .../application-NotaryService.properties | 2 +- .../web/src/main/resources/static/js/Deal.js | 34 ++-- .../web/src/main/resources/static/js/app.js | 16 +- .../static/js/controllers/CreateDeal.js | 31 ++-- .../resources/static/js/controllers/Deal.js | 18 +- .../resources/static/js/controllers/Home.js | 26 +-- .../resources/static/js/require-config.js | 12 +- .../src/main/resources/static/js/routes.js | 40 ++-- .../static/js/services/HttpErrorHandler.js | 10 +- .../resources/static/js/services/NodeApi.js | 79 ++++---- .../static/js/utils/dayCountBasisLookup.js | 4 +- .../resources/static/js/utils/semantic.js | 10 +- .../resources/static/js/viewmodel/Common.js | 4 +- .../resources/static/js/viewmodel/Deal.js | 2 +- .../resources/static/js/viewmodel/FixedLeg.js | 4 +- .../static/js/viewmodel/FloatingLeg.js | 6 +- .../src/main/resources/static/view/home.html | 4 +- 38 files changed, 829 insertions(+), 472 deletions(-) create mode 100644 gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Baseform.kt create mode 100644 gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt delete mode 100644 gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt delete mode 100644 gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/SslOptions.kt create mode 100644 gradle-plugins/cordformation/src/main/resources/net/corda/plugins/Dockerfile create mode 100644 gradle-plugins/cordformation/src/main/resources/net/corda/plugins/run-corda.sh create mode 100644 samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt diff --git a/build.gradle b/build.gradle index c65590cebf..5d7b2353a3 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,10 @@ buildscript { ext.jsr305_version = constants.getProperty("jsr305Version") ext.shiro_version = '1.4.0' ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') + ext.snake_yaml_version = constants.getProperty('snakeYamlVersion') + ext.docker_compose_rule_version = '0.33.0' + ext.selenium_version = '3.8.1' + ext.ghostdriver_version = '2.1.0' // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: ext.java8_minUpdateVersion = '131' diff --git a/constants.properties b/constants.properties index 7744042d3c..b2c39c9ce2 100644 --- a/constants.properties +++ b/constants.properties @@ -1,8 +1,9 @@ -gradlePluginsVersion=3.0.4 +gradlePluginsVersion=3.0.5 kotlinVersion=1.1.60 platformVersion=2 guavaVersion=21.0 bouncycastleVersion=1.57 typesafeConfigVersion=1.3.1 jsr305Version=3.0.2 -artifactoryPluginVersion=4.4.18 \ No newline at end of file +artifactoryPluginVersion=4.4.18 +snakeYamlVersion=1.19 \ No newline at end of file diff --git a/gradle-plugins/build.gradle b/gradle-plugins/build.gradle index 1e04813546..32763f85f6 100644 --- a/gradle-plugins/build.gradle +++ b/gradle-plugins/build.gradle @@ -14,6 +14,7 @@ buildscript { jsr305_version = constants.getProperty("jsr305Version") kotlin_version = constants.getProperty("kotlinVersion") artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') + snake_yaml_version = constants.getProperty('snakeYamlVersion') } repositories { diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java index 52300f0f63..c3dc01a5da 100644 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/CordformNode.java @@ -20,7 +20,8 @@ public class CordformNode implements NodeDefinition { protected static final String DEFAULT_HOST = "localhost"; /** - * Name of the node. + * Name of the node. Node will be placed in directory based on this name - all lowercase with whitespaces removed. + * Actual node name inside node.conf will be as set here. */ private String name; @@ -28,6 +29,20 @@ public class CordformNode implements NodeDefinition { return name; } + /** + * p2p Port. + */ + private int p2pPort = 10002; + + public int getP2pPort() { return p2pPort; } + + /** + * RPC Port. + */ + private int rpcPort = 10003; + + public int getRpcPort() { return rpcPort; } + /** * Set the RPC users for this node. This configuration block allows arbitrary configuration. * The recommended current structure is: @@ -79,6 +94,7 @@ public class CordformNode implements NodeDefinition { */ public void p2pPort(int p2pPort) { p2pAddress(DEFAULT_HOST + ':' + p2pPort); + this.p2pPort = p2pPort; } /** @@ -110,6 +126,7 @@ public class CordformNode implements NodeDefinition { @Deprecated public void rpcPort(int rpcPort) { rpcAddress(DEFAULT_HOST + ':' + rpcPort); + this.rpcPort = rpcPort; } /** diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java index e429bb0ca6..1869733271 100644 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/RpcSettings.java @@ -8,6 +8,17 @@ public final class RpcSettings { private Config config = ConfigFactory.empty(); + private int port = 10003; + private int adminPort = 10005; + + public int getPort() { + return port; + } + + public int getAdminPort() { + return adminPort; + } + /** * RPC address for the node. */ @@ -15,6 +26,14 @@ public final class RpcSettings { setValue("address", value); } + /** + * RPC Port for the node + */ + public final void port(final int value) { + this.port = value; + setValue("address", "localhost:"+port); + } + /** * RPC admin address for the node (necessary if [useSsl] is false or unset). */ @@ -22,6 +41,11 @@ public final class RpcSettings { setValue("adminAddress", value); } + public final void adminPort(final int value) { + this.adminPort = value; + setValue("adminAddress", "localhost:"+adminPort); + } + /** * Specifies whether the node RPC layer will require SSL from clients. */ @@ -43,7 +67,7 @@ public final class RpcSettings { config = options.addTo("ssl", config); } - final Config addTo(final String key, final Config config) { + public final Config addTo(final String key, final Config config) { if (this.config.isEmpty()) { return config; } diff --git a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java index da3cc22288..1444d4ed8c 100644 --- a/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java +++ b/gradle-plugins/cordform-common/src/main/java/net/corda/cordform/SslOptions.java @@ -43,7 +43,7 @@ public final class SslOptions { setValue("trustStoreFile", value); } - final Config addTo(final String key, final Config config) { + public final Config addTo(final String key, final Config config) { if (this.config.isEmpty()) { return config; } diff --git a/gradle-plugins/cordformation/build.gradle b/gradle-plugins/cordformation/build.gradle index 6c22f78a9e..5ac1e33473 100644 --- a/gradle-plugins/cordformation/build.gradle +++ b/gradle-plugins/cordformation/build.gradle @@ -40,6 +40,8 @@ dependencies { noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" compile project(':cordform-common') + // Docker-compose file generation + compile "org.yaml:snakeyaml:$snake_yaml_version" } task createNodeRunner(type: Jar, dependsOn: [classes]) { diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Baseform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Baseform.kt new file mode 100644 index 0000000000..ea528aaf08 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Baseform.kt @@ -0,0 +1,174 @@ +package net.corda.plugins + +import groovy.lang.Closure +import net.corda.cordform.CordformDefinition +import org.apache.tools.ant.filters.FixCrLfFilter +import org.gradle.api.DefaultTask +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.lang.reflect.InvocationTargetException +import java.net.URLClassLoader +import java.nio.file.Path +import java.nio.file.Paths +import java.util.jar.JarInputStream + +/** + * Creates nodes based on the configuration of this task in the gradle configuration DSL. + * + * See documentation for examples. + */ +@Suppress("unused") +open class Baseform : DefaultTask() { + private companion object { + val nodeJarName = "corda.jar" + private val defaultDirectory: Path = Paths.get("build", "nodes") + } + + /** + * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. + */ + @Suppress("MemberVisibilityCanPrivate") + var definitionClass: String? = null + var directory = defaultDirectory + protected val nodes = mutableListOf() + + /** + * Set the directory to install nodes into. + * + * @param directory The directory the nodes will be installed into. + */ + fun directory(directory: String) { + this.directory = Paths.get(directory) + } + + /** + * Add a node configuration. + * + * @param configureClosure A node configuration that will be deployed. + */ + @Suppress("MemberVisibilityCanPrivate") + fun node(configureClosure: Closure) { + nodes += project.configure(Node(project), configureClosure) as Node + } + + /** + * Add a node configuration + * + * @param configureFunc A node configuration that will be deployed + */ + @Suppress("MemberVisibilityCanPrivate") + fun node(configureFunc: Node.() -> Any?): Node { + val node = Node(project).apply { configureFunc() } + nodes += node + return node + } + + /** + * Returns a node by name. + * + * @param name The name of the node as specified in the node configuration DSL. + * @return A node instance. + */ + private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name } + + /** + * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. + */ + private fun loadCordformDefinition(): CordformDefinition { + val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) + val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath + val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() + return URLClassLoader(urls, CordformDefinition::class.java.classLoader) + .loadClass(definitionClass) + .asSubclass(CordformDefinition::class.java) + .newInstance() + } + + /** + * The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. + */ + private fun loadNetworkBootstrapperClass(): Class<*> { + val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) + val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath + val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() + return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper") + } + + /** + * Installs the corda fat JAR to the root directory, for the network bootstrapper to use. + */ + protected fun installCordaJar() { + val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda") + project.copy { + it.apply { + from(cordaJar) + into(directory) + rename(cordaJar.name, nodeJarName) + fileMode = Cordformation.executableFileMode + } + } + } + + protected fun initializeConfiguration() { + if (definitionClass != null) { + val cd = loadCordformDefinition() + // If the user has specified their own directory (even if it's the same default path) then let them know + // it's not used and should just rely on the one in CordformDefinition + require(directory === defaultDirectory) { + "'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead." + } + directory = cd.nodesDirectory + val cordapps = cd.getMatchingCordapps() + cd.nodeConfigurers.forEach { + val node = node { } + it.accept(node) + node.additionalCordapps.addAll(cordapps) + node.rootDir(directory) + } + cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) } + } else { + nodes.forEach { + it.rootDir(directory) + } + } + } + + protected fun bootstrapNetwork() { + val networkBootstrapperClass = loadNetworkBootstrapperClass() + val networkBootstrapper = networkBootstrapperClass.newInstance() + val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true } + // Call NetworkBootstrapper.bootstrap + try { + val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize() + bootstrapMethod.invoke(networkBootstrapper, rootDir) + } catch (e: InvocationTargetException) { + throw e.cause!! + } + } + + private fun CordformDefinition.getMatchingCordapps(): List { + val cordappJars = project.configuration("cordapp").files + return cordappPackages.map { `package` -> + val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) } + when (cordappsWithPackage.size) { + 0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`") + 1 -> cordappsWithPackage[0] + else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage") + } + } + } + + private fun File.containsPackage(`package`: String): Boolean { + JarInputStream(inputStream()).use { + while (true) { + val name = it.nextJarEntry?.name ?: break + if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) { + return true + } + } + return false + } + } +} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 1d44484eb2..f207dc1818 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -1,18 +1,12 @@ package net.corda.plugins -import groovy.lang.Closure -import net.corda.cordform.CordformDefinition import org.apache.tools.ant.filters.FixCrLfFilter import org.gradle.api.DefaultTask import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME import org.gradle.api.tasks.TaskAction -import java.io.File -import java.lang.reflect.InvocationTargetException -import java.net.URLClassLoader import java.nio.file.Path import java.nio.file.Paths -import java.util.jar.JarInputStream /** * Creates nodes based on the configuration of this task in the gradle configuration DSL. @@ -20,59 +14,12 @@ import java.util.jar.JarInputStream * See documentation for examples. */ @Suppress("unused") -open class Cordform : DefaultTask() { +open class Cordform : Baseform() { private companion object { val nodeJarName = "corda.jar" private val defaultDirectory: Path = Paths.get("build", "nodes") } - /** - * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. - */ - @Suppress("MemberVisibilityCanPrivate") - var definitionClass: String? = null - private var directory = defaultDirectory - private val nodes = mutableListOf() - - /** - * Set the directory to install nodes into. - * - * @param directory The directory the nodes will be installed into. - */ - fun directory(directory: String) { - this.directory = Paths.get(directory) - } - - /** - * Add a node configuration. - * - * @param configureClosure A node configuration that will be deployed. - */ - @Suppress("MemberVisibilityCanPrivate") - fun node(configureClosure: Closure) { - nodes += project.configure(Node(project), configureClosure) as Node - } - - /** - * Add a node configuration - * - * @param configureFunc A node configuration that will be deployed - */ - @Suppress("MemberVisibilityCanPrivate") - fun node(configureFunc: Node.() -> Any?): Node { - val node = Node(project).apply { configureFunc() } - nodes += node - return node - } - - /** - * Returns a node by name. - * - * @param name The name of the node as specified in the node configuration DSL. - * @return A node instance. - */ - private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name } - /** * Installs the run script into the nodes directory. */ @@ -103,29 +50,6 @@ open class Cordform : DefaultTask() { } } - /** - * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. - */ - private fun loadCordformDefinition(): CordformDefinition { - val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) - val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath - val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() - return URLClassLoader(urls, CordformDefinition::class.java.classLoader) - .loadClass(definitionClass) - .asSubclass(CordformDefinition::class.java) - .newInstance() - } - - /** - * The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. - */ - private fun loadNetworkBootstrapperClass(): Class<*> { - val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) - val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath - val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() - return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper") - } - /** * This task action will create and install the nodes based on the node configurations added. */ @@ -139,80 +63,4 @@ open class Cordform : DefaultTask() { bootstrapNetwork() nodes.forEach(Node::build) } - - /** - * Installs the corda fat JAR to the root directory, for the network bootstrapper to use. - */ - private fun installCordaJar() { - val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda") - project.copy { - it.apply { - from(cordaJar) - into(directory) - rename(cordaJar.name, nodeJarName) - fileMode = Cordformation.executableFileMode - } - } - } - - private fun initializeConfiguration() { - if (definitionClass != null) { - val cd = loadCordformDefinition() - // If the user has specified their own directory (even if it's the same default path) then let them know - // it's not used and should just rely on the one in CordformDefinition - require(directory === defaultDirectory) { - "'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead." - } - directory = cd.nodesDirectory - val cordapps = cd.getMatchingCordapps() - cd.nodeConfigurers.forEach { - val node = node { } - it.accept(node) - node.additionalCordapps.addAll(cordapps) - node.rootDir(directory) - } - cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) } - } else { - nodes.forEach { - it.rootDir(directory) - } - } - } - - private fun bootstrapNetwork() { - val networkBootstrapperClass = loadNetworkBootstrapperClass() - val networkBootstrapper = networkBootstrapperClass.newInstance() - val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true } - // Call NetworkBootstrapper.bootstrap - try { - val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize() - bootstrapMethod.invoke(networkBootstrapper, rootDir) - } catch (e: InvocationTargetException) { - throw e.cause!! - } - } - - private fun CordformDefinition.getMatchingCordapps(): List { - val cordappJars = project.configuration("cordapp").files - return cordappPackages.map { `package` -> - val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) } - when (cordappsWithPackage.size) { - 0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`") - 1 -> cordappsWithPackage[0] - else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage") - } - } - } - - private fun File.containsPackage(`package`: String): Boolean { - JarInputStream(inputStream()).use { - while (true) { - val name = it.nextJarEntry?.name ?: break - if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) { - return true - } - } - return false - } - } } diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt new file mode 100644 index 0000000000..4e86e5caac --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Dockerform.kt @@ -0,0 +1,66 @@ +package net.corda.plugins + +import org.apache.tools.ant.filters.FixCrLfFilter +import org.gradle.api.DefaultTask +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME +import org.gradle.api.tasks.TaskAction +import org.yaml.snakeyaml.DumperOptions +import java.nio.file.Path +import java.nio.file.Paths +import org.yaml.snakeyaml.Yaml +import java.nio.charset.StandardCharsets +import java.nio.file.Files + +/** + * Creates docker-compose file and image definitions based on the configuration of this task in the gradle configuration DSL. + * + * See documentation for examples. + */ +@Suppress("unused") +open class Dockerform : Baseform() { + private companion object { + val nodeJarName = "corda.jar" + private val defaultDirectory: Path = Paths.get("build", "docker") + + private val dockerComposeFileVersion = "3" + + private val yamlOptions = DumperOptions().apply { + indent = 2 + defaultFlowStyle = DumperOptions.FlowStyle.BLOCK + } + private val yaml = Yaml(yamlOptions) + } + + private val directoryPath = project.projectDir.toPath().resolve(directory) + + val dockerComposePath = directoryPath.resolve("docker-compose.yml") + + /** + * This task action will create and install the nodes based on the node configurations added. + */ + @TaskAction + fun build() { + project.logger.info("Running Cordform task") + initializeConfiguration() + nodes.forEach(Node::installDockerConfig) + installCordaJar() + bootstrapNetwork() + nodes.forEach(Node::buildDocker) + + + // Transform nodes path the absolute ones + val services = nodes.map { it.containerName to mapOf( + "build" to directoryPath.resolve(it.nodeDir.name).toAbsolutePath().toString(), + "ports" to listOf(it.rpcPort)) }.toMap() + + + val dockerComposeObject = mapOf( + "version" to dockerComposeFileVersion, + "services" to services) + + val dockerComposeContent = yaml.dump(dockerComposeObject) + + Files.write(dockerComposePath, dockerComposeContent.toByteArray(StandardCharsets.UTF_8)) + } +} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index 5fb670cceb..59f9a7f6f5 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -3,8 +3,10 @@ package net.corda.plugins import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigValueFactory +import com.typesafe.config.ConfigObject import groovy.lang.Closure import net.corda.cordform.CordformNode +import net.corda.cordform.RpcSettings import org.gradle.api.Project import java.io.File import java.nio.charset.StandardCharsets @@ -34,6 +36,11 @@ class Node(private val project: Project) : CordformNode() { private set internal lateinit var rootDir: File private set + internal lateinit var containerName: String + private set + + internal var rpcSettings: RpcSettings = RpcSettings() + private set /** * Sets whether this node will use HTTPS communication. @@ -59,7 +66,7 @@ class Node(private val project: Project) : CordformNode() { * Specifies RPC settings for the node. */ fun rpcSettings(configureClosure: Closure) { - val rpcSettings = project.configure(RpcSettings(project), configureClosure) as RpcSettings + rpcSettings = project.configure(RpcSettings(), configureClosure) as RpcSettings config = rpcSettings.addTo("rpcSettings", config) } @@ -81,6 +88,19 @@ class Node(private val project: Project) : CordformNode() { installCordapps() } + internal fun buildDocker() { + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/Dockerfile")) + from(Cordformation.getPluginFile(project, "net/corda/plugins/run-corda.sh")) + into("$nodeDir/") + } + } + installAgentJar() + installBuiltCordapp() + installCordapps() + } + internal fun rootDir(rootDir: Path) { if (name == null) { project.logger.error("Node has a null name - cannot create node") @@ -90,8 +110,9 @@ class Node(private val project: Project) : CordformNode() { // with loading our custom X509EdDSAEngine. val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=") val dirName = organizationName ?: name + containerName = dirName.replace("\\s+".toRegex(), "-").toLowerCase() this.rootDir = rootDir.toFile() - nodeDir = File(this.rootDir, dirName) + nodeDir = File(this.rootDir, dirName.replace("\\s", "")) Files.createDirectories(nodeDir.toPath()) } @@ -156,14 +177,14 @@ class Node(private val project: Project) : CordformNode() { } } - private fun createTempConfigFile(): File { + private fun createTempConfigFile(configObject: ConfigObject): File { val options = ConfigRenderOptions .defaults() .setOriginComments(false) .setComments(false) .setFormatted(true) .setJson(false) - val configFileText = config.root().render(options).split("\n").toList() + val configFileText = configObject.render(options).split("\n").toList() // Need to write a temporary file first to use the project.copy, which resolves directories correctly. val tmpDir = File(project.buildDir, "tmp") Files.createDirectories(tmpDir.toPath()) @@ -178,7 +199,27 @@ class Node(private val project: Project) : CordformNode() { */ internal fun installConfig() { configureProperties() - val tmpConfFile = createTempConfigFile() + val tmpConfFile = createTempConfigFile(config.root()) + appendOptionalConfig(tmpConfFile) + project.copy { + it.apply { + from(tmpConfFile) + into(rootDir) + } + } + } + + /** + * Installs the Dockerized configuration file to the root directory and detokenises it. + */ + internal fun installDockerConfig() { + configureProperties() + val dockerConf = config + .withValue("p2pAddress", ConfigValueFactory.fromAnyRef("$containerName:$p2pPort")) + .withValue("rpcSettings.address", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.port}")) + .withValue("rpcSettings.adminAddress", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.adminPort}")) + .withValue("detectPublicIp", ConfigValueFactory.fromAnyRef(false)) + val tmpConfFile = createTempConfigFile(dockerConf.root()) appendOptionalConfig(tmpConfFile) project.copy { it.apply { diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt deleted file mode 100644 index 2f46f0b2c5..0000000000 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/RpcSettings.kt +++ /dev/null @@ -1,59 +0,0 @@ -package net.corda.plugins - -import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigValueFactory -import groovy.lang.Closure -import org.gradle.api.Project - -class RpcSettings(private val project: Project) { - private var config: Config = ConfigFactory.empty() - - /** - * RPC address for the node. - */ - fun address(value: String) { - config += "address" to value - } - - /** - * RPC admin address for the node (necessary if [useSsl] is false or unset). - */ - fun adminAddress(value: String) { - config += "adminAddress" to value - } - - /** - * Specifies whether the node RPC layer will require SSL from clients. - */ - fun useSsl(value: Boolean) { - config += "useSsl" to value - } - - /** - * Specifies whether the RPC broker is separate from the node. - */ - fun standAloneBroker(value: Boolean) { - config += "standAloneBroker" to value - } - - /** - * Specifies SSL certificates options for the RPC layer. - */ - fun ssl(configureClosure: Closure) { - val sslOptions = project.configure(SslOptions(), configureClosure) as SslOptions - config = sslOptions.addTo("ssl", config) - } - - internal fun addTo(key: String, config: Config): Config { - if (this.config.isEmpty) { - return config - } - return config + (key to this.config.root()) - } -} - -internal operator fun Config.plus(entry: Pair): Config { - - return withValue(entry.first, ConfigValueFactory.fromAnyRef(entry.second)) -} \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/SslOptions.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/SslOptions.kt deleted file mode 100644 index bb8fed288e..0000000000 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/SslOptions.kt +++ /dev/null @@ -1,50 +0,0 @@ -package net.corda.plugins - -import com.typesafe.config.Config -import com.typesafe.config.ConfigFactory - -class SslOptions { - private var config: Config = ConfigFactory.empty() - - /** - * Password for the keystore. - */ - fun keyStorePassword(value: String) { - config += "keyStorePassword" to value - } - - /** - * Password for the truststore. - */ - fun trustStorePassword(value: String) { - config += "trustStorePassword" to value - } - - /** - * Directory under which key stores are to be placed. - */ - fun certificatesDirectory(value: String) { - config += "certificatesDirectory" to value - } - - /** - * Absolute path to SSL keystore. Default: "[certificatesDirectory]/sslkeystore.jks" - */ - fun sslKeystore(value: String) { - config += "sslKeystore" to value - } - - /** - * Absolute path to SSL truststore. Default: "[certificatesDirectory]/truststore.jks" - */ - fun trustStoreFile(value: String) { - config += "trustStoreFile" to value - } - - internal fun addTo(key: String, config: Config): Config { - if (this.config.isEmpty) { - return config - } - return config + (key to this.config.root()) - } -} \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/Dockerfile b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/Dockerfile new file mode 100644 index 0000000000..66ab277852 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/Dockerfile @@ -0,0 +1,44 @@ +# Base image from (http://phusion.github.io/baseimage-docker) +FROM openjdk:8u151-jre-alpine + +ENV CORDA_VERSION=${BUILDTIME_CORDA_VERSION} +ENV JAVA_OPTIONS=${BUILDTIME_JAVA_OPTIONS} + +# Set image labels +LABEL net.corda.version = ${CORDA_VERSION} \ + maintainer = "" \ + vendor = "R3" + +RUN apk upgrade --update && \ + apk add --update --no-cache bash iputils && \ + rm -rf /var/cache/apk/* && \ + # Add user to run the app && \ + addgroup corda && \ + adduser -G corda -D -s /bin/bash corda && \ + # Create /opt/corda directory && \ + mkdir -p /opt/corda/plugins && \ + mkdir -p /opt/corda/logs + +# Copy corda files +ADD --chown=corda:corda corda.jar /opt/corda/corda.jar +ADD --chown=corda:corda node.conf /opt/corda/node.conf +ADD --chown=corda:corda network-parameters /opt/corda/ +ADD --chown=corda:corda cordapps/ /opt/corda/cordapps +ADD --chown=corda:corda additional-node-infos/ /opt/corda/additional-node-infos +ADD --chown=corda:corda certificates/ /opt/corda/certificates +ADD --chown=corda:corda drivers/ /opt/corda/drivers +ADD --chown=corda:corda persistence* /opt/corda/ + +COPY run-corda.sh /run-corda.sh + +RUN chmod +x /run-corda.sh && \ + sync && \ + chown -R corda:corda /opt/corda + +# Working directory for Corda +WORKDIR /opt/corda +ENV HOME=/opt/corda +USER corda + +# Start it +CMD ["/run-corda.sh"] \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/run-corda.sh b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/run-corda.sh new file mode 100644 index 0000000000..c10604f859 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/resources/net/corda/plugins/run-corda.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# If variable not present use default values +: ${CORDA_HOME:=/opt/corda} +: ${JAVA_OPTIONS:=-Xmx512m} + +export CORDA_HOME JAVA_OPTIONS + +cd ${CORDA_HOME} +java $JAVA_OPTIONS -jar ${CORDA_HOME}/corda.jar 2>&1 \ No newline at end of file diff --git a/samples/irs-demo/README.md b/samples/irs-demo/README.md index a22d7acaaf..22b3364c4a 100644 --- a/samples/irs-demo/README.md +++ b/samples/irs-demo/README.md @@ -35,3 +35,26 @@ view it. *Note:* The IRS web UI currently has a bug when changing the clock time where it may show no numbers or apply fixings inconsistently. The issues will be addressed in a future milestone release. Meanwhile, you can take a look at a simpler oracle example here: https://github.com/corda/oracle-example. + +## Running the system test + +The system test utilize docker. Amount of RAM required to run the IRS system test is around 2.5GB, it is important +to allocated appropriate system resources (On MacOS/Windows this may require explicit changes to docker configuration) + +### Gradle + +The system test is designed to exercise the entire stack, including Corda nodes and the web frontend. It uses [Docker](https://www.docker.com), [docker-compose](https://docs.docker.com/compose/), and +[PhantomJS](http://phantomjs.org/). Docker and docker-compose need to be installed and configured to be inside the system path +(default installation). PhantomJs binary have to be put in a known location and have execution permission enabled +(``chmod a+x phantomjs`` on Unix) and the full path to the binary exposed as system property named ``phantomjs.binary.path`` or +a system variable named ``PHANTOMJS_BINARY_PATH``. +Having this done, the system test can be run by running the Gradle task ``:samples:irs-demo:systemTest``. + +### Other + +In order to run the the test by other means that the Gradle task - two more system properties are expected - +``CORDAPP_DOCKER_COMPOSE`` and ``WEB_DOCKER_COMPOSE`` which should specify full path docker-compose file for IRS cordapp + and web frontend respectively. Those can be obtained by running ``:samples:irs-demo:cordapp:prepareDockerNodes`` and +``web:generateDockerCompose`` Gradle tasks. ``systemTest`` task simply executes those two and set proper system properties up. + + \ No newline at end of file diff --git a/samples/irs-demo/build.gradle b/samples/irs-demo/build.gradle index 8bfc3785db..c738eb6c48 100644 --- a/samples/irs-demo/build.gradle +++ b/samples/irs-demo/build.gradle @@ -15,6 +15,7 @@ buildscript { // See https://github.com/spring-gradle-plugins/dependency-management-plugin/blob/master/README.md#changing-the-value-of-a-version-property ext['artemis.version'] = "$artemis_version" ext['hibernate.version'] = "$hibernate_version" +ext['selenium.version'] = "$selenium_version" apply plugin: 'java' apply plugin: 'kotlin' @@ -33,12 +34,29 @@ sourceSets { srcDir file('src/integration-test/kotlin') } } + systemTest { + kotlin { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/system-test/kotlin') + } + } } configurations { integrationTestCompile.extendsFrom testCompile integrationTestRuntime.extendsFrom testRuntime demoArtifacts.extendsFrom testRuntime + systemTestCompile.extendsFrom testCompile +} + +repositories { + maven { + url 'https://dl.bintray.com/palantir/releases' // docker-compose-rule is published on bintray + } + repositories { + maven { url 'https://jitpack.io' } + } } dependencies { @@ -55,6 +73,9 @@ dependencies { testCompile "org.assertj:assertj-core:${assertj_version}" integrationTestCompile project(path: ":samples:irs-demo:web", configuration: "demoArtifacts") + testCompile "com.palantir.docker.compose:docker-compose-rule-junit4:$docker_compose_rule_version" + testCompile "org.seleniumhq.selenium:selenium-java:$selenium_version" + testCompile "com.github.detro:ghostdriver:$ghostdriver_version" } bootRepackage { @@ -66,6 +87,22 @@ task integrationTest(type: Test, dependsOn: []) { classpath = sourceSets.integrationTest.runtimeClasspath } +evaluationDependsOn("cordapp") +evaluationDependsOn("web") + +task systemTest(type: Test, dependsOn: ["cordapp:prepareDockerNodes", "web:generateDockerCompose"]) { + testClassesDirs = sourceSets.systemTest.output.classesDirs + classpath = sourceSets.systemTest.runtimeClasspath + + systemProperty "CORDAPP_DOCKER_COMPOSE", tasks.getByPath("cordapp:prepareDockerNodes").dockerComposePath.toString() + systemProperty "WEB_DOCKER_COMPOSE", tasks.getByPath("web:generateDockerCompose").dockerComposePath.toString() + + def phantomJsPath = System.getProperty("phantomjs.binary.path") ?: System.getenv("PHANTOMJS_BINARY_PATH") + if (phantomJsPath != null) { + systemProperty "phantomjs.binary.path", phantomJsPath + } +} + idea { module { downloadJavadoc = true // defaults to false diff --git a/samples/irs-demo/cordapp/build.gradle b/samples/irs-demo/cordapp/build.gradle index 3a2dab78d5..00cf16c846 100644 --- a/samples/irs-demo/cordapp/build.gradle +++ b/samples/irs-demo/cordapp/build.gradle @@ -43,63 +43,96 @@ dependencies { testCompile "org.assertj:assertj-core:${assertj_version}" } +def rpcUsersList = [ + ['username' : "user", + 'password' : "password", + 'permissions' : [ + "StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester", + "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast", + "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow", + "InvokeRpc.vaultQueryBy", + "InvokeRpc.networkMapSnapshot", + "InvokeRpc.currentNodeTime", + "InvokeRpc.wellKnownPartyFromX500Name" + ]] +] + task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { - ext.rpcUsers = [ - ['username' : "user", - 'password' : "password", - 'permissions' : [ - "StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester", - "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast", - "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow", - "InvokeRpc.vaultQueryBy", - "InvokeRpc.networkMapSnapshot", - "InvokeRpc.currentNodeTime", - "InvokeRpc.wellKnownPartyFromX500Name" - ]] - ] - - directory "./build/nodes" node { name "O=Notary Service,L=Zurich,C=CH" notary = [validating : true] p2pPort 10002 rpcSettings { - address "localhost:10003" - adminAddress "localhost:10023" + port 10003 + adminPort 10023 } - cordapps = ["${project.group}:finance:$corda_release_version"] - rpcUsers = ext.rpcUsers + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList useTestClock true } node { name "O=Bank A,L=London,C=GB" p2pPort 10005 rpcSettings { - address "localhost:10006" - adminAddress "localhost:10026" + port 10006 + adminPort 10026 } - cordapps = ["${project.group}:finance:$corda_release_version"] - rpcUsers = ext.rpcUsers + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList useTestClock true } node { name "O=Bank B,L=New York,C=US" p2pPort 10008 rpcSettings { - address "localhost:10009" - adminAddress "localhost:10029" + port 10009 + adminPort 10029 } cordapps = ["${project.group}:finance:$corda_release_version"] - rpcUsers = ext.rpcUsers + rpcUsers = rpcUsersList useTestClock true } node { name "O=Regulator,L=Moscow,C=RU" p2pPort 10010 - rpcPort 10011 + rpcSettings { + port 10009 + adminPort 10029 + } cordapps = ["${project.group}:finance:$corda_release_version"] - rpcUsers = ext.rpcUsers + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList + useTestClock true + } + +} + +task prepareDockerNodes(type: net.corda.plugins.Dockerform, dependsOn: ['jar']) { + + node { + name "O=Notary Service,L=Zurich,C=CH" + notary = [validating : true] + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList + useTestClock true + } + node { + name "O=Bank A,L=London,C=GB" + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList + useTestClock true + } + node { + name "O=Bank B,L=New York,C=US" + cordapps = ["${project(":finance").group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList + useTestClock true + } + node { + name "O=Regulator,L=Moscow,C=RU" + cordapps = ["${project.group}:finance:$corda_release_version"] + rpcUsers = rpcUsersList useTestClock true } } diff --git a/samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt b/samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt new file mode 100644 index 0000000000..5f0769f669 --- /dev/null +++ b/samples/irs-demo/src/system-test/kotlin/IRSDemoDockerTest.kt @@ -0,0 +1,80 @@ +package net.corda.irs + +import com.palantir.docker.compose.DockerComposeRule +import com.palantir.docker.compose.configuration.DockerComposeFiles +import com.palantir.docker.compose.connection.waiting.HealthChecks +import org.junit.ClassRule +import org.junit.Test +import org.openqa.selenium.By +import org.openqa.selenium.OutputType +import org.openqa.selenium.WebElement +import org.openqa.selenium.phantomjs.PhantomJSDriver +import org.openqa.selenium.support.ui.WebDriverWait +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class IRSDemoDockerTest { + companion object { + + private fun ensureSystemVariable(variable: String) { + if (System.getProperty(variable) == null) { + throw IllegalStateException("System variable $variable not set. Please refer to README file for proper setup instructions.") + } + } + + init { + ensureSystemVariable("CORDAPP_DOCKER_COMPOSE") + ensureSystemVariable("WEB_DOCKER_COMPOSE") + ensureSystemVariable("phantomjs.binary.path") + } + + @ClassRule + @JvmField + var docker = DockerComposeRule.builder() + .files(DockerComposeFiles.from( + System.getProperty("CORDAPP_DOCKER_COMPOSE"), + System.getProperty("WEB_DOCKER_COMPOSE"))) + .waitingForService("web-a", HealthChecks.toRespondOverHttp(8080, { port -> port.inFormat("http://\$HOST:\$EXTERNAL_PORT") })) + .build() + } + + @Test + fun `runs IRS demo selenium phantomjs`() { + + + val driver = PhantomJSDriver() + + val webAPort = docker.containers() + .container("web-a") + .port(8080) + + + driver.get("http://${webAPort.ip}:${webAPort.externalPort}"); + + //no deals on fresh interface + val dealRows = driver.findElementsByCssSelector("table#deal-list tbody tr") + assertTrue(dealRows.isEmpty()) + + // Click Angular link and wait for form to appear + val findElementByLinkText = driver.findElementByLinkText("Create Deal") + findElementByLinkText.click() + + val driverWait = WebDriverWait(driver, 120) + + val form = driverWait.until({ + it?.findElement(By.cssSelector("form")) + }) + + form.submit() + + //Wait for deals to appear in a rows table + val dealsList = driverWait.until({ + it?.findElement(By.cssSelector("table#deal-list tbody tr")) + }) + + assertNotNull(dealsList) + } +} diff --git a/samples/irs-demo/web/build.gradle b/samples/irs-demo/web/build.gradle index bad8f8da07..ce592344f6 100644 --- a/samples/irs-demo/web/build.gradle +++ b/samples/irs-demo/web/build.gradle @@ -1,17 +1,28 @@ +import java.nio.charset.StandardCharsets +import java.nio.file.Files + + buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") - classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}") - } + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}") + classpath 'com.bmuschko:gradle-docker-plugin:3.2.1' + classpath "org.yaml:snakeyaml:1.19" + } } +import org.yaml.snakeyaml.DumperOptions + + plugins { - id 'com.craigburke.client-dependencies' version '1.4.0' + id 'com.craigburke.client-dependencies' version '1.4.0' } +group = "${parent.group}.irs-demo" + clientDependencies { registry 'realBower', type:'bower', url:'https://registry.bower.io' realBower { @@ -69,6 +80,8 @@ jar { dependsOn clientInstall } +def docker_dir = file("$project.buildDir/docker") + task deployWebapps(type: Copy, dependsOn: ['jar', 'bootRepackage']) { ext.webappDir = file("build/webapps") @@ -89,4 +102,58 @@ task demoJar(type: Jar) { artifacts { demoArtifacts demoJar +} + +task createDockerfile(type: com.bmuschko.gradle.docker.tasks.image.Dockerfile, dependsOn: [bootRepackage]) { + destFile = file("$docker_dir/Dockerfile") + + from 'azul/zulu-openjdk-alpine:8u152' + copyFile jar.archiveName, "/opt/irs/web/" + workingDir "/opt/irs/web/" + defaultCommand "sh", "-c", "java -Dcorda.host=\$CORDA_HOST -jar ${jar.archiveName}" +} + +task prepareDockerDir(type: Copy, dependsOn: [bootRepackage, createDockerfile]) { + from jar + into docker_dir +} + +task generateDockerCompose(dependsOn: [prepareDockerDir]) { + + def outFile = new File(project.buildDir, "docker-compose.yml") + + ext['dockerComposePath'] = outFile + + doLast { + def dockerComposeObject = [ + "version": "3", + "services": [ + "web-a": [ + "build": "$docker_dir".toString(), + "environment": [ + "CORDA_HOST": "bank-a:10003" + ], + "ports": ["8080"] + ], + "web-b": [ + "build": "$docker_dir".toString(), + "environment": [ + "CORDA_HOST": "bank-b:10003" + ], + "ports": ["8080"] + ] + ] + ] + + + def options = new org.yaml.snakeyaml.DumperOptions() + options.indent = 2 + options.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK + + def dockerComposeContent = new org.yaml.snakeyaml.Yaml(options).dump(dockerComposeObject) + + Files.write(outFile.toPath(), dockerComposeContent.getBytes(StandardCharsets.UTF_8)) + } + + outputs.file(outFile) } \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt index b6e81f928f..77d5c31368 100644 --- a/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt +++ b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt @@ -6,6 +6,8 @@ import net.corda.client.rpc.CordaRPCClient import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.NetworkHostAndPort import net.corda.finance.plugin.registerFinanceJSONMappers +import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException +import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.SpringApplication @@ -29,10 +31,22 @@ class IrsDemoWebApplication { @Value("\${corda.password}") lateinit var cordaPassword:String - @Bean fun rpcClient(): CordaRPCOps { - return CordaRPCClient(NetworkHostAndPort.parse(cordaHost)).start(cordaUser, cordaPassword).proxy + log.info("Connecting to Corda on $cordaHost using username $cordaUser and password $cordaPassword") + // TODO remove this when CordaRPC gets proper connection retry, please + var maxRetries = 100; + do { + try { + return CordaRPCClient(NetworkHostAndPort.parse(cordaHost)).start(cordaUser, cordaPassword).proxy + } catch (ex: ActiveMQNotConnectedException) { + if (maxRetries-- > 0) { + Thread.sleep(1000) + } else { + throw ex + } + } + } while (true) } @Bean @@ -44,6 +58,8 @@ class IrsDemoWebApplication { // running as standalone java app companion object { + private val log = LoggerFactory.getLogger(this::class.java) + @JvmStatic fun main(args: Array) { SpringApplication.run(IrsDemoWebApplication::class.java, *args) } diff --git a/samples/irs-demo/web/src/main/resources/application-NotaryService.properties b/samples/irs-demo/web/src/main/resources/application-NotaryService.properties index e3de4502b7..e424a238ad 100644 --- a/samples/irs-demo/web/src/main/resources/application-NotaryService.properties +++ b/samples/irs-demo/web/src/main/resources/application-NotaryService.properties @@ -1,2 +1,2 @@ corda.host=localhost:10003 -server.port=10004 \ No newline at end of file +server.port=10004 diff --git a/samples/irs-demo/web/src/main/resources/static/js/Deal.js b/samples/irs-demo/web/src/main/resources/static/js/Deal.js index 712ea2d8a2..206870087b 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/Deal.js +++ b/samples/irs-demo/web/src/main/resources/static/js/Deal.js @@ -1,36 +1,32 @@ "use strict"; -define(['viewmodel/FixedRate'], (fixedRateViewModel) => { - let calculationModel = { +define(['viewmodel/FixedRate'], function (fixedRateViewModel) { + var calculationModel = { expression: "( fixedLeg.notional.quantity * (fixedLeg.fixedRate.ratioUnit.value)) - (floatingLeg.notional.quantity * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))", - floatingLegPaymentSchedule: { - - }, - fixedLegPaymentSchedule: { - - } + floatingLegPaymentSchedule: {}, + fixedLegPaymentSchedule: {} }; - let indexLookup = { + var indexLookup = { "GBP": "ICE LIBOR", "USD": "ICE LIBOR", "EUR": "EURIBOR" }; - let calendarLookup = { + var calendarLookup = { "GBP": "London", "USD": "NewYork", "EUR": "London" }; - let Deal = function(dealViewModel) { - let now = new Date(); - let tradeId = `T${now.getUTCFullYear()}-${now.getUTCMonth()}-${now.getUTCDate()}.${now.getUTCHours()}:${now.getUTCMinutes()}:${now.getUTCSeconds()}:${now.getUTCMilliseconds()}`; + var Deal = function Deal(dealViewModel) { + var now = new Date(); + var tradeId = "T" + now.getUTCFullYear() + "-" + now.getUTCMonth() + "-" + now.getUTCDate() + "." + now.getUTCHours() + ":" + now.getUTCMinutes() + ":" + now.getUTCSeconds() + ":" + now.getUTCMilliseconds(); - this.toJson = () => { - let fixedLeg = {}; - let floatingLeg = {}; - let common = {}; + this.toJson = function () { + var fixedLeg = {}; + var floatingLeg = {}; + var common = {}; _.assign(fixedLeg, dealViewModel.fixedLeg); _.assign(floatingLeg, dealViewModel.floatingLeg); _.assign(common, dealViewModel.common); @@ -65,7 +61,7 @@ define(['viewmodel/FixedRate'], (fixedRateViewModel) => { delete common.effectiveDate; delete common.terminationDate; - let json = { + var json = { fixedLeg: fixedLeg, floatingLeg: floatingLeg, calculation: calculationModel, @@ -77,4 +73,4 @@ define(['viewmodel/FixedRate'], (fixedRateViewModel) => { }; }; return Deal; -}); +}); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/app.js b/samples/irs-demo/web/src/main/resources/static/js/app.js index fef33d756e..0e5256d66c 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/app.js +++ b/samples/irs-demo/web/src/main/resources/static/js/app.js @@ -2,23 +2,17 @@ function formatDateForNode(date) { // Produces yyyy-dd-mm. JS is missing proper date formatting libs - let day = ("0" + (date.getDate())).slice(-2); - let month = ("0" + (date.getMonth() + 1)).slice(-2); - return `${date.getFullYear()}-${month}-${day}`; + var day = ("0" + date.getDate()).slice(-2); + var month = ("0" + (date.getMonth() + 1)).slice(-2); + return date.getFullYear() + "-" + month + "-" + day; } function formatDateForAngular(dateStr) { - let parts = dateStr.split("-"); + var parts = dateStr.split("-"); return new Date(parts[0], parts[1], parts[2]); } -define([ - 'angular', - 'angularRoute', - 'jquery', - 'fcsaNumber', - 'semantic' -], (angular, angularRoute, $, fcsaNumber, semantic) => { +define(['angular', 'angularRoute', 'jquery', 'fcsaNumber', 'semantic'], function (angular, angularRoute, $, fcsaNumber, semantic) { angular.module('irsViewer', ['ngRoute', 'fcsa-number']); requirejs(['routes']); }); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js b/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js index 15b7af2381..c021f6b781 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js +++ b/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js @@ -1,34 +1,27 @@ 'use strict'; -define([ - 'angular', - 'maskedInput', - 'utils/semantic', - 'utils/dayCountBasisLookup', - 'services/NodeApi', - 'Deal', - 'services/HttpErrorHandler' -], (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) => { +define(['angular', 'maskedInput', 'utils/semantic', 'utils/dayCountBasisLookup', 'services/NodeApi', 'Deal', 'services/HttpErrorHandler'], function (angular, maskedInput, semantic, dayCountBasisLookup, nodeApi, Deal) { angular.module('irsViewer').controller('CreateDealController', function CreateDealController($http, $scope, $location, nodeService, httpErrorHandler) { semantic.init($scope, nodeService.isLoading); - let handleHttpFail = httpErrorHandler.createErrorHandler($scope); + var handleHttpFail = httpErrorHandler.createErrorHandler($scope); $scope.dayCountBasisLookup = dayCountBasisLookup; $scope.deal = nodeService.newDeal(); - $scope.createDeal = () => { - nodeService.createDeal(new Deal($scope.deal)) - .then((tradeId) => $location.path('#/deal/' + tradeId), (resp) => { + $scope.createDeal = function () { + nodeService.createDeal(new Deal($scope.deal)).then(function (tradeId) { + return $location.path('#/deal/' + tradeId); + }, function (resp) { $scope.formError = resp.data; }, handleHttpFail); }; - $('input.percent').mask("9.999999", {placeholder: "", autoclear: false}); - $('#swapirscolumns').click(() => { - let first = $('#irscolumns .irscolumn:eq( 0 )'); - let last = $('#irscolumns .irscolumn:eq( 1 )'); + $('input.percent').mask("9.999999", { placeholder: "", autoclear: false }); + $('#swapirscolumns').click(function () { + var first = $('#irscolumns .irscolumn:eq( 0 )'); + var last = $('#irscolumns .irscolumn:eq( 1 )'); first.before(last); - let swapPayers = () => { - let tmp = $scope.deal.floatingLeg.floatingRatePayer; + var swapPayers = function swapPayers() { + var tmp = $scope.deal.floatingLeg.floatingRatePayer; $scope.deal.floatingLeg.floatingRatePayer = $scope.deal.fixedLeg.fixedRatePayer; $scope.deal.fixedLeg.fixedRatePayer = tmp; }; diff --git a/samples/irs-demo/web/src/main/resources/static/js/controllers/Deal.js b/samples/irs-demo/web/src/main/resources/static/js/controllers/Deal.js index 2ccaca2e9b..48d4f4432d 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/controllers/Deal.js +++ b/samples/irs-demo/web/src/main/resources/static/js/controllers/Deal.js @@ -1,19 +1,21 @@ 'use strict'; -define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], (angular, semantic) => { +define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], function (angular, semantic) { angular.module('irsViewer').controller('DealController', function DealController($http, $scope, $routeParams, nodeService, httpErrorHandler) { semantic.init($scope, nodeService.isLoading); - let handleHttpFail = httpErrorHandler.createErrorHandler($scope); - let decorateDeal = (deal) => { - let paymentSchedule = deal.calculation.floatingLegPaymentSchedule; - Object.keys(paymentSchedule).map((key, index) => { - const sign = paymentSchedule[key].rate.positive ? 1 : -1; - paymentSchedule[key].ratePercent = paymentSchedule[key].rate.ratioUnit ? (paymentSchedule[key].rate.ratioUnit.value * 100 * sign).toFixed(5) + "%": ""; + var handleHttpFail = httpErrorHandler.createErrorHandler($scope); + var decorateDeal = function decorateDeal(deal) { + var paymentSchedule = deal.calculation.floatingLegPaymentSchedule; + Object.keys(paymentSchedule).map(function (key, index) { + var sign = paymentSchedule[key].rate.positive ? 1 : -1; + paymentSchedule[key].ratePercent = paymentSchedule[key].rate.ratioUnit ? (paymentSchedule[key].rate.ratioUnit.value * 100 * sign).toFixed(5) + "%" : ""; }); return deal; }; - nodeService.getDeal($routeParams.dealId).then((deal) => $scope.deal = decorateDeal(deal), handleHttpFail); + nodeService.getDeal($routeParams.dealId).then(function (deal) { + return $scope.deal = decorateDeal(deal); + }, handleHttpFail); }); }); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/controllers/Home.js b/samples/irs-demo/web/src/main/resources/static/js/controllers/Home.js index b92d31f1bc..476a2bff9e 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/controllers/Home.js +++ b/samples/irs-demo/web/src/main/resources/static/js/controllers/Home.js @@ -1,23 +1,23 @@ 'use strict'; -define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], (angular, semantic) => { +define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHandler'], function (angular, semantic) { angular.module('irsViewer').controller('HomeController', function HomeController($http, $scope, nodeService, httpErrorHandler) { semantic.addLoadingModal($scope, nodeService.isLoading); - let handleHttpFail = httpErrorHandler.createErrorHandler($scope); + var handleHttpFail = httpErrorHandler.createErrorHandler($scope); $scope.infoMsg = ""; $scope.errorText = ""; $scope.date = { "year": "...", "month": "...", "day": "..." }; - $scope.updateDate = (type) => { - nodeService.updateDate(type).then((newDate) => { - $scope.date = newDate + $scope.updateDate = function (type) { + nodeService.updateDate(type).then(function (newDate) { + $scope.date = newDate; }, handleHttpFail); }; /* Extract the common name from an X500 name */ - $scope.renderX500Name = (x500Name) => { - var name = x500Name - x500Name.split(',').forEach(function(element) { + $scope.renderX500Name = function (x500Name) { + var name = x500Name; + x500Name.split(',').forEach(function (element) { var keyValue = element.split('='); if (keyValue[0].toUpperCase() == 'CN') { name = keyValue[1]; @@ -26,7 +26,11 @@ define(['angular', 'utils/semantic', 'services/NodeApi', 'services/HttpErrorHand return name; }; - nodeService.getDate().then((date) => $scope.date = date, handleHttpFail); - nodeService.getDeals().then((deals) => $scope.deals = deals, handleHttpFail); + nodeService.getDate().then(function (date) { + return $scope.date = date; + }, handleHttpFail); + nodeService.getDeals().then(function (deals) { + return $scope.deals = deals; + }, handleHttpFail); }); -}); +}); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/require-config.js b/samples/irs-demo/web/src/main/resources/static/js/require-config.js index 243e207344..85debee88f 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/require-config.js +++ b/samples/irs-demo/web/src/main/resources/static/js/require-config.js @@ -11,18 +11,14 @@ require.config({ maskedInput: 'bower_components/jquery.maskedinput/jquery.maskedinput' }, shim: { - 'angular' : {'exports' : 'angular'}, + 'angular': { 'exports': 'angular' }, 'angularRoute': ['angular'], 'fcsaNumber': ['angular'], 'semantic': ['jquery'], 'maskedInput': ['jquery'] }, - priority: [ - "angular" - ], - baseUrl: 'js', + priority: ["angular"], + baseUrl: 'js' }); -require(['angular', 'app'], (angular, app) => { - -}); \ No newline at end of file +require(['angular', 'app'], function (angular, app) {}); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/routes.js b/samples/irs-demo/web/src/main/resources/static/js/routes.js index 9f99accfbc..e66b100776 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/routes.js +++ b/samples/irs-demo/web/src/main/resources/static/js/routes.js @@ -1,32 +1,22 @@ 'use strict'; -define([ - 'angular', - 'controllers/Home', - 'controllers/Deal', - 'controllers/CreateDeal' -], (angular) => { - angular.module('irsViewer').config(($routeProvider, $locationProvider) => { - $routeProvider - .when('/', { - controller: 'HomeController', - templateUrl: 'view/home.html' - }) - .when('/deal/:dealId', { - controller: 'DealController', - templateUrl: 'view/deal.html' - }) - .when('/party/:partyId', { - templateUrl: 'view/party.html' - }) - .when('/create-deal', { - controller: 'CreateDealController', - templateUrl: 'view/create-deal.html' - }) - .otherwise({redirectTo: '/'}); +define(['angular', 'controllers/Home', 'controllers/Deal', 'controllers/CreateDeal'], function (angular) { + angular.module('irsViewer').config(function ($routeProvider, $locationProvider) { + $routeProvider.when('/', { + controller: 'HomeController', + templateUrl: 'view/home.html' + }).when('/deal/:dealId', { + controller: 'DealController', + templateUrl: 'view/deal.html' + }).when('/party/:partyId', { + templateUrl: 'view/party.html' + }).when('/create-deal', { + controller: 'CreateDealController', + templateUrl: 'view/create-deal.html' + }).otherwise({ redirectTo: '/' }); }); - angular.element().ready(function() { + angular.element().ready(function () { // bootstrap the app manually angular.bootstrap(document, ['irsViewer']); }); diff --git a/samples/irs-demo/web/src/main/resources/static/js/services/HttpErrorHandler.js b/samples/irs-demo/web/src/main/resources/static/js/services/HttpErrorHandler.js index 0ac757b5f6..e25a076df6 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/services/HttpErrorHandler.js +++ b/samples/irs-demo/web/src/main/resources/static/js/services/HttpErrorHandler.js @@ -1,11 +1,11 @@ 'use strict'; -define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _) => { - angular.module('irsViewer').factory('httpErrorHandler', () => { +define(['angular', 'lodash', 'viewmodel/Deal'], function (angular, _) { + angular.module('irsViewer').factory('httpErrorHandler', function () { return { - createErrorHandler: (scope) => { - return (resp) => { - if(resp.status == -1) { + createErrorHandler: function createErrorHandler(scope) { + return function (resp) { + if (resp.status == -1) { scope.httpError = "Could not connect to node web server"; } else { scope.httpError = resp.data; diff --git a/samples/irs-demo/web/src/main/resources/static/js/services/NodeApi.js b/samples/irs-demo/web/src/main/resources/static/js/services/NodeApi.js index 1cfdf401c5..2a068ea7f2 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/services/NodeApi.js +++ b/samples/irs-demo/web/src/main/resources/static/js/services/NodeApi.js @@ -1,44 +1,48 @@ 'use strict'; -define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => { - angular.module('irsViewer').factory('nodeService', ($http) => { - return new (function() { - let date = new Date(2016, 0, 1, 0, 0, 0); - let curLoading = {}; - let serverAddr = ''; // Leave empty to target the same host this page is served from +define(['angular', 'lodash', 'viewmodel/Deal'], function (angular, _, dealViewModel) { + angular.module('irsViewer').factory('nodeService', function ($http) { + return new function () { + var _this = this; - let load = (type, promise) => { + var date = new Date(2016, 0, 1, 0, 0, 0); + var curLoading = {}; + var serverAddr = ''; // Leave empty to target the same host this page is served from + + var load = function load(type, promise) { curLoading[type] = true; - return promise.then((arg) => { + return promise.then(function (arg) { curLoading[type] = false; return arg; - }, (arg) => { + }, function (arg) { curLoading[type] = false; throw arg; }); }; - let endpoint = (target) => serverAddr + target; + var endpoint = function endpoint(target) { + return serverAddr + target; + }; - let changeDateOnNode = (newDate) => { - const dateStr = formatDateForNode(newDate); - return load('date', $http.put(endpoint('/api/irs/demodate'), "\"" + dateStr + "\"")).then((resp) => { + var changeDateOnNode = function changeDateOnNode(newDate) { + var dateStr = formatDateForNode(newDate); + return load('date', $http.put(endpoint('/api/irs/demodate'), "\"" + dateStr + "\"")).then(function (resp) { date = newDate; - return this.getDateModel(date); + return _this.getDateModel(date); }); }; - this.getDate = () => { - return load('date', $http.get(endpoint('/api/irs/demodate'))).then((resp) => { - const dateParts = resp.data; + this.getDate = function () { + return load('date', $http.get(endpoint('/api/irs/demodate'))).then(function (resp) { + var dateParts = resp.data; date = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); // JS uses 0 based months - return this.getDateModel(date); + return _this.getDateModel(date); }); }; - this.updateDate = (type) => { - let newDate = date; - switch(type) { + this.updateDate = function (type) { + var newDate = date; + switch (type) { case "year": newDate.setFullYear(date.getFullYear() + 1); break; @@ -55,22 +59,22 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => { return changeDateOnNode(newDate); }; - this.getDeals = () => { - return load('deals', $http.get(endpoint('/api/irs/deals'))).then((resp) => { + this.getDeals = function () { + return load('deals', $http.get(endpoint('/api/irs/deals'))).then(function (resp) { return resp.data.reverse(); }); }; - this.getDeal = (dealId) => { - return load('deal' + dealId, $http.get(endpoint('/api/irs/deals/' + dealId))).then((resp) => { + this.getDeal = function (dealId) { + return load('deal' + dealId, $http.get(endpoint('/api/irs/deals/' + dealId))).then(function (resp) { // Do some data modification to simplify the model - let deal = resp.data; + var deal = resp.data; deal.fixedLeg.fixedRate.value = (deal.fixedLeg.fixedRate.ratioUnit.value * 100).toString().slice(0, 6); return deal; }); }; - this.getDateModel = (date) => { + this.getDateModel = function (date) { return { "year": date.getFullYear(), "month": date.getMonth() + 1, // JS uses 0 based months @@ -78,24 +82,23 @@ define(['angular', 'lodash', 'viewmodel/Deal'], (angular, _, dealViewModel) => { }; }; - this.isLoading = () => { - return _.reduce(Object.keys(curLoading), (last, key) => { - return (last || curLoading[key]); + this.isLoading = function () { + return _.reduce(Object.keys(curLoading), function (last, key) { + return last || curLoading[key]; }, false); }; - this.newDeal = () => { + this.newDeal = function () { return dealViewModel; }; - this.createDeal = (deal) => { - return load('create-deal', $http.post(endpoint('/api/irs/deals'), deal.toJson())) - .then((resp) => { + this.createDeal = function (deal) { + return load('create-deal', $http.post(endpoint('/api/irs/deals'), deal.toJson())).then(function (resp) { return deal.tradeId; - }, (resp) => { + }, function (resp) { throw resp; - }) - } - }); + }); + }; + }(); }); }); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/utils/dayCountBasisLookup.js b/samples/irs-demo/web/src/main/resources/static/js/utils/dayCountBasisLookup.js index be80477ac2..18a3f04625 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/utils/dayCountBasisLookup.js +++ b/samples/irs-demo/web/src/main/resources/static/js/utils/dayCountBasisLookup.js @@ -1,6 +1,6 @@ 'use strict'; -define([], () => { +define([], function () { return { "30/360": { "day": "D30", @@ -29,6 +29,6 @@ define([], () => { "ACT/ACT ICMA": { "day": "DActual", "year": "YICMA" - }, + } }; }); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/utils/semantic.js b/samples/irs-demo/web/src/main/resources/static/js/utils/semantic.js index c4874f922b..6b1e9746d1 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/utils/semantic.js +++ b/samples/irs-demo/web/src/main/resources/static/js/utils/semantic.js @@ -1,17 +1,17 @@ 'use strict'; -define(['jquery', 'semantic'], ($, semantic) => { +define(['jquery', 'semantic'], function ($, semantic) { return { - init: function($scope, loadingFunc) { + init: function init($scope, loadingFunc) { $('.ui.accordion').accordion(); $('.ui.dropdown').dropdown(); $('.ui.sticky').sticky(); this.addLoadingModal($scope, loadingFunc); }, - addLoadingModal: ($scope, loadingFunc) => { - $scope.$watch(loadingFunc, (newVal) => { - if(newVal === true) { + addLoadingModal: function addLoadingModal($scope, loadingFunc) { + $scope.$watch(loadingFunc, function (newVal) { + if (newVal === true) { $('#loading').modal('setting', 'closable', false).modal('show'); } else { $('#loading').modal('hide'); diff --git a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Common.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Common.js index c6e58ffbc2..2744d3b926 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Common.js +++ b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Common.js @@ -1,6 +1,6 @@ 'use strict'; -define([], () => { +define([], function () { return { baseCurrency: "USD", effectiveDate: new Date(2016, 2, 11), @@ -31,7 +31,7 @@ define([], () => { }, addressForTransfers: "", exposure: {}, - localBusinessDay: [ "London" , "NewYork" ], + localBusinessDay: ["London", "NewYork"], dailyInterestAmount: "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360", hashLegalDocs: "put hash here" }; diff --git a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Deal.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Deal.js index 54d4e99783..61d61ef326 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Deal.js +++ b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Deal.js @@ -1,6 +1,6 @@ 'use strict'; -define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], (fixedLeg, floatingLeg, common) => { +define(['viewmodel/FixedLeg', 'viewmodel/FloatingLeg', 'viewmodel/Common'], function (fixedLeg, floatingLeg, common) { return { fixedLeg: fixedLeg, floatingLeg: floatingLeg, diff --git a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedLeg.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedLeg.js index 30cfd34650..b143542fd0 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedLeg.js +++ b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedLeg.js @@ -1,6 +1,6 @@ 'use strict'; -define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => { +define(['utils/dayCountBasisLookup'], function (dayCountBasisLookup) { return { fixedRatePayer: "O=Bank A,L=London,C=GB", notional: 2500000000, @@ -15,4 +15,4 @@ define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => { paymentDelay: "0", interestPeriodAdjustment: "Adjusted" }; -}); +}); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FloatingLeg.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FloatingLeg.js index a2fb24778d..38d0845c5f 100644 --- a/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FloatingLeg.js +++ b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FloatingLeg.js @@ -1,6 +1,6 @@ 'use strict'; -define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => { +define(['utils/dayCountBasisLookup'], function (dayCountBasisLookup) { return { floatingRatePayer: "O=Bank B,L=New York,C=US", notional: 2500000000, @@ -20,7 +20,7 @@ define(['utils/dayCountBasisLookup'], (dayCountBasisLookup) => { fixingsPerPayment: "Quarterly", indexSource: "Rates Service Provider", indexTenor: { - name: "3M" + name: "3M" } }; -}); +}); \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/view/home.html b/samples/irs-demo/web/src/main/resources/static/view/home.html index 65610fd1f4..7dc3f8af57 100644 --- a/samples/irs-demo/web/src/main/resources/static/view/home.html +++ b/samples/irs-demo/web/src/main/resources/static/view/home.html @@ -34,7 +34,7 @@ Recent deals - +
@@ -45,7 +45,7 @@ - + From 429da85650cdf2f23c1b261f094c2741e98a72b2 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 5 Feb 2018 12:07:02 +0000 Subject: [PATCH 018/114] CORDA-946 - Fixes to fingerprinting breaks backward compatibility (#2453) * CORDA-946 - Fixes to fingerprinting breaks backward compatibility Demonstrated using the network map parameters signed form as that's where the problem was first seen * Review Comments --- .../serialization/amqp/EvolutionSerializer.kt | 22 +++-- .../internal/serialization/amqp/Schema.kt | 2 +- .../carpenter/AMQPSchemaExtensions.kt | 13 +-- .../serialization/amqp/EvolvabilityTests.kt | 76 ++++++++++++++++++ .../amqp/networkParams.r3corda.6a6b6f256 | Bin 0 -> 3066 bytes 5 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/networkParams.r3corda.6a6b6f256 diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt index 103e0d082e..78ca924deb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt @@ -177,12 +177,24 @@ class EvolutionSerializerGetter : EvolutionSerializerGetterBase() { override fun getEvolutionSerializer(factory: SerializerFactory, typeNotation: TypeNotation, newSerializer: AMQPSerializer, - schemas: SerializationSchemas): AMQPSerializer = - factory.getSerializersByDescriptor().computeIfAbsent(typeNotation.descriptor.name!!) { - when (typeNotation) { - is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory) - is RestrictedType -> EnumEvolutionSerializer.make(typeNotation, newSerializer, factory, schemas) + schemas: SerializationSchemas): AMQPSerializer { + return factory.getSerializersByDescriptor().computeIfAbsent(typeNotation.descriptor.name!!) { + when (typeNotation) { + is CompositeType -> EvolutionSerializer.make(typeNotation, newSerializer as ObjectSerializer, factory) + is RestrictedType -> { + // The fingerprint of a generic collection can be changed through bug fixes to the + // fingerprinting function making it appear as if the class has altered whereas it hasn't. + // Given we don't support the evolution of these generic containers, if it appears + // one has been changed, simply return the original serializer and associate it with + // both the new and old fingerprint + if (newSerializer is CollectionSerializer || newSerializer is MapSerializer) { + newSerializer + } else { + EnumEvolutionSerializer.make(typeNotation, newSerializer, factory, schemas) + } } } + } + } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index 372cfed9be..e309882d7b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -460,6 +460,6 @@ private fun fingerprintForObject( .putUnencodedChars(prop.getter.name) .putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH) } - interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, debugIndent+4) } + interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, debugIndent+1) } return hasher } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt index 1608a22e83..2c7dea9605 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt @@ -120,16 +120,17 @@ val typeStrToType: Map, Class> = mapOf( Pair("byte", false) to Byte::class.javaObjectType ) +fun String.stripGenerics() : String = if(this.endsWith('>')) { + this.substring(0, this.indexOf('<')) +} else this + fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type, mandatory)] ?: when (type) { "string" -> String::class.java "binary" -> ByteArray::class.java - "*" -> if (requires.isEmpty()) Any::class.java else classloader.loadClass(requires[0]) - else -> { - classloader.loadClass( - if (type.endsWith("?>")) { - type.substring(0, type.indexOf('<')) - } else type) + "*" -> if (requires.isEmpty()) Any::class.java else { + classloader.loadClass(requires[0].stripGenerics()) } + else -> classloader.loadClass(type.stripGenerics()) } fun AMQPField.validateType(classloader: ClassLoader) = when (type) { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index 164feb2bab..b18abd9f82 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -1,12 +1,21 @@ package net.corda.nodeapi.internal.serialization.amqp +import net.corda.core.crypto.Crypto.generateKeyPair +import net.corda.core.crypto.SignedData +import net.corda.core.crypto.sign import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.SerializedBytes +import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.testing.common.internal.ProjectStructure.projectRootDir +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.core.TestIdentity +import org.junit.Ignore import org.junit.Test import java.io.File import java.io.NotSerializableException import java.net.URI +import java.time.Instant import kotlin.test.assertEquals // To regenerate any of the binary test files do the following @@ -462,4 +471,71 @@ class EvolvabilityTests { assertEquals(f, db3.f) assertEquals(-1, db3.g) } + + // + // This test uses a NetworkParameters signed set of bytes generated by R3 Corda and + // is here to ensure we can still read them. This test exists because of the break in + // being able to deserialize an object serialized prior to some fixes to the fingerprinter. + // + // The file itself was generated from R3 Corda at commit + // 6a6b6f256 Skip cache invalidation during init() - caches are still null. + // + // To regenerate the file un-ignore the test below this one (regenerate broken network parameters), + // to regenerate at a specific version add that test to a checkout at the desired sha then take + // the resulting file and add to the repo, changing the filename as appropriate + // + @Test + fun readBrokenNetworkParameters(){ + val sf = testDefaultFactory() + sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(sf)) + sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer) + + // + // filename breakdown + // networkParams - because this is a serialised set of network parameters + // r3corda - generated by R3 Corda instead of Corda + // 6a6b6f256 - Commit sha of the build that generated the file we're testing against + // + val resource = "networkParams.r3corda.6a6b6f256" + + val path = EvolvabilityTests::class.java.getResource(resource) + val f = File(path.toURI()) + val sc2 = f.readBytes() + val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes>(sc2)) + val networkParams = DeserializationInput(sf).deserialize(deserializedC.raw) + + assertEquals(1000, networkParams.maxMessageSize) + assertEquals(1000, networkParams.maxTransactionSize) + assertEquals(3, networkParams.minimumPlatformVersion) + assertEquals(1, networkParams.notaries.size) + assertEquals(TestIdentity(DUMMY_NOTARY_NAME, 20).party, networkParams.notaries.firstOrNull()?.identity) + } + + // + // This test created a serialized and signed set of Network Parameters to test whether we + // can still deserialize them + // + @Test + @Ignore("This test simply regenerates the test file used for readBrokenNetworkParameters") + fun `regenerate broken network parameters`() { + // note: 6a6b6f256 is the sha that generates the file + val resource = "networkParams.." + val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party + val networkParameters = NetworkParameters( + 3, listOf(NotaryInfo(DUMMY_NOTARY, false)),1000, 1000, Instant.EPOCH, 1 ) + + val sf = testDefaultFactory() + sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(sf)) + sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer) + + val testOutput = TestSerializationOutput(true, sf) + val serialized = testOutput.serialize(networkParameters) + val keyPair = generateKeyPair() + val sig = keyPair.private.sign(serialized.bytes, keyPair.public) + val signed = SignedData(serialized, sig) + val signedAndSerialized = testOutput.serialize(signed) + + File(URI("$localPath/$resource")).writeBytes( signedAndSerialized.bytes) + } + } \ No newline at end of file diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/networkParams.r3corda.6a6b6f256 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/networkParams.r3corda.6a6b6f256 new file mode 100644 index 0000000000000000000000000000000000000000..dcdbaa7b5f0d92f240af49f1068fcaee9c5b77f2 GIT binary patch literal 3066 zcmcImTWl0n7@ob%P7#CHh$xCF4XB|yy_YJbOt(99mtH7zw`{j2=Ire0_O!ESmYK8N zZh0|E@bYAg#>51}u@Al?FG5Vr7}RJ&q{f&iQ4+(8C{bffG=Zr9Y;`-kI)#StGM(=E z=D&Ua_y0#T%nb4gg7C~oX?O~PZ%Gi=Uxt@}u-Hg3lQjqNfa7^fqH= zSKK_1-LnVK(se-ltI#vFYdWv0X5T_vegMyQV5`u*|KKvZ-f?mYK3i9|TwkQQ zbDb0OXl&k^=uh^yCJr4?2ZoxLefwg74#lfujppZKLlZND(PYQ%2-aYjV1+yQDj~hLEE04I>6aW~pHen{!0NJd`S!M4MK)zdtH0Zd)n! z44Ol-g*7;4C3&=v)`>QVOD7*|+1Vn70!=s{*e0C+>yJa%qd%Wl>pC{E_tIZZUYaa) z`zNn$FzPOTcjgNzd;}Q$zWfjtAE+_TNDj*}YB5BaE&3BeT!{NB9pDE}#it#XoR@KU z5i4gR{N#Jb`H6Q_jLOu=V3a2^p$wZ8=`tJ@4Rcl=S^hhUOq9cLW+`gLRKR}N@9r=! zmKDckuS0X?)1bXW;`inVCAmUwR7Y&eFmvNzo*0w|APSZHT9m%9>w~z&>!?Ej!4~)J zv=txUQ!aFY5nUc47VEj0e99EY=#5c%V4D!A+GT8CabR&3(0L>aW3p#?|=QwFl)TUVUJ7{c~p;<1a4MWF>s$85xx`o}XAljdcH?Y1+Dqf>Tv5&e_X_t7Af~5EkuQd9d zsh#X>X&C`nZ8}hibw-gwDlk_ZHEi+m%^qW?s=dnckCf$qbDmd8)oWTR_GP!yw?OGN zBbPI%lL-%5%!Ifhqp(}v4HDmIm{~-L1r!-HK?(E1J_w?6V_mqak#QgNi*9H@JM_zn z-zv-|P#cU<<{8*#iA$PMpv?5JvF-!dfbKb%jx{G^bpPl^;&^4$q!_y>wyk z@6VyDXDWF6Lej}HM?U~lGYd@7^aTXV0 zu+C&Bi!pqpfT@OiD>bj3l3sT*#co{L{XyI5=(JN79!e7m7na(jPK&n`-|b5zu2tMEkn9R#I~VKfKPqv5dQD^chAcWm=Wk|j B^OXPq literal 0 HcmV?d00001 From 45ff60fccc59bf3fa16546b623b85666e23026d0 Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Mon, 5 Feb 2018 14:01:38 +0000 Subject: [PATCH 019/114] Init CRaSH shell only when it's really needed (#2448) * Avoid initializing CraSH if its not to be used --- node/src/main/kotlin/net/corda/node/ArgsParser.kt | 5 ++++- .../kotlin/net/corda/node/internal/AbstractNode.kt | 9 ++++----- .../kotlin/net/corda/node/internal/NodeStartup.kt | 3 ++- .../corda/node/services/config/NodeConfiguration.kt | 6 ++++++ .../services/config/NodeConfigurationImplTest.kt | 13 +++++++++++++ 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/ArgsParser.kt b/node/src/main/kotlin/net/corda/node/ArgsParser.kt index 8353dd7f1a..f42a922564 100644 --- a/node/src/main/kotlin/net/corda/node/ArgsParser.kt +++ b/node/src/main/kotlin/net/corda/node/ArgsParser.kt @@ -1,5 +1,6 @@ package net.corda.node +import com.typesafe.config.ConfigFactory import joptsimple.OptionParser import joptsimple.util.EnumConverter import net.corda.core.internal.div @@ -93,7 +94,9 @@ data class CmdLineOptions(val baseDirectory: Path, val justGenerateNodeInfo: Boolean, val bootstrapRaftCluster: Boolean) { fun loadConfig(): NodeConfiguration { - val config = ConfigHelper.loadConfig(baseDirectory, configFile).parseAsNodeConfiguration() + val config = ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap( + mapOf("noLocalShell" to this.noLocalShell) + )).parseAsNodeConfiguration() if (isRegistration) { requireNotNull(config.compatibilityZoneURL) { "Compatibility Zone Url must be provided in registration mode." } requireNotNull(networkRootTruststorePath) { "Network root trust store path must be provided in registration mode." } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 42355c78f4..30e484471f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -36,10 +36,7 @@ import net.corda.node.services.ContractUpgradeHandler import net.corda.node.services.FinalityHandler import net.corda.node.services.NotaryChangeHandler import net.corda.node.services.api.* -import net.corda.node.services.config.BFTSMaRtConfiguration -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.config.NotaryConfig -import net.corda.node.services.config.configureWithDevSSLCertificate +import net.corda.node.services.config.* import net.corda.node.services.events.NodeSchedulerService import net.corda.node.services.events.ScheduledActivityObserver import net.corda.node.services.identity.PersistentIdentityService @@ -278,7 +275,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected abstract fun getRxIoScheduler(): Scheduler open fun startShell(rpcOps: CordaRPCOps) { - InteractiveShell.startShell(configuration, rpcOps, securityManager, _services.identityService, _services.database) + if (configuration.shouldInitCrashShell()) { + InteractiveShell.startShell(configuration, rpcOps, securityManager, _services.identityService, _services.database) + } } private fun initNodeInfo(networkMapCache: NetworkMapCacheBaseInternal, diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index fa3ea52d3a..c19516aafb 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -11,6 +11,7 @@ import net.corda.core.utilities.loggerFor import net.corda.node.* import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfigurationImpl +import net.corda.node.services.config.shouldStartLocalShell import net.corda.node.services.transactions.bftSMaRtSerialFilter import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.registration.HTTPNetworkRegistrationService @@ -140,7 +141,7 @@ open class NodeStartup(val args: Array) { Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec") // Don't start the shell if there's no console attached. - if (!cmdlineOptions.noLocalShell && System.console() != null && conf.devMode) { + if (conf.shouldStartLocalShell()) { startedNode.internals.startupComplete.then { try { InteractiveShell.runLocalShell(startedNode) 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 4af6cb07e4..b26e18cffb 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 @@ -45,6 +45,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val detectPublicIp: Boolean get() = true val sshd: SSHDConfiguration? val database: DatabaseConfig + val noLocalShell: Boolean get() = false val transactionCacheSizeBytes: Long get() = defaultTransactionCacheSize val attachmentContentCacheSizeBytes: Long get() = defaultAttachmentContentCacheSize val attachmentCacheBound: Long get() = defaultAttachmentCacheBound @@ -69,6 +70,10 @@ fun NodeConfiguration.shouldCheckCheckpoints(): Boolean { return this.devMode && this.devModeOptions?.disableCheckpointChecker != true } +fun NodeConfiguration.shouldStartSSHDaemon() = this.sshd != null +fun NodeConfiguration.shouldStartLocalShell() = !this.noLocalShell && System.console() != null && this.devMode +fun NodeConfiguration.shouldInitCrashShell() = shouldStartLocalShell() || shouldStartSSHDaemon() + data class NotaryConfig(val validating: Boolean, val raft: RaftConfig? = null, val bftSMaRt: BFTSMaRtConfiguration? = null, @@ -128,6 +133,7 @@ data class NodeConfigurationImpl( override val notary: NotaryConfig?, override val certificateChainCheckPolicies: List, override val devMode: Boolean = false, + override val noLocalShell: Boolean = false, override val devModeOptions: DevModeOptions? = null, override val useTestClock: Boolean = false, override val detectPublicIp: Boolean = true, 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 ab9958f73c..21899cce86 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 @@ -29,6 +29,18 @@ class NodeConfigurationImplTest { assertFalse { configDebugOptions(true, DevModeOptions(true)).shouldCheckCheckpoints() } } + @Test + fun `check crashShell flags helper`() { + assertFalse { testConfiguration.copy(sshd = null).shouldStartSSHDaemon() } + assertTrue { testConfiguration.copy(sshd = SSHDConfiguration(1234)).shouldStartSSHDaemon() } + assertFalse { testConfiguration.copy(noLocalShell = true).shouldStartLocalShell() } + assertFalse { testConfiguration.copy(noLocalShell = false, devMode = false).shouldStartLocalShell() } + assertFalse { testConfiguration.copy(noLocalShell = false, devMode = true).shouldStartLocalShell() } + assertFalse { testConfiguration.copy(noLocalShell = true).shouldInitCrashShell() } + assertFalse { testConfiguration.copy(sshd = null).shouldInitCrashShell() } + assertFalse { testConfiguration.copy(noLocalShell = true, sshd = null).shouldInitCrashShell() } + } + private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfiguration { return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions) } @@ -63,6 +75,7 @@ class NodeConfigurationImplTest { notary = null, certificateChainCheckPolicies = emptyList(), devMode = true, + noLocalShell = false, activeMQServer = ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0)), rpcSettings = rpcSettings ) From 69c989478ab6d488d3b418259ccc75d536520dda Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 5 Feb 2018 16:54:58 +0000 Subject: [PATCH 020/114] CORDA-979 - Make public Java setter accessible within Kotlin (#2464) * CORDA-979 - Make public Java setter accessible within Kotlin * Review comments --- .../serialization/amqp/PropertySerializers.kt | 10 ++- .../serialization/amqp/SerializationHelper.kt | 83 +++++++++++-------- .../amqp/SetterConstructorTests.java | 16 +--- 3 files changed, 59 insertions(+), 50 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt index 537912a044..eaf0433fb3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt @@ -102,12 +102,18 @@ abstract class PropertyAccessor( class PropertyAccessorGetterSetter( initialPosition: Int, getter: PropertySerializer, - private val setter: Method?) : PropertyAccessor(initialPosition, getter) { + private val setter: Method) : PropertyAccessor(initialPosition, getter) { + init { + /** + * Play nicely with Java interop, public methods aren't marked as accessible + */ + setter.isAccessible = true + } /** * Invokes the setter on the underlying object passing in the serialized value. */ override fun set(instance: Any, obj: Any?) { - setter?.invoke(instance, *listOf(obj).toTypedArray()) + setter.invoke(instance, *listOf(obj).toTypedArray()) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index b4c15a0110..6763e79c9f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -89,12 +89,18 @@ fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbs * @property getter the method of a class that returns a fields value. Determined by * locating a function named getXyz for the property named in field as xyz. */ -data class PropertyDescriptor(var field: Field?, var setter: Method?, var getter: Method?) { +data class PropertyDescriptor(var field: Field?, var setter: Method?, var getter: Method?, var iser: Method?) { override fun toString() = StringBuilder("").apply { appendln("Property - ${field?.name ?: "null field"}\n") appendln(" getter - ${getter?.name ?: "no getter"}") - append(" setter - ${setter?.name ?: "no setter"}") + appendln(" setter - ${setter?.name ?: "no setter"}") + appendln(" iser - ${iser?.name ?: "no isXYZ defined"}") }.toString() + + constructor() : this(null, null, null, null) + constructor(field: Field?) : this(field, null, null, null) + + fun preferredGetter() : Method? = getter ?: iser } object PropertyDescriptorsRegex { @@ -122,12 +128,10 @@ fun Class.propertyDescriptors(): Map { val classProperties = mutableMapOf() var clazz: Class? = this - do { - // get the properties declared on this instance of class - clazz!!.declaredFields.forEach { classProperties.put(it.name, PropertyDescriptor(it, null, null)) } + clazz!!.declaredFields.forEach { classProperties.put(it.name, PropertyDescriptor(it)) } - // then pair them up with the declared getter and setter - // Note: It is possible for a class to have multiple instancess of a function where the types + do { + // Note: It is possible for a class to have multiple instances of a function where the types // differ. For example: // interface I { val a: T } // class D(override val a: String) : I @@ -138,45 +142,45 @@ fun Class.propertyDescriptors(): Map { // // In addition, only getters that take zero parameters and setters that take a single // parameter will be considered - clazz.declaredMethods?.map { func -> + clazz!!.declaredMethods?.map { func -> + if (!Modifier.isPublic(func.modifiers)) return@map + if (func.name == "getClass") return@map + PropertyDescriptorsRegex.re.find(func.name)?.apply { - try { - classProperties.getValue(groups[2]!!.value.decapitalize()).apply { - when (groups[1]!!.value) { - "set" -> { - if (func.parameterCount == 1) { - if (setter == null) setter = func - else if (TypeToken.of(setter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) { - setter = func - } + // take into account those constructor properties that don't directly map to a named + // property which are, by default, already added to the map + classProperties.computeIfAbsent(groups[2]!!.value.decapitalize()) { PropertyDescriptor() }.apply { + when (groups[1]!!.value) { + "set" -> { + if (func.parameterCount == 1) { + if (setter == null) setter = func + else if (TypeToken.of(setter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) { + setter = func } } - "get" -> { - if (func.parameterCount == 0) { - if (getter == null) getter = func - else if (TypeToken.of(getter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) { - getter = func - } + } + "get" -> { + if (func.parameterCount == 0) { + if (getter == null) getter = func + else if (TypeToken.of(getter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) { + getter = func } } - "is" -> { - if (func.parameterCount == 0) { - val rtnType = TypeToken.of(func.genericReturnType) - if ((rtnType == TypeToken.of(Boolean::class.java)) - || (rtnType == TypeToken.of(Boolean::class.javaObjectType))) { - if (getter == null) getter = func - } + } + "is" -> { + if (func.parameterCount == 0) { + val rtnType = TypeToken.of(func.genericReturnType) + if ((rtnType == TypeToken.of(Boolean::class.java)) + || (rtnType == TypeToken.of(Boolean::class.javaObjectType))) { + if (iser == null) iser = func } } } } - } catch (e: NoSuchElementException) { - // handles the getClass case from java.lang.Object - return@apply } } } - clazz = clazz?.superclass + clazz = clazz.superclass } while (clazz != null) return classProperties @@ -260,7 +264,7 @@ private fun propertiesForSerializationFromSetters( return mutableListOf().apply { var idx = 0 properties.forEach { property -> - val getter: Method? = property.value.getter + val getter: Method? = property.value.preferredGetter() val setter: Method? = property.value.setter if (getter == null || setter == null) return@forEach @@ -270,13 +274,20 @@ private fun propertiesForSerializationFromSetters( "takes too many arguments") } - val setterType = setter.parameterTypes.getOrNull(0)!! + val setterType = setter.parameterTypes[0]!! + if (!(TypeToken.of(property.value.field?.genericType!!).isSupertypeOf(setterType))) { throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " + "takes parameter of type $setterType yet underlying type is " + "${property.value.field?.genericType!!}") } + // make sure the setter returns the same type (within inheritance bounds) the getter accepts + if (!(TypeToken.of (setterType).isSupertypeOf(getter.returnType))) { + throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " + + "takes parameter of type $setterType yet the defined getter returns a value of type " + + "${getter.returnType}") + } this += PropertyAccessorGetterSetter( idx++, PropertySerializer.make(property.value.field!!.name, PublicPropertyReader(getter), diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java index bb710a841e..defd86d948 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/SetterConstructorTests.java @@ -286,12 +286,8 @@ public class SetterConstructorTests { tm.setA(10); assertEquals("10", tm.getA()); - TypeMismatch post = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(tm), - TypeMismatch.class); - - // because there is a type mismatch in the class, it won't return that info as a BEAN property and thus - // we won't serialise it and thus on deserialization it won't be initialized - Assertions.assertThatThrownBy(() -> post.getA()).isInstanceOf(NullPointerException.class); + Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm)).isInstanceOf ( + NotSerializableException.class); } @Test @@ -306,11 +302,7 @@ public class SetterConstructorTests { tm.setA("10"); assertEquals((Integer)10, tm.getA()); - TypeMismatch2 post = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(tm), - TypeMismatch2.class); - - // because there is a type mismatch in the class, it won't return that info as a BEAN property and thus - // we won't serialise it and thus on deserialization it won't be initialized - assertEquals(null, post.getA()); + Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm)).isInstanceOf( + NotSerializableException.class); } } From 3b5d89883dcf3eb0b29ec612d131f66307f00251 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Mon, 5 Feb 2018 18:17:54 +0000 Subject: [PATCH 021/114] Added basic node configuration validation. (#2433) --- .../net/corda/node/internal/NodeStartup.kt | 5 +++++ .../node/services/config/NodeConfiguration.kt | 18 ++++++++++++++++++ .../testing/node/internal/DriverDSLImpl.kt | 7 ++++++- .../testing/node/internal/NodeBasedTest.kt | 7 ++++++- 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index c19516aafb..99fe31d1fc 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -90,6 +90,11 @@ open class NodeStartup(val args: Array) { logger.error("Exception during node configuration", e) return false } + val errors = conf.validate() + if (errors.isNotEmpty()) { + logger.error("Invalid node configuration. Errors where:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}") + return false + } try { banJavaSerialisation(conf) 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 b26e18cffb..d881b1c968 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 @@ -50,6 +50,8 @@ interface NodeConfiguration : NodeSSLConfiguration { val attachmentContentCacheSizeBytes: Long get() = defaultAttachmentContentCacheSize val attachmentCacheBound: Long get() = defaultAttachmentCacheBound + fun validate(): List + companion object { // default to at least 8MB and a bit extra for larger heap sizes val defaultTransactionCacheSize: Long = 8.MB + getAdditionalCacheMemory() @@ -163,6 +165,22 @@ data class NodeConfigurationImpl( }.asOptions(fallbackSslOptions) } + override fun validate(): List { + val errors = mutableListOf() + errors + validateRpcOptions(rpcOptions) + return errors + } + + private fun validateRpcOptions(options: NodeRpcOptions): List { + val errors = mutableListOf() + if (!options.useSsl) { + if (options.adminAddress == null) { + errors + "'rpcSettings.adminAddress': missing. Property is mandatory when 'rpcSettings.useSsl' is false (default)." + } + } + return errors + } + override val exportJMXto: String get() = "http" override val transactionCacheSizeBytes: Long get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes 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 3a41a4627a..0b1ac2d4b5 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 @@ -711,7 +711,12 @@ class DriverDSLImpl( * Keeping [Config] around is needed as the user may specify extra config options not specified in [NodeConfiguration]. */ private class NodeConfig(val typesafe: Config) { - val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration() + val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration().also { nodeConfiguration -> + val errors = nodeConfiguration.validate() + if (errors.isNotEmpty()) { + throw IllegalStateException("Invalid node configuration. Errors where:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}") + } + } } companion object { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt index 70cfb2c7ea..560a52a865 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt @@ -99,7 +99,12 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi ) + configOverrides ) - val parsedConfig = config.parseAsNodeConfiguration() + val parsedConfig = config.parseAsNodeConfiguration().also { nodeConfiguration -> + val errors = nodeConfiguration.validate() + if (errors.isNotEmpty()) { + throw IllegalStateException("Invalid node configuration. Errors where:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}") + } + } defaultNetworkParameters.install(baseDirectory) val node = InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), cordappPackages).start() nodes += node From 95f062e8ffe323b49739f5f232f3737075f9ec05 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Tue, 6 Feb 2018 12:55:49 +0000 Subject: [PATCH 022/114] CORDA-904 - Fix evolver to work with setter instantiated classses (#2463) * CORDA-904 - Make evolver work with classes that use setters * review comments * review comments * small fixs * don't include systemTest in compiler.xml --- .idea/compiler.xml | 2 +- .../serialization/amqp/EvolutionSerializer.kt | 133 +++++++++++++----- .../serialization/amqp/PropertySerializers.kt | 21 +++ .../serialization/amqp/SerializationHelper.kt | 30 ++-- .../serialization/amqp/EvolvabilityTests.kt | 43 +++++- .../EvolvabilityTests.getterSetterEvolver1 | Bin 0 -> 420 bytes 6 files changed, 177 insertions(+), 52 deletions(-) create mode 100644 node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.getterSetterEvolver1 diff --git a/.idea/compiler.xml b/.idea/compiler.xml index a33ce1e0f0..d8d9f1498a 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -159,4 +159,4 @@ - \ No newline at end of file + diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt index 78ca924deb..b2dfa16f53 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolutionSerializer.kt @@ -21,12 +21,11 @@ import kotlin.reflect.jvm.javaType * @property constructorArgs used to hold the properties as sent to the object's constructor. Passed in as a * pre populated array as properties not present on the old constructor must be initialised in the factory */ -class EvolutionSerializer( +abstract class EvolutionSerializer( clazz: Type, factory: SerializerFactory, - private val oldReaders: Map, - override val kotlinConstructor: KFunction?, - private val constructorArgs: Array) : ObjectSerializer(clazz, factory) { + protected val oldReaders: Map, + override val kotlinConstructor: KFunction?) : ObjectSerializer(clazz, factory) { // explicitly set as empty to indicate it's unused by this type of serializer override val propertySerializers = PropertySerializersEvolution() @@ -35,12 +34,11 @@ class EvolutionSerializer( * Represents a parameter as would be passed to the constructor of the class as it was * when it was serialised and NOT how that class appears now * - * @param type The jvm type of the parameter * @param resultsIndex index into the constructor argument list where the read property * should be placed * @param property object to read the actual property value */ - data class OldParam(val type: Type, var resultsIndex: Int, val property: PropertySerializer) { + data class OldParam(var resultsIndex: Int, val property: PropertySerializer) { fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput, new: Array) = property.readProperty(obj, schemas, input).apply { if(resultsIndex >= 0) { @@ -62,9 +60,10 @@ class EvolutionSerializer( */ private fun getEvolverConstructor(type: Type, oldArgs: Map): KFunction? { val clazz: Class<*> = type.asClass()!! + if (!isConcrete(clazz)) return null - val oldArgumentSet = oldArgs.map { Pair(it.key as String?, it.value.type) } + val oldArgumentSet = oldArgs.map { Pair(it.key as String?, it.value.property.resolvedType) } var maxConstructorVersion = Integer.MIN_VALUE var constructor: KFunction? = null @@ -82,6 +81,36 @@ class EvolutionSerializer( return constructor ?: constructorForDeserialization(type) } + private fun makeWithConstructor( + new: ObjectSerializer, + factory: SerializerFactory, + constructor: KFunction, + readersAsSerialized: Map): AMQPSerializer { + val constructorArgs = arrayOfNulls(constructor.parameters.size) + + constructor.parameters.withIndex().forEach { + readersAsSerialized.get(it.value.name!!)?.apply { + this.resultsIndex = it.index + } ?: if (!it.value.type.isMarkedNullable) { + throw NotSerializableException( + "New parameter ${it.value.name} is mandatory, should be nullable for evolution to worK") + } + } + return EvolutionSerializerViaConstructor (new.type, factory, readersAsSerialized, constructor, constructorArgs) + } + + private fun makeWithSetters( + new: ObjectSerializer, + factory: SerializerFactory, + constructor: KFunction, + readersAsSerialized: Map, + classProperties: Map): AMQPSerializer { + val setters = propertiesForSerializationFromSetters(classProperties, + new.type, + factory).associateBy({ it.getter.name }, { it }) + return EvolutionSerializerViaSetters (new.type, factory, readersAsSerialized, constructor, setters) + } + /** * Build a serialization object for deserialization only of objects serialised * as different versions of a class. @@ -95,46 +124,43 @@ class EvolutionSerializer( */ fun make(old: CompositeType, new: ObjectSerializer, factory: SerializerFactory): AMQPSerializer { - - val readersAsSerialized = linkedMapOf( - *(old.fields.map { - val returnType = try { - it.getTypeAsClass(factory.classloader) - } catch (e: ClassNotFoundException) { - throw NotSerializableException(e.message) - } - - it.name to OldParam( - returnType, - -1, - PropertySerializer.make( - it.name, PublicPropertyReader(null), returnType, factory)) - }.toTypedArray()) - ) - - val constructor = getEvolverConstructor(new.type, readersAsSerialized) ?: - throw NotSerializableException( - "Attempt to deserialize an interface: ${new.type}. Serialized form is invalid.") - - val constructorArgs = arrayOfNulls(constructor.parameters.size) - - constructor.parameters.withIndex().forEach { - readersAsSerialized.get(it.value.name!!)?.apply { - this.resultsIndex = it.index - } ?: if (!it.value.type.isMarkedNullable) { - throw NotSerializableException( - "New parameter ${it.value.name} is mandatory, should be nullable for evolution to worK") + // The order in which the properties were serialised is important and must be preserved + val readersAsSerialized = LinkedHashMap() + old.fields.forEach { + readersAsSerialized[it.name] = try { + OldParam(-1, PropertySerializer.make(it.name, EvolutionPropertyReader(), + it.getTypeAsClass(factory.classloader), factory)) + } catch (e: ClassNotFoundException) { + throw NotSerializableException(e.message) } } - return EvolutionSerializer(new.type, factory, readersAsSerialized, constructor, constructorArgs) + // cope with the situation where a generic interface was serialised as a type, in such cases + // return the synthesised object which is, given the absence of a constructor, a no op + val constructor = getEvolverConstructor(new.type, readersAsSerialized) ?: return new + + val classProperties = new.type.asClass()?.propertyDescriptors() ?: emptyMap() + + return if (classProperties.isNotEmpty() && constructor.parameters.isEmpty()) { + makeWithSetters(new, factory, constructor, readersAsSerialized, classProperties) + } + else { + makeWithConstructor(new, factory, constructor, readersAsSerialized) + } } } - override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) { + override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) { throw UnsupportedOperationException("It should be impossible to write an evolution serializer") } +} +class EvolutionSerializerViaConstructor( + clazz: Type, + factory: SerializerFactory, + oldReaders: Map, + kotlinConstructor: KFunction?, + private val constructorArgs: Array) : EvolutionSerializer (clazz, factory, oldReaders, kotlinConstructor) { /** * Unlike a normal [readObject] call where we simply apply the parameter deserialisers * to the object list of values we need to map that list, which is ordered per the @@ -151,7 +177,36 @@ class EvolutionSerializer( oldReaders.values.zip(obj).map { it.first.readProperty(it.second, schemas, input, constructorArgs) } return javaConstructor?.newInstance(*(constructorArgs)) ?: - throw NotSerializableException("Attempt to deserialize an interface: $clazz. Serialized form is invalid.") + throw NotSerializableException( + "Attempt to deserialize an interface: $clazz. Serialized form is invalid.") + } +} + +/** + * Specific instance of an [EvolutionSerializer] where the properties of the object are set via calling + * named setter functions on the instantiated object. + */ +class EvolutionSerializerViaSetters( + clazz: Type, + factory: SerializerFactory, + oldReaders: Map, + kotlinConstructor: KFunction?, + private val setters: Map) : EvolutionSerializer (clazz, factory, oldReaders, kotlinConstructor) { + + override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any { + if (obj !is List<*>) throw NotSerializableException("Body of described type is unexpected $obj") + + val instance : Any = javaConstructor?.newInstance() ?: throw NotSerializableException ( + "Failed to instantiate instance of object $clazz") + + // *must* read all the parameters in the order they were serialized + oldReaders.values.zip(obj).forEach { + // if that property still exists on the new object then set it + it.first.property.readProperty(it.second, schemas, input).apply { + setters[it.first.property.name]?.set(instance, this) + } + } + return instance } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt index eaf0433fb3..9029dd171b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/PropertySerializers.kt @@ -14,6 +14,9 @@ abstract class PropertyReader { abstract fun isNullable(): Boolean } +/** + * Accessor for those properties of a class that have defined getter functions. + */ class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() { init { readMethod?.isAccessible = true @@ -43,6 +46,10 @@ class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() { override fun isNullable(): Boolean = readMethod?.returnsNullable() ?: false } +/** + * Accessor for those properties of a class that do not have defined getter functions. In which case + * we used reflection to remove the unreadable status from that property whilst it's accessed. + */ class PrivatePropertyReader(val field: Field, parentType: Type) : PropertyReader() { init { loggerFor().warn("Create property Serializer for private property '${field.name}' not " @@ -68,6 +75,20 @@ class PrivatePropertyReader(val field: Field, parentType: Type) : PropertyReader } } +/** + * Special instance of a [PropertyReader] for use only by [EvolutionSerializer]s to make + * it explicit that no properties are ever actually read from an object as the evolution + * serializer should only be accessing the already serialized form. + */ +class EvolutionPropertyReader : PropertyReader() { + override fun read(obj: Any?): Any? { + throw UnsupportedOperationException("It should be impossible for an evolution serializer to " + + "be reading from an object") + } + + override fun isNullable() = true +} + /** * Represents a generic interface to a serializable property of an object. * diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 6763e79c9f..4ce172cc78 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -98,7 +98,6 @@ data class PropertyDescriptor(var field: Field?, var setter: Method?, var getter }.toString() constructor() : this(null, null, null, null) - constructor(field: Field?) : this(field, null, null, null) fun preferredGetter() : Method? = getter ?: iser } @@ -128,9 +127,16 @@ fun Class.propertyDescriptors(): Map { val classProperties = mutableMapOf() var clazz: Class? = this - clazz!!.declaredFields.forEach { classProperties.put(it.name, PropertyDescriptor(it)) } do { + clazz!!.declaredFields.forEach { property -> + classProperties.computeIfAbsent(property.name) { + PropertyDescriptor() + }.apply { + this.field = property + } + } + // Note: It is possible for a class to have multiple instances of a function where the types // differ. For example: // interface I { val a: T } @@ -142,7 +148,7 @@ fun Class.propertyDescriptors(): Map { // // In addition, only getters that take zero parameters and setters that take a single // parameter will be considered - clazz!!.declaredMethods?.map { func -> + clazz.declaredMethods?.map { func -> if (!Modifier.isPublic(func.modifiers)) return@map if (func.name == "getClass") return@map @@ -231,15 +237,13 @@ internal fun propertiesForSerializationFromConstructor( Pair(PublicPropertyReader(getter), returnType) } else { - try { - val field = clazz.getDeclaredField(param.value.name) - Pair(PrivatePropertyReader(field, type), field.genericType) - } catch (e: NoSuchFieldException) { + val field = classProperties[name]!!.field ?: throw NotSerializableException("No property matching constructor parameter named '$name' " + "of '$clazz'. If using Java, check that you have the -parameters option specified " + "in the Java compiler. Alternately, provide a proxy serializer " + "(SerializationCustomSerializer) if recompiling isn't an option") - } + + Pair(PrivatePropertyReader(field, type), field.genericType) } } else { throw NotSerializableException( @@ -257,12 +261,13 @@ internal fun propertiesForSerializationFromConstructor( * If we determine a class has a constructor that takes no parameters then check for pairs of getters / setters * and use those */ -private fun propertiesForSerializationFromSetters( +fun propertiesForSerializationFromSetters( properties: Map, type: Type, factory: SerializerFactory): List { return mutableListOf().apply { var idx = 0 + properties.forEach { property -> val getter: Method? = property.value.preferredGetter() val setter: Method? = property.value.setter @@ -276,7 +281,8 @@ private fun propertiesForSerializationFromSetters( val setterType = setter.parameterTypes[0]!! - if (!(TypeToken.of(property.value.field?.genericType!!).isSupertypeOf(setterType))) { + if ((property.value.field != null) && + (!(TypeToken.of(property.value.field?.genericType!!).isSupertypeOf(setterType)))) { throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " + "takes parameter of type $setterType yet underlying type is " + "${property.value.field?.genericType!!}") @@ -290,7 +296,7 @@ private fun propertiesForSerializationFromSetters( } this += PropertyAccessorGetterSetter( idx++, - PropertySerializer.make(property.value.field!!.name, PublicPropertyReader(getter), + PropertySerializer.make(property.key, PublicPropertyReader(getter), resolveTypeVariables(getter.genericReturnType, type), factory), setter) } @@ -322,6 +328,8 @@ private fun propertiesForSerializationFromAbstract( return mutableListOf().apply { properties.toList().withIndex().forEach { val getter = it.value.second.getter ?: return@forEach + if (it.value.second.field == null) return@forEach + val returnType = resolveTypeVariables(getter.genericReturnType, type) this += PropertyAccessorConstructor( it.index, diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index b18abd9f82..0fce099955 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -538,4 +538,45 @@ class EvolvabilityTests { File(URI("$localPath/$resource")).writeBytes( signedAndSerialized.bytes) } -} \ No newline at end of file + @Suppress("UNCHECKED_CAST") + @Test + fun getterSetterEvolver1() { + val resource = "EvolvabilityTests.getterSetterEvolver1" + val sf = testDefaultFactory() + + // + // Class as it was serialised + // + // data class C(var c: Int, var d: Int, var b: Int, var e: Int, var a: Int) { + // // This will force the serialization engine to use getter / setter + // // instantiation for the object rather than construction + // @ConstructorForDeserialization + // @Suppress("UNUSED") + // constructor() : this(0, 0, 0, 0, 0) + // } + // + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(3,4,2,5,1)).bytes) + + // + // Class as it exists now, c has been removed + // + data class C(var d: Int, var b: Int, var e: Int, var a: Int) { + // This will force the serialization engine to use getter / setter + // instantiation for the object rather than construction + @ConstructorForDeserialization + @Suppress("UNUSED") + constructor() : this(0, 0, 0, 0) + } + + val path = EvolvabilityTests::class.java.getResource(resource) + val f = File(path.toURI()) + + val sc2 = f.readBytes() + val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes(sc2)) + + assertEquals(1, deserializedC.a) + assertEquals(2, deserializedC.b) + assertEquals(4, deserializedC.d) + assertEquals(5, deserializedC.e) + } +} diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.getterSetterEvolver1 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.getterSetterEvolver1 new file mode 100644 index 0000000000000000000000000000000000000000..c544432e6d33b7a2ff606f99b0702636cf8477d0 GIT binary patch literal 420 zcmYe!FG@*dWME)uIGO|`85kHZ0C6vn!OXB&DKE7|FBzo5Dl%9ZVS%1&S$-iN_uJu zP Date: Wed, 7 Feb 2018 11:55:06 +0000 Subject: [PATCH 023/114] CORDA-939 - Dont expose FlowStateMachine via public API (#2438) * Create CordaInternal attribute for properties on public classes that are not part of the api and apply to FlowLogic.stateMachine * Remove startFlow from public test api and replace with startFlowAndReturnFuture * Update api-current with changed signature * Change test used in documentation to use public test methods * Remove the rest of the unneccessary usages of the startFlow test utility * Remove extra whitespace * Rename startFlowAndReturnFuture back to startFlow * Update api * The annotation doesn't appear unless its marked as on the actual getter and setter * Updated docs and removed pointless attribute * Deleted whitespace --- .ci/api-current.txt | 8 +++++--- .../corda/confidential/IdentitySyncFlowTests.kt | 10 +++++----- .../confidential/SwapIdentitiesFlowTests.kt | 2 +- .../main/kotlin/net/corda/core/CordaInternal.kt | 11 +++++++++++ .../kotlin/net/corda/core/flows/FlowLogic.kt | 3 +++ .../net/corda/core/flows/FlowsInJavaTest.java | 4 ++-- .../net/corda/core/flows/AttachmentTests.kt | 8 ++++---- .../core/flows/CollectSignaturesFlowTests.kt | 8 ++++---- .../corda/core/flows/ContractUpgradeFlowTest.kt | 16 ++++++++-------- .../net/corda/core/flows/FinalityFlowTests.kt | 4 ++-- .../net/corda/core/flows/ReceiveAllFlowTests.kt | 6 +++--- .../core/internal/ResolveTransactionsFlowTest.kt | 14 +++++++------- .../tutorial/mocknetwork/TutorialMockNetwork.kt | 8 ++------ .../net/corda/docs/CustomVaultQueryTest.kt | 4 ++-- .../corda/docs/FxTransactionBuildTutorialTest.kt | 6 +++--- .../docs/WorkflowTransactionBuildTutorialTest.kt | 4 ++-- .../cash/selection/CashSelectionH2ImplTest.kt | 10 +++++----- .../net/corda/finance/flows/CashExitFlowTests.kt | 6 +++--- .../corda/finance/flows/CashIssueFlowTests.kt | 4 ++-- .../corda/finance/flows/CashPaymentFlowTests.kt | 9 ++++----- .../corda/node/services/BFTNotaryServiceTests.kt | 4 ++-- .../node/services/RaftNotaryServiceTests.kt | 4 ++-- .../services/statemachine/FlowVersioningTest.kt | 2 +- .../corda/services/messaging/MQSecurityTest.kt | 4 +--- .../corda/node/internal/NetworkParametersTest.kt | 2 +- .../node/messaging/TwoPartyTradeFlowTests.kt | 2 +- .../net/corda/node/services/NotaryChangeTests.kt | 10 +++++----- .../node/services/events/ScheduledFlowTests.kt | 4 ++-- .../services/statemachine/FlowFrameworkTests.kt | 2 +- .../services/transactions/NotaryServiceTests.kt | 10 +++++----- .../transactions/ValidatingNotaryServiceTests.kt | 2 +- .../net/corda/irs/api/NodeInterestRatesTest.kt | 2 +- .../net/corda/netmap/simulation/IRSSimulation.kt | 2 +- .../corda/testing/node/FlowStackSnapshotTest.kt | 2 +- .../kotlin/net/corda/testing/driver/Driver.kt | 2 +- .../net/corda/testing/node/NodeTestUtils.kt | 9 +++------ .../testing/node/internal/InternalTestUtils.kt | 8 ++++++++ 37 files changed, 115 insertions(+), 101 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/CordaInternal.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 8d485ec906..8fff5bb205 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -14,6 +14,8 @@ public void setMessage(String) public void setOriginalExceptionClassName(String) ## +public @interface net.corda.core.CordaInternal +## public final class net.corda.core.CordaOID extends java.lang.Object @org.jetbrains.annotations.NotNull public static final String CORDA_PLATFORM = "1.3.6.1.4.1.50530.1" public static final net.corda.core.CordaOID INSTANCE @@ -1227,7 +1229,7 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object @org.jetbrains.annotations.Nullable public net.corda.core.utilities.ProgressTracker getProgressTracker() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId getRunId() @org.jetbrains.annotations.NotNull public final net.corda.core.node.ServiceHub getServiceHub() - @org.jetbrains.annotations.NotNull public final net.corda.core.internal.FlowStateMachine getStateMachine() + @net.corda.core.CordaInternal @org.jetbrains.annotations.NotNull public final net.corda.core.internal.FlowStateMachine getStateMachine() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowSession initiateFlow(net.corda.core.identity.Party) @co.paralleluniverse.fibers.Suspendable public final void persistFlowStackSnapshot() @kotlin.Deprecated @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public net.corda.core.utilities.UntrustworthyData receive(Class, net.corda.core.identity.Party) @@ -1236,7 +1238,7 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object public final void recordAuditEvent(String, String, Map) @kotlin.Deprecated @co.paralleluniverse.fibers.Suspendable public void send(net.corda.core.identity.Party, Object) @kotlin.Deprecated @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public net.corda.core.utilities.UntrustworthyData sendAndReceive(Class, net.corda.core.identity.Party, Object) - public final void setStateMachine(net.corda.core.internal.FlowStateMachine) + @net.corda.core.CordaInternal public final void setStateMachine(net.corda.core.internal.FlowStateMachine) @co.paralleluniverse.fibers.Suspendable @kotlin.jvm.JvmStatic public static final void sleep(java.time.Duration) @co.paralleluniverse.fibers.Suspendable public Object subFlow(net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed track() @@ -4153,7 +4155,7 @@ public final class net.corda.testing.node.NodeTestUtils extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.testing.dsl.LedgerDSL ledger(net.corda.core.node.ServiceHub, kotlin.jvm.functions.Function1) @org.jetbrains.annotations.NotNull public static final net.corda.testing.dsl.LedgerDSL ledger(net.corda.core.node.ServiceHub, net.corda.core.identity.Party, kotlin.jvm.functions.Function1) @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext newContext(net.corda.node.services.api.StartedNodeServices) - @org.jetbrains.annotations.NotNull public static final net.corda.core.internal.FlowStateMachine startFlow(net.corda.node.services.api.StartedNodeServices, net.corda.core.flows.FlowLogic) + @org.jetbrains.annotations.NotNull public static final net.corda.core.concurrent.CordaFuture startFlow(net.corda.node.services.api.StartedNodeServices, net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.NotNull public static final net.corda.core.context.Actor testActor(net.corda.core.identity.CordaX500Name) @org.jetbrains.annotations.NotNull public static final net.corda.core.context.InvocationContext testContext(net.corda.core.identity.CordaX500Name) @org.jetbrains.annotations.NotNull public static final net.corda.testing.dsl.LedgerDSL transaction(net.corda.core.node.ServiceHub, kotlin.jvm.functions.Function1) diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt index 11c8e45532..506f021b68 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -58,12 +58,12 @@ class IdentitySyncFlowTests { val anonymous = true val ref = OpaqueBytes.of(0x01) val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notary)) - val issueTx = issueFlow.resultFuture.getOrThrow().stx + val issueTx = issueFlow.getOrThrow().stx val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance().single().owner assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) }) // Run the flow to sync up the identities - aliceNode.services.startFlow(Initiator(bob, issueTx.tx)).resultFuture.getOrThrow() + aliceNode.services.startFlow(Initiator(bob, issueTx.tx)).getOrThrow() val expected = aliceNode.database.transaction { aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) } @@ -88,7 +88,7 @@ class IdentitySyncFlowTests { val anonymous = true val ref = OpaqueBytes.of(0x01) val issueFlow = charlieNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, charlie, anonymous, notary)) - val issueTx = issueFlow.resultFuture.getOrThrow().stx + val issueTx = issueFlow.getOrThrow().stx val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance().single().owner val confidentialIdentCert = charlieNode.services.identityService.certificateFromKey(confidentialIdentity.owningKey)!! @@ -97,11 +97,11 @@ class IdentitySyncFlowTests { assertNotNull(aliceNode.database.transaction { aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) }) // Generate a payment from Charlie to Alice, including the confidential state - val payTx = charlieNode.services.startFlow(CashPaymentFlow(1000.DOLLARS, alice, anonymous)).resultFuture.getOrThrow().stx + val payTx = charlieNode.services.startFlow(CashPaymentFlow(1000.DOLLARS, alice, anonymous)).getOrThrow().stx // Run the flow to sync up the identities, and confirm Charlie's confidential identity doesn't leak assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) }) - aliceNode.services.startFlow(Initiator(bob, payTx.tx)).resultFuture.getOrThrow() + aliceNode.services.startFlow(Initiator(bob, payTx.tx)).getOrThrow() assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) }) } diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index f1e2efe9ae..f8fbc6668d 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -30,7 +30,7 @@ class SwapIdentitiesFlowTests { val requesterFlow = aliceNode.services.startFlow(SwapIdentitiesFlow(bob)) // Get the results - val actual: Map = requesterFlow.resultFuture.getOrThrow().toMap() + val actual: Map = requesterFlow.getOrThrow().toMap() assertEquals(2, actual.size) // Verify that the generated anonymous identities do not match the well known identities val aliceAnonymousIdentity = actual[alice] ?: throw IllegalStateException() diff --git a/core/src/main/kotlin/net/corda/core/CordaInternal.kt b/core/src/main/kotlin/net/corda/core/CordaInternal.kt new file mode 100644 index 0000000000..40f145fdec --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/CordaInternal.kt @@ -0,0 +1,11 @@ +package net.corda.core + +/** + * These methods are not part of Corda's API compatibility guarantee and applications should not use them. + * + * These fields are only meant to be used by Corda internally, and are not intended to be part of the public API. + */ +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) +@MustBeDocumented +annotation class CordaInternal \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index 476521f0ec..6b92c533ab 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -2,6 +2,7 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.strands.Strand +import net.corda.core.CordaInternal import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate @@ -419,7 +420,9 @@ abstract class FlowLogic { * is public only because it must be accessed across module boundaries. */ var stateMachine: FlowStateMachine<*> + @CordaInternal get() = _stateMachine ?: throw IllegalStateException("This can only be done after the flow has been started.") + @CordaInternal set(value) { _stateMachine = value } diff --git a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java index 848b74aed7..b5b8371959 100644 --- a/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java +++ b/core/src/test/java/net/corda/core/flows/FlowsInJavaTest.java @@ -40,7 +40,7 @@ public class FlowsInJavaTest { @Test public void suspendableActionInsideUnwrap() throws Exception { bobNode.registerInitiatedFlow(SendHelloAndThenReceive.class); - Future result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(bob)).getResultFuture(); + Future result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(bob)); mockNet.runNetwork(); assertThat(result.get()).isEqualTo("Hello"); } @@ -56,7 +56,7 @@ public class FlowsInJavaTest { private void primitiveReceiveTypeTest(Class receiveType) throws InterruptedException { PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(bob, receiveType); - Future result = startFlow(aliceNode.getServices(), flow).getResultFuture(); + Future result = startFlow(aliceNode.getServices(), flow); mockNet.runNetwork(); try { result.get(); diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index 6de955e66d..5499040845 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -65,7 +65,7 @@ class AttachmentTests { mockNet.runNetwork() val bobFlow = bobNode.startAttachmentFlow(setOf(id), alice) mockNet.runNetwork() - assertEquals(0, bobFlow.resultFuture.getOrThrow().fromDisk.size) + assertEquals(0, bobFlow.getOrThrow().fromDisk.size) // Verify it was inserted into node one's store. val attachment = bobNode.database.transaction { @@ -77,7 +77,7 @@ class AttachmentTests { // Shut down node zero and ensure node one can still resolve the attachment. aliceNode.dispose() - val response: FetchDataFlow.Result = bobNode.startAttachmentFlow(setOf(id), alice).resultFuture.getOrThrow() + val response: FetchDataFlow.Result = bobNode.startAttachmentFlow(setOf(id), alice).getOrThrow() assertEquals(attachment, response.fromDisk[0]) } @@ -92,7 +92,7 @@ class AttachmentTests { val alice = aliceNode.info.singleIdentity() val bobFlow = bobNode.startAttachmentFlow(setOf(hash), alice) mockNet.runNetwork() - val e = assertFailsWith { bobFlow.resultFuture.getOrThrow() } + val e = assertFailsWith { bobFlow.getOrThrow() } assertEquals(hash, e.requested) } @@ -127,7 +127,7 @@ class AttachmentTests { mockNet.runNetwork() val bobFlow = bobNode.startAttachmentFlow(setOf(id), alice) mockNet.runNetwork() - assertFailsWith { bobFlow.resultFuture.getOrThrow() } + assertFailsWith { bobFlow.getOrThrow() } } private fun StartedNode<*>.startAttachmentFlow(hashes: Set, otherSide: Party) = services.startFlow(InitiatingFetchAttachmentsFlow(otherSide, hashes)) diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index 353c20034f..d3bbcd9dc8 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -115,7 +115,7 @@ class CollectSignaturesFlowTests { val state = DummyContract.MultiOwnerState(magicNumber, parties) val flow = aliceNode.services.startFlow(TestFlow.Initiator(state, notary)) mockNet.runNetwork() - val result = flow.resultFuture.getOrThrow() + val result = flow.getOrThrow() result.verifyRequiredSignatures() println(result.tx) println(result.sigs) @@ -127,7 +127,7 @@ class CollectSignaturesFlowTests { val ptx = aliceNode.services.signInitialTransaction(onePartyDummyContract) val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet())) mockNet.runNetwork() - val result = flow.resultFuture.getOrThrow() + val result = flow.getOrThrow() result.verifyRequiredSignatures() println(result.tx) println(result.sigs) @@ -141,7 +141,7 @@ class CollectSignaturesFlowTests { val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet())) mockNet.runNetwork() assertFailsWith("The Initiator of CollectSignaturesFlow must have signed the transaction.") { - flow.resultFuture.getOrThrow() + flow.getOrThrow() } } @@ -155,7 +155,7 @@ class CollectSignaturesFlowTests { val signedByBoth = bobNode.services.addSignature(signedByA) val flow = aliceNode.services.startFlow(CollectSignaturesFlow(signedByBoth, emptySet())) mockNet.runNetwork() - val result = flow.resultFuture.getOrThrow() + val result = flow.getOrThrow() println(result.tx) println(result.sigs) } diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index aae132071a..61f50c1e05 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -81,24 +81,24 @@ class ContractUpgradeFlowTest { requireNotNull(btx) // The request is expected to be rejected because party B hasn't authorised the upgrade yet. - val rejectedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture + val rejectedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx!!.tx.outRef(0), DummyContractV2::class.java)) mockNet.runNetwork() assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() } // Party B authorise the contract state upgrade, and immediately deauthorise the same. - bobNode.services.startFlow(ContractUpgradeFlow.Authorise(btx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture.getOrThrow() - bobNode.services.startFlow(ContractUpgradeFlow.Deauthorise(btx.tx.outRef(0).ref)).resultFuture.getOrThrow() + bobNode.services.startFlow(ContractUpgradeFlow.Authorise(btx!!.tx.outRef(0), DummyContractV2::class.java)).getOrThrow() + bobNode.services.startFlow(ContractUpgradeFlow.Deauthorise(btx.tx.outRef(0).ref)).getOrThrow() // The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade. - val deauthorisedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture + val deauthorisedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java)) mockNet.runNetwork() assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.getOrThrow() } // Party B authorise the contract state upgrade - bobNode.services.startFlow(ContractUpgradeFlow.Authorise(btx.tx.outRef(0), DummyContractV2::class.java)).resultFuture.getOrThrow() + bobNode.services.startFlow(ContractUpgradeFlow.Authorise(btx.tx.outRef(0), DummyContractV2::class.java)).getOrThrow() // Party A initiates contract upgrade flow, expected to succeed this time. - val resultFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture + val resultFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java)) mockNet.runNetwork() val result = resultFuture.getOrThrow() @@ -213,7 +213,7 @@ class ContractUpgradeFlowTest { fun `upgrade Cash to v2`() { // Create some cash. val chosenIdentity = alice - val result = aliceNode.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary)).resultFuture + val result = aliceNode.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary)) mockNet.runNetwork() val stx = result.getOrThrow().stx val anonymisedRecipient = result.get().recipient!! @@ -221,7 +221,7 @@ class ContractUpgradeFlowTest { val baseState = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy().states.single() } assertTrue(baseState.state.data is Cash.State, "Contract state is old version.") // Starts contract upgrade flow. - val upgradeResult = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(stateAndRef, CashV2::class.java)).resultFuture + val upgradeResult = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(stateAndRef, CashV2::class.java)) mockNet.runNetwork() upgradeResult.getOrThrow() // Get contract state from the vault. diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index 0263c3f234..15d0f42b03 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -53,7 +53,7 @@ class FinalityFlowTests { val stx = aliceServices.signInitialTransaction(builder) val flow = aliceServices.startFlow(FinalityFlow(stx)) mockNet.runNetwork() - val notarisedTx = flow.resultFuture.getOrThrow() + val notarisedTx = flow.getOrThrow() notarisedTx.verifyRequiredSignatures() val transactionSeenByB = bobServices.database.transaction { bobServices.validatedTransactions.getTransaction(notarisedTx.id) @@ -71,7 +71,7 @@ class FinalityFlowTests { val flow = aliceServices.startFlow(FinalityFlow(stx)) mockNet.runNetwork() assertFailsWith { - flow.resultFuture.getOrThrow() + flow.getOrThrow() } } } \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt index 6dccd55c9f..1893e3607e 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ReceiveAllFlowTests.kt @@ -52,7 +52,7 @@ class ReceiveMultipleFlowTests { val flow = nodes[0].services.startFlow(initiatingFlow) mockNet.runNetwork() - val receivedAnswer = flow.resultFuture.getOrThrow() + val receivedAnswer = flow.getOrThrow() assertThat(receivedAnswer).isEqualTo(answer) } @@ -64,7 +64,7 @@ class ReceiveMultipleFlowTests { nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue) val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) mockNet.runNetwork() - val result = flow.resultFuture.getOrThrow() + val result = flow.getOrThrow() assertThat(result).isEqualTo(doubleValue * stringValue.length) } @@ -76,7 +76,7 @@ class ReceiveMultipleFlowTests { nodes[2].registerAnswer(ParallelAlgorithmList::class, value2) val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) mockNet.runNetwork() - val data = flow.resultFuture.getOrThrow() + val data = flow.getOrThrow() assertThat(data[0]).isEqualTo(value1) assertThat(data[1]).isEqualTo(value2) assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2) diff --git a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt index b61374a40a..9038232355 100644 --- a/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/ResolveTransactionsFlowTest.kt @@ -52,14 +52,14 @@ class ResolveTransactionsFlowTest { fun tearDown() { mockNet.stopNodes() } -// DOCEND 3 + // DOCEND 3 // DOCSTART 1 @Test fun `resolve from two hashes`() { val (stx1, stx2) = makeTransactions() val p = TestFlow(setOf(stx2.id), megaCorp) - val future = miniCorpNode.services.startFlow(p).resultFuture + val future = miniCorpNode.services.startFlow(p) mockNet.runNetwork() val results = future.getOrThrow() assertEquals(listOf(stx1.id, stx2.id), results.map { it.id }) @@ -74,7 +74,7 @@ class ResolveTransactionsFlowTest { fun `dependency with an error`() { val stx = makeTransactions(signFirstTX = false).second val p = TestFlow(setOf(stx.id), megaCorp) - val future = miniCorpNode.services.startFlow(p).resultFuture + val future = miniCorpNode.services.startFlow(p) mockNet.runNetwork() assertFailsWith(SignedTransaction.SignaturesMissingException::class) { future.getOrThrow() } } @@ -83,7 +83,7 @@ class ResolveTransactionsFlowTest { fun `resolve from a signed transaction`() { val (stx1, stx2) = makeTransactions() val p = TestFlow(stx2, megaCorp) - val future = miniCorpNode.services.startFlow(p).resultFuture + val future = miniCorpNode.services.startFlow(p) mockNet.runNetwork() future.getOrThrow() miniCorpNode.database.transaction { @@ -108,7 +108,7 @@ class ResolveTransactionsFlowTest { cursor = stx } val p = TestFlow(setOf(cursor.id), megaCorp, 40) - val future = miniCorpNode.services.startFlow(p).resultFuture + val future = miniCorpNode.services.startFlow(p) mockNet.runNetwork() assertFailsWith { future.getOrThrow() } } @@ -132,7 +132,7 @@ class ResolveTransactionsFlowTest { } val p = TestFlow(setOf(stx3.id), megaCorp) - val future = miniCorpNode.services.startFlow(p).resultFuture + val future = miniCorpNode.services.startFlow(p) mockNet.runNetwork() future.getOrThrow() } @@ -154,7 +154,7 @@ class ResolveTransactionsFlowTest { } val stx2 = makeTransactions(withAttachment = id).second val p = TestFlow(stx2, megaCorp) - val future = miniCorpNode.services.startFlow(p).resultFuture + val future = miniCorpNode.services.startFlow(p) mockNet.runNetwork() future.getOrThrow() diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt index 0a71750b11..6841c5d7fc 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt @@ -15,11 +15,7 @@ import net.corda.core.utilities.unwrap import net.corda.node.internal.StartedNode import net.corda.node.services.messaging.Message import net.corda.node.services.statemachine.SessionData -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MessagingServiceSpy -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.setMessagingServiceSpy -import net.corda.testing.node.startFlow +import net.corda.testing.node.* import org.junit.After import org.junit.Before import org.junit.Rule @@ -102,6 +98,6 @@ class TutorialMockNetwork { expectedEx.expect(IllegalArgumentException::class.java) expectedEx.expectMessage("Expected to receive 1") - initiatingReceiveFlow.resultFuture.getOrThrow() + initiatingReceiveFlow.getOrThrow() } } \ No newline at end of file diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt index 372259ddb6..efd1935059 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt @@ -60,7 +60,7 @@ class CustomVaultQueryTest { OpaqueBytes.of(0x01), notary)) // Wait for the flow to stop and print - flowHandle1.resultFuture.getOrThrow() + flowHandle1.getOrThrow() } private fun topUpCurrencies() { @@ -69,7 +69,7 @@ class CustomVaultQueryTest { OpaqueBytes.of(0x01), nodeA.info.chooseIdentity(), notary)) - flowHandle1.resultFuture.getOrThrow() + flowHandle1.getOrThrow() } private fun getBalances(): Pair>, Map>> { diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index eaabd01a40..8ecfb2c453 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -43,7 +43,7 @@ class FxTransactionBuildTutorialTest { OpaqueBytes.of(0x01), notary)) // Wait for the flow to stop and print - flowHandle1.resultFuture.getOrThrow() + flowHandle1.getOrThrow() printBalances() // Using NodeB as Issuer create some pounds. @@ -51,7 +51,7 @@ class FxTransactionBuildTutorialTest { OpaqueBytes.of(0x01), notary)) // Wait for flow to come to an end and print - flowHandle2.resultFuture.getOrThrow() + flowHandle2.getOrThrow() printBalances() // Setup some futures on the vaults to await the arrival of the exchanged funds at both nodes @@ -65,7 +65,7 @@ class FxTransactionBuildTutorialTest { nodeB.info.chooseIdentity(), weAreBaseCurrencySeller = false)) // wait for the flow to finish and the vault updates to be done - doIt.resultFuture.getOrThrow() + doIt.getOrThrow() // Get the balances when the vault updates nodeAVaultUpdate.get() val balancesA = nodeA.database.transaction { diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt index c74c5c7d45..7f751e577a 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt @@ -56,7 +56,7 @@ class WorkflowTransactionBuildTutorialTest { // Kick of the proposal flow val flow1 = aliceServices.startFlow(SubmitTradeApprovalFlow("1234", bob)) // Wait for the flow to finish - val proposalRef = flow1.resultFuture.getOrThrow() + val proposalRef = flow1.getOrThrow() val proposalLinearId = proposalRef.state.data.linearId // Wait for NodeB to include it's copy in the vault nodeBVaultUpdate.get() @@ -80,7 +80,7 @@ class WorkflowTransactionBuildTutorialTest { // Run the manual completion flow from NodeB val flow2 = bobServices.startFlow(SubmitCompletionFlow(latestFromB.ref, WorkflowState.APPROVED)) // wait for the flow to end - val completedRef = flow2.resultFuture.getOrThrow() + val completedRef = flow2.getOrThrow() // wait for the vault updates to stabilise nodeAVaultUpdate.get() secondNodeBVaultUpdate.get() diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt index faba806b2a..79feb44f34 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt @@ -31,10 +31,10 @@ class CashSelectionH2ImplTest { // spend operation below. // Issuing Integer.MAX_VALUE will not cause an exception since PersistentCashState.pennies is a long nCopies(2, Integer.MAX_VALUE).map { issueAmount -> - node.services.startFlow(CashIssueFlow(issueAmount.POUNDS, OpaqueBytes.of(1), mockNet.defaultNotaryIdentity)).resultFuture + node.services.startFlow(CashIssueFlow(issueAmount.POUNDS, OpaqueBytes.of(1), mockNet.defaultNotaryIdentity)) }.transpose().getOrThrow() // The spend must be more than the size of a single cash state to force the accumulator onto the second state. - node.services.startFlow(CashPaymentFlow((Integer.MAX_VALUE + 1L).POUNDS, node.info.legalIdentities[0])).resultFuture.getOrThrow() + node.services.startFlow(CashPaymentFlow((Integer.MAX_VALUE + 1L).POUNDS, node.info.legalIdentities[0])).getOrThrow() } @Test @@ -50,8 +50,8 @@ class CashSelectionH2ImplTest { val flow2 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary)) val flow3 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary)) - assertThatThrownBy { flow1.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) - assertThatThrownBy { flow2.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) - assertThatThrownBy { flow3.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) + assertThatThrownBy { flow1.getOrThrow() }.isInstanceOf(CashException::class.java) + assertThatThrownBy { flow2.getOrThrow() }.isInstanceOf(CashException::class.java) + assertThatThrownBy { flow3.getOrThrow() }.isInstanceOf(CashException::class.java) } } \ No newline at end of file diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt index fc32245551..9c70c96316 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashExitFlowTests.kt @@ -33,7 +33,7 @@ class CashExitFlowTests { bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME) notary = mockNet.defaultNotaryIdentity - val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)) mockNet.runNetwork() future.getOrThrow() } @@ -46,7 +46,7 @@ class CashExitFlowTests { @Test fun `exit some cash`() { val exitAmount = 500.DOLLARS - val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount, ref)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount, ref)) mockNet.runNetwork() val exitTx = future.getOrThrow().stx.tx val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref)) @@ -59,7 +59,7 @@ class CashExitFlowTests { @Test fun `exit zero cash`() { val expected = 0.DOLLARS - val future = bankOfCordaNode.services.startFlow(CashExitFlow(expected, ref)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashExitFlow(expected, ref)) mockNet.runNetwork() assertFailsWith { future.getOrThrow() diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt index 9979ec31b0..95c283415e 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashIssueFlowTests.kt @@ -41,7 +41,7 @@ class CashIssueFlowTests { fun `issue some cash`() { val expected = 500.DOLLARS val ref = OpaqueBytes.of(0x01) - val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary)) mockNet.runNetwork() val issueTx = future.getOrThrow().stx val output = issueTx.tx.outputsOfType().single() @@ -52,7 +52,7 @@ class CashIssueFlowTests { fun `issue zero cash`() { val expected = 0.DOLLARS val ref = OpaqueBytes.of(0x01) - val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary)) mockNet.runNetwork() assertFailsWith { future.getOrThrow() diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt index de51e3b702..6380a4b9a6 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashPaymentFlowTests.kt @@ -35,7 +35,7 @@ class CashPaymentFlowTests { bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME) aliceNode = mockNet.createPartyNode(ALICE_NAME) - val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity)) future.getOrThrow() } @@ -56,8 +56,7 @@ class CashPaymentFlowTests { val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultService.trackBy(criteria) val (_, vaultUpdatesBankClient) = aliceNode.services.vaultService.trackBy(criteria) - val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expectedPayment, - payTo)).resultFuture + val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expectedPayment, payTo)) mockNet.runNetwork() future.getOrThrow() @@ -89,7 +88,7 @@ class CashPaymentFlowTests { val payTo = aliceNode.info.chooseIdentity() val expected = 4000.DOLLARS val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, - payTo)).resultFuture + payTo)) mockNet.runNetwork() assertFailsWith { future.getOrThrow() @@ -101,7 +100,7 @@ class CashPaymentFlowTests { val payTo = aliceNode.info.chooseIdentity() val expected = 0.DOLLARS val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, - payTo)).resultFuture + payTo)) mockNet.runNetwork() assertFailsWith { future.getOrThrow() diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 5da28ec519..2e25870301 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -92,7 +92,7 @@ class BFTNotaryServiceTests { addOutputState(DummyContract.SingleOwnerState(owner = info.chooseIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint) } // Create a new consensus while the redundant replica is sleeping: - services.startFlow(NotaryFlow.Client(trivialTx)).resultFuture + services.startFlow(NotaryFlow.Client(trivialTx)) } mockNet.runNetwork() f.getOrThrow() @@ -127,7 +127,7 @@ class BFTNotaryServiceTests { val flows = spendTxs.map { NotaryFlow.Client(it) } val stateMachines = flows.map { services.startFlow(it) } mockNet.runNetwork() - val results = stateMachines.map { Try.on { it.resultFuture.getOrThrow() } } + val results = stateMachines.map { Try.on { it.getOrThrow() } } val successfulIndex = results.mapIndexedNotNull { index, result -> if (result is Try.Success) { val signers = result.value.map { it.by } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt index 825c24b273..6e56ee45d3 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/RaftNotaryServiceTests.kt @@ -44,7 +44,7 @@ class RaftNotaryServiceTests { val firstSpendTx = bankA.services.signInitialTransaction(firstTxBuilder) val firstSpend = bankA.services.startFlow(NotaryFlow.Client(firstSpendTx)) - firstSpend.resultFuture.getOrThrow() + firstSpend.getOrThrow() val secondSpendBuilder = TransactionBuilder(defaultNotaryIdentity).withItems(inputState).run { val dummyState = DummyContract.SingleOwnerState(0, bankA.info.chooseIdentity()) @@ -55,7 +55,7 @@ class RaftNotaryServiceTests { val secondSpendTx = bankA.services.signInitialTransaction(secondSpendBuilder) val secondSpend = bankA.services.startFlow(NotaryFlow.Client(secondSpendTx)) - val ex = assertFailsWith(NotaryException::class) { secondSpend.resultFuture.getOrThrow() } + val ex = assertFailsWith(NotaryException::class) { secondSpend.getOrThrow() } val error = ex.error as NotaryError.Conflict assertEquals(error.txId, secondSpendTx.id) } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt index 04702e02bd..4322161a16 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/statemachine/FlowVersioningTest.kt @@ -22,7 +22,7 @@ class FlowVersioningTest : NodeBasedTest() { val bob = startNode(BOB_NAME, platformVersion = 3) bob.internals.installCoreFlow(PretendInitiatingCoreFlow::class, ::PretendInitiatedCoreFlow) val (alicePlatformVersionAccordingToBob, bobPlatformVersionAccordingToAlice) = alice.services.startFlow( - PretendInitiatingCoreFlow(bob.info.chooseIdentity())).resultFuture.getOrThrow() + PretendInitiatingCoreFlow(bob.info.chooseIdentity())).getOrThrow() assertThat(alicePlatformVersionAccordingToBob).isEqualTo(2) assertThat(bobPlatformVersionAccordingToAlice).isEqualTo(3) } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index abbe152173..cacf677ed7 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -3,9 +3,7 @@ package net.corda.services.messaging import co.paralleluniverse.fibers.Suspendable import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection -import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.random63BitValue -import net.corda.core.crypto.toStringShort import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowSession import net.corda.core.flows.InitiatedBy @@ -193,7 +191,7 @@ abstract class MQSecurityTest : NodeBasedTest() { bob.registerInitiatedFlow(ReceiveFlow::class.java) val bobParty = bob.info.chooseIdentity() // Perform a protocol exchange to force the peer queue to be created - alice.services.startFlow(SendFlow(bobParty, 0)).resultFuture.getOrThrow() + alice.services.startFlow(SendFlow(bobParty, 0)).getOrThrow() return bobParty } diff --git a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt index 42bdc9e0c8..3b5df29df8 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt @@ -67,7 +67,7 @@ class NetworkParametersTest { val alice = mockNet.createPartyNode(ALICE_NAME) assertThat(alice.services.networkMapCache.notaryIdentities).doesNotContain(fakeNotaryId) assertFails { - alice.services.startFlow(CashIssueFlow(500.DOLLARS, OpaqueBytes.of(0x01), fakeNotaryId)).resultFuture.getOrThrow() + alice.services.startFlow(CashIssueFlow(500.DOLLARS, OpaqueBytes.of(0x01), fakeNotaryId)).getOrThrow() } } diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index c5d1922a29..e17201cf46 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -536,7 +536,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { val buyerFlows: Observable> = buyerNode.registerInitiatedFlow(BuyerAcceptor::class.java) val firstBuyerFiber = buyerFlows.toFuture().map { it.stateMachine } val seller = SellerInitiator(buyer, notary, assetToSell, 1000.DOLLARS, anonymous) - val sellerResult = sellerNode.services.startFlow(seller).resultFuture + val sellerResult = sellerNode.services.startFlow(seller) return RunResult(firstBuyerFiber, sellerResult, seller.stateMachine.id) } diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index 4f1292cf80..bdb1381f57 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -74,7 +74,7 @@ class NotaryChangeTests { mockNet.runNetwork() - val newState = future.resultFuture.getOrThrow() + val newState = future.getOrThrow() assertEquals(newState.state.notary, newNotary) val loadedStateA = clientNodeA.services.loadState(newState.ref) val loadedStateB = clientNodeB.services.loadState(newState.ref) @@ -91,7 +91,7 @@ class NotaryChangeTests { mockNet.runNetwork() assertThatExceptionOfType(StateReplacementException::class.java).isThrownBy { - future.resultFuture.getOrThrow() + future.getOrThrow() } } @@ -104,7 +104,7 @@ class NotaryChangeTests { val flow = NotaryChangeFlow(state, newNotary) val future = clientNodeA.services.startFlow(flow) mockNet.runNetwork() - val newState = future.resultFuture.getOrThrow() + val newState = future.getOrThrow() assertEquals(newState.state.notary, newNotary) val recordedTx = clientNodeA.services.validatedTransactions.getTransaction(newState.ref.txhash)!! @@ -150,7 +150,7 @@ class NotaryChangeTests { val future = node.services.startFlow(flow) mockNet.runNetwork() - return future.resultFuture.getOrThrow() + return future.getOrThrow() } private fun moveState(state: StateAndRef, fromNode: StartedNode<*>, toNode: StartedNode<*>): StateAndRef { @@ -161,7 +161,7 @@ class NotaryChangeTests { val future = fromNode.services.startFlow(notaryFlow) mockNet.runNetwork() - val notarySignature = future.resultFuture.getOrThrow() + val notarySignature = future.getOrThrow() val finalTransaction = stx + notarySignature fromNode.services.recordTransactions(finalTransaction) diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index f40782a3e7..4e7405f47c 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -143,8 +143,8 @@ class ScheduledFlowTests { val N = 100 val futures = mutableListOf>() for (i in 0 until N) { - futures.add(aliceNode.services.startFlow(InsertInitialStateFlow(bob, notary)).resultFuture) - futures.add(bobNode.services.startFlow(InsertInitialStateFlow(alice, notary)).resultFuture) + futures.add(aliceNode.services.startFlow(InsertInitialStateFlow(bob, notary))) + futures.add(bobNode.services.startFlow(InsertInitialStateFlow(alice, notary))) } mockNet.waitQuiescent() diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 94b87c65e5..2ac326ff2a 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -38,7 +38,7 @@ import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.pumpReceive -import net.corda.testing.node.startFlow +import net.corda.testing.node.internal.startFlow import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index a60bd303cb..5cf3319fbb 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -19,8 +19,8 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.core.dummyCommand import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters -import net.corda.testing.node.startFlow import net.corda.testing.core.singleIdentity +import net.corda.testing.node.startFlow import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -120,11 +120,11 @@ class NotaryServiceTests { // Note that the notary will only return identical signatures when using deterministic signature // schemes (e.g. EdDSA) and when deterministic metadata is attached (no timestamps or nonces). // We only really care that both signatures are over the same transaction and by the same notary. - val sig1 = f1.resultFuture.getOrThrow().single() + val sig1 = f1.getOrThrow().single() assertEquals(sig1.by, notary.owningKey) assertTrue(sig1.isValid(stx.id)) - val sig2 = f2.resultFuture.getOrThrow().single() + val sig2 = f2.getOrThrow().single() assertEquals(sig2.by, notary.owningKey) assertTrue(sig2.isValid(stx.id)) } @@ -153,7 +153,7 @@ class NotaryServiceTests { mockNet.runNetwork() - val ex = assertFailsWith(NotaryException::class) { future.resultFuture.getOrThrow() } + val ex = assertFailsWith(NotaryException::class) { future.getOrThrow() } val notaryError = ex.error as NotaryError.Conflict assertEquals(notaryError.txId, stx2.id) notaryError.conflict.verified() @@ -161,7 +161,7 @@ class NotaryServiceTests { private fun runNotaryClient(stx: SignedTransaction): CordaFuture> { val flow = NotaryFlow.Client(stx) - val future = aliceServices.startFlow(flow).resultFuture + val future = aliceServices.startFlow(flow) mockNet.runNetwork() return future } diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 5a28634624..f7dd12b0cc 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -94,7 +94,7 @@ class ValidatingNotaryServiceTests { private fun runClient(stx: SignedTransaction): CordaFuture> { val flow = NotaryFlow.Client(stx) - val future = aliceServices.startFlow(flow).resultFuture + val future = aliceServices.startFlow(flow) mockNet.runNetwork() return future } diff --git a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index 1be2b70c09..bc35a1e7ad 100644 --- a/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -227,7 +227,7 @@ class NodeInterestRatesTest { val flow = FilteredRatesFlow(tx, oracle, fixOf, BigDecimal("0.675"), BigDecimal("0.1")) LogHelper.setLevel("rates") mockNet.runNetwork() - val future = aliceNode.services.startFlow(flow).resultFuture + val future = aliceNode.services.startFlow(flow) mockNet.runNetwork() future.getOrThrow() // We should now have a valid fix of our tx from the oracle. diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt index f651fc8e4e..aed3705c38 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt @@ -160,7 +160,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten val instigator = StartDealFlow( node2.info.chooseIdentity(), AutoOffer(mockNet.defaultNotaryIdentity, irs)) // TODO Pass notary as parameter to Simulation. - val instigatorTxFuture = node1.services.startFlow(instigator).resultFuture + val instigatorTxFuture = node1.services.startFlow(instigator) return allOf(instigatorTxFuture.toCompletableFuture(), acceptorTxFuture).thenCompose { instigatorTxFuture.toCompletableFuture() } } diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt index 17fdbc814a..e2c03d66a1 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/node/FlowStackSnapshotTest.kt @@ -290,7 +290,7 @@ class FlowStackSnapshotTest { val mockNet = MockNetwork(emptyList(), threadPerNode = true) val node = mockNet.createPartyNode() node.registerInitiatedFlow(DummyFlow::class.java) - node.services.startFlow(FlowStackSnapshotSerializationTestingFlow()).resultFuture.get() + node.services.startFlow(FlowStackSnapshotSerializationTestingFlow()).get() val thrown = try { // Due to the [MockNetwork] implementation, the easiest way to trigger object serialization process is at // the network stopping stage. diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 85b863ba6f..16e6b0d88d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -263,4 +263,4 @@ data class DriverParameters( fun setNotarySpecs(notarySpecs: List) = copy(notarySpecs = notarySpecs) fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) fun setJmxPolicy(jmxPolicy: JmxPolicy) = copy(jmxPolicy = jmxPolicy) -} +} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt index 85248cab2d..1561564756 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt @@ -2,6 +2,7 @@ package net.corda.testing.node +import net.corda.core.concurrent.CordaFuture import net.corda.core.context.Actor import net.corda.core.context.AuthServiceId import net.corda.core.context.InvocationContext @@ -60,13 +61,9 @@ fun testActor(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company I fun testContext(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company Inc.", "London", "GB")) = InvocationContext.rpc(testActor(owningLegalIdentity)) -/** - * Starts an already constructed flow. Note that you must be on the server thread to call this method. [InvocationContext] - * has origin [InvocationOrigin.RPC] and actor with id "Only For Testing". - */ -fun StartedNodeServices.startFlow(logic: FlowLogic): FlowStateMachine = startFlow(logic, newContext()).getOrThrow() - /** * Creates a new [InvocationContext] for testing purposes. */ fun StartedNodeServices.newContext() = testContext(myInfo.chooseIdentity().name) + +fun StartedNodeServices.startFlow(logic: FlowLogic): CordaFuture = startFlow(logic, newContext()).getOrThrow().resultFuture \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt index 7a5bfc4f71..92f51f191e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt @@ -2,12 +2,18 @@ package net.corda.testing.node.internal import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture +import net.corda.core.context.InvocationContext +import net.corda.core.context.InvocationOrigin +import net.corda.core.flows.FlowLogic +import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.times import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.millis import net.corda.core.utilities.seconds +import net.corda.node.services.api.StartedNodeServices +import net.corda.testing.node.newContext import org.slf4j.LoggerFactory import java.net.Socket import java.net.SocketException @@ -91,3 +97,5 @@ fun poll( class ListenProcessDeathException(hostAndPort: NetworkHostAndPort, listenProcess: Process) : CordaException("The process that was expected to listen on $hostAndPort has died with status: ${listenProcess.exitValue()}") + +fun StartedNodeServices.startFlow(logic: FlowLogic): FlowStateMachine = startFlow(logic, newContext()).getOrThrow() From 865364f283173efa03bcf2071d9a77106adc7ffc Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 30 Jan 2018 15:55:05 +0100 Subject: [PATCH 024/114] Upgrade Kotlin to 1.2.20 --- constants.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constants.properties b/constants.properties index b2c39c9ce2..930a39acd2 100644 --- a/constants.properties +++ b/constants.properties @@ -1,5 +1,5 @@ gradlePluginsVersion=3.0.5 -kotlinVersion=1.1.60 +kotlinVersion=1.2.20 platformVersion=2 guavaVersion=21.0 bouncycastleVersion=1.57 From 058e694d319d855435b180f3cad3a868a68a986d Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 6 Feb 2018 13:32:13 +0000 Subject: [PATCH 025/114] Minor: fix a couple of Kotlin build file issues that cropped up whilst upgrading --- build.gradle | 3 ++- samples/irs-demo/web/build.gradle | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 5d7b2353a3..09199763b3 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ buildscript { * https://issues.apache.org/jira/browse/ARTEMIS-1559 */ ext.artemis_version = '2.2.0' - ext.jackson_version = '2.9.2' + ext.jackson_version = '2.9.3' ext.jetty_version = '9.4.7.v20170914' ext.jersey_version = '2.25' ext.jolokia_version = '1.3.7' @@ -77,6 +77,7 @@ buildscript { mavenLocal() mavenCentral() jcenter() + // This repository is needed for Dokka until 0.9.16 is released. maven { url 'https://dl.bintray.com/kotlin/kotlin-eap/' } diff --git a/samples/irs-demo/web/build.gradle b/samples/irs-demo/web/build.gradle index ce592344f6..573433b3e9 100644 --- a/samples/irs-demo/web/build.gradle +++ b/samples/irs-demo/web/build.gradle @@ -64,7 +64,7 @@ dependencies { exclude module: "spring-boot-starter-logging" exclude module: "logback-classic" } - compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.9") + compile("com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version") compile project(":client:rpc") compile project(":client:jackson") compile project(":test-utils") From cbe947694d394e3ac76a1ec06e2077029d2f5956 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 7 Feb 2018 12:16:16 +0000 Subject: [PATCH 026/114] Minor: upgrade some Spring Boot deps and tweak the Jackson version it uses to avoid a conflict with Kotlin 1.2.20 --- client/jackson/build.gradle | 4 ++-- samples/irs-demo/build.gradle | 6 ++++-- samples/irs-demo/web/build.gradle | 6 ++++-- samples/network-visualiser/build.gradle | 4 +++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/client/jackson/build.gradle b/client/jackson/build.gradle index fadfdf9975..2fd7eb9dc7 100644 --- a/client/jackson/build.gradle +++ b/client/jackson/build.gradle @@ -8,11 +8,11 @@ dependencies { compile project(':core') testCompile project(':test-utils') - compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" // Jackson and its plugins: parsing to/from JSON and other textual formats. - compile "com.fasterxml.jackson.module:jackson-module-kotlin:${jackson_version}" + compile "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version" // Yaml is useful for parsing strings to method calls. compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jackson_version" // This adds support for java.time types. diff --git a/samples/irs-demo/build.gradle b/samples/irs-demo/build.gradle index c738eb6c48..e781ba6d81 100644 --- a/samples/irs-demo/build.gradle +++ b/samples/irs-demo/build.gradle @@ -1,12 +1,13 @@ buildscript { ext { - springBootVersion = '1.5.7.RELEASE' + springBootVersion = '1.5.10.RELEASE' } repositories { mavenCentral() } dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" + classpath "io.spring.gradle:dependency-management-plugin:1.0.4.RELEASE" } } @@ -16,6 +17,7 @@ buildscript { ext['artemis.version'] = "$artemis_version" ext['hibernate.version'] = "$hibernate_version" ext['selenium.version'] = "$selenium_version" +ext['jackson.version'] = "$jackson_version" apply plugin: 'java' apply plugin: 'kotlin' diff --git a/samples/irs-demo/web/build.gradle b/samples/irs-demo/web/build.gradle index 573433b3e9..9f92026c58 100644 --- a/samples/irs-demo/web/build.gradle +++ b/samples/irs-demo/web/build.gradle @@ -7,8 +7,9 @@ buildscript { mavenCentral() } dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") - classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}") + classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" + classpath "io.spring.gradle:dependency-management-plugin:1.0.4.RELEASE" + classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" classpath 'com.bmuschko:gradle-docker-plugin:3.2.1' classpath "org.yaml:snakeyaml:1.19" } @@ -47,6 +48,7 @@ clientDependencies { // See https://github.com/spring-gradle-plugins/dependency-management-plugin/blob/master/README.md#changing-the-value-of-a-version-property ext['artemis.version'] = "$artemis_version" ext['hibernate.version'] = "$hibernate_version" +ext['jackson.version'] = "$jackson_version" apply plugin: 'kotlin' apply plugin: 'kotlin-spring' diff --git a/samples/network-visualiser/build.gradle b/samples/network-visualiser/build.gradle index 9767030c79..543f5dbf92 100644 --- a/samples/network-visualiser/build.gradle +++ b/samples/network-visualiser/build.gradle @@ -6,7 +6,8 @@ buildscript { mavenCentral() } dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" + classpath "io.spring.gradle:dependency-management-plugin:1.0.4.RELEASE" } } @@ -16,6 +17,7 @@ buildscript { // This has to be repeated here as otherwise the order of files does matter ext['artemis.version'] = "$artemis_version" ext['hibernate.version'] = "$hibernate_version" +ext['jackson.version'] = "$jackson_version" apply plugin: 'java' From 6acff3a7df4019fa50363cde22340759403e5598 Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Thu, 8 Feb 2018 14:31:43 +0000 Subject: [PATCH 027/114] First approach to network parameters updates (#2412) * Network parameters updates Add two RPC methods networkParametersFeed and acceptNewNetworkParameters. Implementation of client handling of network parameters update event. Partial implementation of accepting new parameters and installing them on the node as well as node startup with updated parameters. Move reading of network parameters on startup to separate NetworkParametersReader class. Add tests. Move NetworkParameters and NotaryInfo classes to core. * Ignore evolvability test - to be fixed later * Add documentation on update process --- .../net/corda/core/internal/InternalUtils.kt | 25 +++++- .../net/corda/core/messaging/CordaRPCOps.kt | 43 ++++++++++ .../net/corda/core/node/NetworkParameters.kt | 45 ++++++++++ .../core/node/services/NetworkMapCache.kt | 1 - docs/source/network-map.rst | 34 ++++++++ .../corda/nodeapi/internal/SignedNodeInfo.kt | 10 +++ .../nodeapi/internal/crypto/X509Utilities.kt | 5 +- .../internal/network/NetworkBootstrapper.kt | 4 +- .../nodeapi/internal/network/NetworkMap.kt | 56 +++++-------- .../network/NetworkParametersCopier.kt | 12 +-- .../serialization/amqp/EvolvabilityTests.kt | 5 +- .../node/services/BFTNotaryServiceTests.kt | 2 +- .../node/services/network/NetworkMapTest.kt | 4 +- .../net/corda/node/internal/AbstractNode.kt | 55 +++--------- .../corda/node/internal/CordaRPCOpsImpl.kt | 14 ++++ .../node/internal/NetworkParametersReader.kt | 81 ++++++++++++++++++ .../node/internal/RpcAuthorisationProxy.kt | 8 ++ .../node/services/api/ServiceHubInternal.kt | 2 + .../node/services/network/NetworkMapClient.kt | 83 +++++++++++++++---- .../network/PersistentNetworkMapCache.kt | 2 +- .../node/internal/NetworkParametersTest.kt | 4 +- .../services/network/NetworkMapClientTest.kt | 24 ++++++ .../services/network/NetworkMapUpdaterTest.kt | 81 ++++++++++++++++-- .../network/NetworkParametersReaderTest.kt | 63 ++++++++++++++ .../kotlin/net/corda/testing/node/MockNode.kt | 2 +- .../testing/node/internal/DriverDSLImpl.kt | 2 +- .../node/internal/network/NetworkMapServer.kt | 45 +++++++++- .../common/internal/ParametersUtilities.kt | 4 +- .../corda/demobench/model/NodeController.kt | 4 +- 29 files changed, 588 insertions(+), 132 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt create mode 100644 node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt create mode 100644 node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 6d22445cb6..32283dafcf 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -3,16 +3,16 @@ package net.corda.core.internal import net.corda.core.cordapp.CordappProvider -import net.corda.core.crypto.Crypto -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.sha256 +import net.corda.core.crypto.* import net.corda.core.identity.CordaX500Name import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction +import net.corda.core.utilities.OpaqueBytes import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x500.style.BCStyle @@ -30,6 +30,7 @@ import java.nio.charset.Charset import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.* import java.nio.file.attribute.FileAttribute +import java.security.KeyPair import java.security.PrivateKey import java.security.cert.X509Certificate import java.time.Duration @@ -307,6 +308,16 @@ val KClass<*>.packageName: String get() = java.`package`.name fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection +fun URL.post(serializedData: OpaqueBytes) { + openHttpConnection().apply { + doOutput = true + requestMethod = "POST" + setRequestProperty("Content-Type", "application/octet-stream") + outputStream.use { serializedData.open().copyTo(it) } + checkOkResponse() + } +} + fun HttpURLConnection.checkOkResponse() { if (responseCode != 200) { val message = errorStream.use { it.reader().readText() } @@ -353,3 +364,11 @@ fun T.signWithCert(privateKey: PrivateKey, certificate: X509Certificat val signature = Crypto.doSign(privateKey, serialised.bytes) return SignedDataWithCert(serialised, DigitalSignatureWithCert(certificate, signature)) } + +inline fun SerializedBytes.sign(signer: (SerializedBytes) -> DigitalSignature.WithKey): SignedData { + return SignedData(this, signer(this)) +} + +inline fun SerializedBytes.sign(keyPair: KeyPair): SignedData { + return SignedData(this, keyPair.sign(this.bytes)) +} diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index 5d56f9d638..e5a168fb5f 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -13,6 +13,7 @@ import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.NetworkMapCache @@ -23,6 +24,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.Try import rx.Observable +import java.io.IOException import java.io.InputStream import java.security.PublicKey import java.time.Instant @@ -72,6 +74,24 @@ sealed class StateMachineUpdate { data class Removed(override val id: StateMachineRunId, val result: Try<*>) : StateMachineUpdate() } +// DOCSTART 1 +/** + * Data class containing information about the scheduled network parameters update. The info is emitted every time node + * receives network map with [ParametersUpdate] which wasn't seen before. For more information see: [CordaRPCOps.networkParametersFeed] and [CordaRPCOps.acceptNewNetworkParameters]. + * @property hash new [NetworkParameters] hash + * @property parameters new [NetworkParameters] data structure + * @property description description of the update + * @property updateDeadline deadline for accepting this update using [CordaRPCOps.acceptNewNetworkParameters] + */ +@CordaSerializable +data class ParametersUpdateInfo( + val hash: SecureHash, + val parameters: NetworkParameters, + val description: String, + val updateDeadline: Instant +) +// DOCEND 1 + @CordaSerializable data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRunId, val transactionId: SecureHash) @@ -205,6 +225,29 @@ interface CordaRPCOps : RPCOps { @RPCReturnsObservables fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange> + /** + * Returns [DataFeed] object containing information on currently scheduled parameters update (null if none are currently scheduled) + * and observable with future update events. Any update that occurs before the deadline automatically cancels the current one. + * Only the latest update can be accepted. + * Note: This operation may be restricted only to node administrators. + */ + // TODO This operation should be restricted to just node admins. + @RPCReturnsObservables + fun networkParametersFeed(): DataFeed + + /** + * Accept network parameters with given hash, hash is obtained through [networkParametersFeed] method. + * Information is sent back to the zone operator that the node accepted the parameters update - this process cannot be + * undone. + * Only parameters that are scheduled for update can be accepted, if different hash is provided this method will fail. + * Note: This operation may be restricted only to node administrators. + * @param parametersHash hash of network parameters to accept + * @throws IllegalArgumentException if network map advertises update with different parameters hash then the one accepted by node's operator. + * @throws IOException if failed to send the approval to network map + */ + // TODO This operation should be restricted to just node admins. + fun acceptNewNetworkParameters(parametersHash: SecureHash) + /** * Start the given flow with the given arguments. [logicType] must be annotated * with [net.corda.core.flows.StartableByRPC]. diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt new file mode 100644 index 0000000000..f7076a8316 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt @@ -0,0 +1,45 @@ +package net.corda.core.node + +import net.corda.core.identity.Party +import net.corda.core.serialization.CordaSerializable +import java.time.Instant + +/** + * Network parameters are a set of values that every node participating in the zone needs to agree on and use to + * correctly interoperate with each other. + * @property minimumPlatformVersion Minimum version of Corda platform that is required for nodes in the network. + * @property notaries List of well known and trusted notary identities with information on validation type. + * @property maxMessageSize Maximum P2P message sent over the wire in bytes. + * @property maxTransactionSize Maximum permitted transaction size in bytes. + * @property modifiedTime Last modification time of network parameters set. + * @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set + * of parameters. + */ +// TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network. +// It needs separate design. +// TODO Currently maxTransactionSize is not wired. +@CordaSerializable +data class NetworkParameters( + val minimumPlatformVersion: Int, + val notaries: List, + val maxMessageSize: Int, + val maxTransactionSize: Int, + val modifiedTime: Instant, + val epoch: Int +) { + init { + require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" } + require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" } + require(epoch > 0) { "epoch must be at least 1" } + require(maxMessageSize > 0) { "maxMessageSize must be at least 1" } + require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" } + } +} + +/** + * Data class storing information about notaries available in the network. + * @property identity Identity of the notary (note that it can be an identity of the distributed node). + * @property validating Indicates if the notary is validating. + */ +@CordaSerializable +data class NotaryInfo(val identity: Party, val validating: Boolean) diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index 6cfe29dbec..69ab1a7307 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -2,7 +2,6 @@ package net.corda.core.node.services import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture -import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst index 160f4c0401..0c8da04e1f 100644 --- a/docs/source/network-map.rst +++ b/docs/source/network-map.rst @@ -22,6 +22,8 @@ The set of REST end-points for the network map service are as follows. +================+=========================================+==============================================================================================================================================+ | POST | /network-map/publish | For the node to upload its signed ``NodeInfo`` object to the network map. | +----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+ +| POST | /network-map/ack-parameters | For the node operator to acknowledge network map that new parameters were accepted for future update. | ++----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+ | GET | /network-map | Retrieve the current signed network map object. The entire object is signed with the network map certificate which is also attached. | +----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+ | GET | /network-map/node-info/{hash} | Retrieve a signed ``NodeInfo`` as specified in the network map object. | @@ -77,3 +79,35 @@ More parameters will be added in future releases to regulate things like allowed offline before it is evicted from the zone, whether or not IPv6 connectivity is required for zone members, required cryptographic algorithms and rollout schedules (e.g. for moving to post quantum cryptography), parameters related to SGX and so on. + +Network parameters update process +--------------------------------- + +In case of the need to change network parameters Corda zone operator will start the update process. There are many reasons +that may lead to this decision: we discovered that some new fields have to be added to enable smooth network interoperability or change +of the existing compatibility constants is required due to upgrade or security reasons. + +To synchronize all nodes in the compatibility zone to use the new set of the network parameters two RPC methods exist. The process +requires human interaction and approval of the change. + +When the update is about to happen the network map service starts to advertise the additional information with the usual network map +data. It includes new network parameters hash, description of the change and the update deadline. Node queries network map server +for the new set of parameters and emits ``ParametersUpdateInfo`` via ``CordaRPCOps::networkParametersFeed`` method to inform +node operator about the event. + +.. container:: codeset + + .. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt + :language: kotlin + :start-after: DOCSTART 1 + :end-before: DOCEND 1 + +Node administrator can review the change and decide if is going to accept it. The approval should be done before ``updateDeadline``. +Nodes that don't approve before the deadline will be removed from the network map. +If the network operator starts advertising a different set of new parameters then that new set overrides the previous set. Only the latest update can be accepted. +To send back parameters approval to the zone operator RPC method ``fun acceptNewNetworkParameters(parametersHash: SecureHash)`` +has to be called with ``parametersHash`` from update. Notice that the process cannot be undone. + +Next time the node polls network map after the deadline the advertised network parameters will be the updated ones. Previous set +of parameters will no longer be valid. At this point the node will automatically shutdown and will require the node operator +to bring it back again. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt index ea7de12aa2..d328674a69 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/SignedNodeInfo.kt @@ -7,6 +7,8 @@ import net.corda.core.node.NodeInfo import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import java.security.PublicKey import java.security.SignatureException /** @@ -44,3 +46,11 @@ class SignedNodeInfo(val raw: SerializedBytes, val signatures: List) -> DigitalSignature): SignedNodeInfo { + // For now we exclude any composite identities, see [SignedNodeInfo] + val owningKeys = legalIdentities.map { it.owningKey }.filter { it !is CompositeKey } + val serialised = serialize() + val signatures = owningKeys.map { signer(it, serialised) } + return SignedNodeInfo(serialised, signatures) +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index f950ccdc7b..0b545290df 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -4,10 +4,7 @@ import net.corda.core.CordaOID import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.random63BitValue -import net.corda.core.internal.CertRole -import net.corda.core.internal.reader -import net.corda.core.internal.uncheckedCast -import net.corda.core.internal.writer +import net.corda.core.internal.* import net.corda.core.utilities.days import net.corda.core.utilities.millis import org.bouncycastle.asn1.* diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index a9b16f61e9..2db900832c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt @@ -5,7 +5,9 @@ import net.corda.cordform.CordformNode import net.corda.core.identity.Party import net.corda.core.internal.* import net.corda.core.internal.concurrent.fork +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo +import net.corda.core.node.NotaryInfo import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.deserialize import net.corda.core.serialization.internal.SerializationEnvironmentImpl @@ -167,7 +169,7 @@ class NetworkBootstrapper { epoch = 1 ), overwriteFile = true) - nodeDirs.forEach(copier::install) + nodeDirs.forEach { copier.install(it) } } private fun NotaryInfo.prettyPrint(): String = "${identity.name} (${if (validating) "" else "non-"}validating)" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt index 118683f753..52841d2946 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt @@ -1,58 +1,48 @@ package net.corda.nodeapi.internal.network import net.corda.core.crypto.SecureHash -import net.corda.core.identity.Party import net.corda.core.internal.CertRole import net.corda.core.internal.SignedDataWithCert +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.serialization.CordaSerializable import net.corda.nodeapi.internal.crypto.X509Utilities import java.security.cert.X509Certificate import java.time.Instant + const val NETWORK_PARAMS_FILE_NAME = "network-parameters" +const val NETWORK_PARAMS_UPDATE_FILE_NAME = "network-parameters-update" /** - * Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes. + * Data structure representing the network map available from the HTTP network map service as a serialised blob. + * @property nodeInfoHashes list of network participant's [NodeInfo] hashes + * @property networkParameterHash hash of the current active [NetworkParameters] + * @property parametersUpdate if present means that network operator has scheduled an update of the network parameters */ @CordaSerializable -data class NetworkMap(val nodeInfoHashes: List, val networkParameterHash: SecureHash) +data class NetworkMap( + val nodeInfoHashes: List, + val networkParameterHash: SecureHash, + val parametersUpdate: ParametersUpdate? +) /** - * @property minimumPlatformVersion Minimum version of Corda platform that is required for nodes in the network. - * @property notaries List of well known and trusted notary identities with information on validation type. - * @property maxMessageSize Maximum P2P message sent over the wire in bytes. - * @property maxTransactionSize Maximum permitted transaction size in bytes. - * @property modifiedTime - * @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set - * of parameters. + * Data class representing scheduled network parameters update. + * @property newParametersHash Hash of the new [NetworkParameters] which can be requested from the network map + * @property description Short description of the update + * @property updateDeadline deadline by which new network parameters need to be accepted, after this date network operator + * can switch to new parameters which will result in getting nodes with old parameters out of the network */ -// TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network. -// It needs separate design. -// TODO Currently maxTransactionSize is not wired. @CordaSerializable -data class NetworkParameters( - val minimumPlatformVersion: Int, - val notaries: List, - val maxMessageSize: Int, - val maxTransactionSize: Int, - val modifiedTime: Instant, - val epoch: Int -) { - init { - require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" } - require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" } - require(epoch > 0) { "epoch must be at least 1" } - require(maxMessageSize > 0) { "maxMessageSize must be at least 1" } - require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" } - } -} - -@CordaSerializable -data class NotaryInfo(val identity: Party, val validating: Boolean) +data class ParametersUpdate( + val newParametersHash: SecureHash, + val description: String, + val updateDeadline: Instant +) fun SignedDataWithCert.verifiedNetworkMapCert(rootCert: X509Certificate): T { require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" } X509Utilities.validateCertificateChain(rootCert, sig.by, rootCert) return verified() -} \ No newline at end of file +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt index fdcb28fef2..18376251a7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkParametersCopier.kt @@ -1,8 +1,7 @@ package net.corda.nodeapi.internal.network -import net.corda.core.internal.copyTo -import net.corda.core.internal.div -import net.corda.core.internal.signWithCert +import net.corda.core.internal.* +import net.corda.core.node.NetworkParameters import net.corda.core.serialization.serialize import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair @@ -13,7 +12,9 @@ import java.nio.file.StandardCopyOption class NetworkParametersCopier( networkParameters: NetworkParameters, networkMapCa: CertificateAndKeyPair = createDevNetworkMapCa(), - overwriteFile: Boolean = false + overwriteFile: Boolean = false, + @VisibleForTesting + val update: Boolean = false ) { private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray() private val serialisedSignedNetParams = networkParameters.signWithCert( @@ -22,8 +23,9 @@ class NetworkParametersCopier( ).serialize() fun install(nodeDir: Path) { + val fileName = if (update) NETWORK_PARAMS_UPDATE_FILE_NAME else NETWORK_PARAMS_FILE_NAME try { - serialisedSignedNetParams.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions) + serialisedSignedNetParams.open().copyTo(nodeDir / fileName, *copyOptions) } catch (e: FileAlreadyExistsException) { // This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we // ignore this exception as we're happy with the existing file. diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index 0fce099955..b48ae16941 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -3,10 +3,10 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.crypto.Crypto.generateKeyPair import net.corda.core.crypto.SignedData import net.corda.core.crypto.sign +import net.corda.core.node.NetworkParameters +import net.corda.core.node.NotaryInfo import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.SerializedBytes -import net.corda.nodeapi.internal.network.NetworkParameters -import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.TestIdentity @@ -485,6 +485,7 @@ class EvolvabilityTests { // the resulting file and add to the repo, changing the filename as appropriate // @Test + @Ignore("Test fails after moving NetworkParameters and NotaryInfo into core from node-api") fun readBrokenNetworkParameters(){ val sf = testDefaultFactory() sf.register(net.corda.nodeapi.internal.serialization.amqp.custom.InstantSerializer(sf)) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 2e25870301..c5a036f09f 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -25,7 +25,7 @@ import net.corda.node.services.transactions.minClusterSize import net.corda.node.services.transactions.minCorrectReplicas import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.network.NetworkParametersCopier -import net.corda.nodeapi.internal.network.NotaryInfo +import net.corda.core.node.NotaryInfo import net.corda.testing.core.chooseIdentity import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.contracts.DummyContract diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt index 833691cae2..53713ee6d2 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NetworkMapTest.kt @@ -8,8 +8,8 @@ import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds +import net.corda.core.node.NetworkParameters import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME -import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.SerializationEnvironmentRule @@ -45,7 +45,7 @@ class NetworkMapTest { networkMapServer = NetworkMapServer(cacheTimeout, portAllocation.nextHostAndPort()) val address = networkMapServer.start() compatibilityZone = CompatibilityZoneParams(URL("http://$address"), publishNotaries = { - networkMapServer.networkParameters = testNetworkParameters(it, modifiedTime = Instant.ofEpochMilli(random63BitValue())) + networkMapServer.networkParameters = testNetworkParameters(it, modifiedTime = Instant.ofEpochMilli(random63BitValue()), epoch = 2) }) } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 30e484471f..94309e4c5f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -8,8 +8,6 @@ import net.corda.confidential.SwapIdentitiesHandler import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.context.InvocationContext -import net.corda.core.crypto.CompositeKey -import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.sign import net.corda.core.flows.* import net.corda.core.identity.CordaX500Name @@ -54,14 +52,11 @@ import net.corda.node.services.vault.VaultSoftLockManager import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AffinityExecutor import net.corda.nodeapi.internal.DevIdentityGenerator -import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME -import net.corda.nodeapi.internal.network.NetworkParameters -import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.HibernateConfiguration +import net.corda.nodeapi.internal.sign import net.corda.nodeapi.internal.storeLegalIdentity import org.apache.activemq.artemis.utils.ReusableLatch import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry @@ -137,7 +132,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, protected val runOnStop = ArrayList<() -> Any?>() private val _nodeReadyFuture = openFuture() protected var networkMapClient: NetworkMapClient? = null - + protected lateinit var networkMapUpdater: NetworkMapUpdater lateinit var securityManager: RPCSecurityManager /** Completes once the node has successfully registered with the network map service @@ -165,14 +160,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, validateKeystore() } - private inline fun signNodeInfo(nodeInfo: NodeInfo, sign: (PublicKey, SerializedBytes) -> DigitalSignature): SignedNodeInfo { - // For now we exclude any composite identities, see [SignedNodeInfo] - val owningKeys = nodeInfo.legalIdentities.map { it.owningKey }.filter { it !is CompositeKey } - val serialised = nodeInfo.serialize() - val signatures = owningKeys.map { sign(it, serialised) } - return SignedNodeInfo(serialised, signatures) - } - open fun generateAndSaveNodeInfo(): NodeInfo { check(started == null) { "Node has already been started" } log.info("Generating nodeInfo ...") @@ -185,7 +172,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList()) persistentNetworkMapCache.start() val (keyPairs, info) = initNodeInfo(persistentNetworkMapCache, identity, identityKeyPair) - val signedNodeInfo = signNodeInfo(info) { publicKey, serialised -> + val signedNodeInfo = info.sign { publicKey, serialised -> val privateKey = keyPairs.single { it.public == publicKey }.private privateKey.sign(serialised.bytes) } @@ -202,7 +189,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) val identityService = makeIdentityService(identity.certificate) networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) } - retrieveNetworkParameters(identityService.trustRoot) + networkParameters = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory).networkParameters + check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { + "Node's platform version is lower than network's required minimumPlatformVersion" + } // Do all of this in a database transaction so anything that might need a connection has one. val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database -> val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries).start(), identityService) @@ -241,14 +231,15 @@ abstract class AbstractNode(val configuration: NodeConfiguration, startShell(rpcOps) Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService) } - val networkMapUpdater = NetworkMapUpdater(services.networkMapCache, + networkMapUpdater = NetworkMapUpdater(services.networkMapCache, NodeInfoWatcher(configuration.baseDirectory, getRxIoScheduler(), Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)), networkMapClient, - networkParameters.serialize().hash) + networkParameters.serialize().hash, + configuration.baseDirectory) runOnStop += networkMapUpdater::close networkMapUpdater.updateNodeInfo(services.myInfo) { - signNodeInfo(it) { publicKey, serialised -> + it.sign { publicKey, serialised -> services.keyManagementService.sign(serialised.bytes, publicKey).withoutKey() } } @@ -633,29 +624,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, return PersistentKeyManagementService(identityService, keyPairs) } - private fun retrieveNetworkParameters(trustRoot: X509Certificate) { - val networkParamsFile = configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME - - networkParameters = if (networkParamsFile.exists()) { - networkParamsFile.readAll().deserialize>().verifiedNetworkMapCert(trustRoot) - } else { - log.info("No network-parameters file found. Expecting network parameters to be available from the network map.") - val networkMapClient = checkNotNull(networkMapClient) { - "Node hasn't been configured to connect to a network map from which to get the network parameters" - } - val (networkMap, _) = networkMapClient.getNetworkMap() - val signedParams = networkMapClient.getNetworkParameters(networkMap.networkParameterHash) - val verifiedParams = signedParams.verifiedNetworkMapCert(trustRoot) - signedParams.serialize().open().copyTo(configuration.baseDirectory / NETWORK_PARAMS_FILE_NAME) - verifiedParams - } - - log.info("Loaded network parameters: $networkParameters") - check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { - "Node's platform version is lower than network's required minimumPlatformVersion" - } - } - private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService { val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service") return notaryConfig.run { @@ -785,6 +753,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, override val networkService: MessagingService get() = network override val clock: Clock get() = platformClock override val configuration: NodeConfiguration get() = this@AbstractNode.configuration + override val networkMapUpdater: NetworkMapUpdater get() = this@AbstractNode.networkMapUpdater override fun cordaService(type: Class): T { require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" } return cordappServices.getInstance(type) ?: throw IllegalArgumentException("Corda service ${type.name} does not exist") diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index 7b8d3b5754..8fb66510e0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -13,12 +13,14 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.sign import net.corda.core.messaging.* import net.corda.core.node.NodeInfo import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.Vault import net.corda.core.node.services.vault.* +import net.corda.core.serialization.serialize import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow @@ -48,6 +50,18 @@ internal class CordaRPCOpsImpl( return snapshot } + override fun networkParametersFeed(): DataFeed { + return services.networkMapUpdater.track() + } + + override fun acceptNewNetworkParameters(parametersHash: SecureHash) { + services.networkMapUpdater.acceptNewNetworkParameters( + parametersHash, + // TODO When multiple identities design will be better specified this should be signature from node operator. + { hash -> hash.serialize().sign { services.keyManagementService.sign(it.bytes, services.myInfo.legalIdentities[0].owningKey) } } + ) + } + override fun networkMapFeed(): DataFeed, NetworkMapCache.MapChange> { return database.transaction { services.networkMapCache.track() diff --git a/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt b/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt new file mode 100644 index 0000000000..f34263f707 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/NetworkParametersReader.kt @@ -0,0 +1,81 @@ +package net.corda.node.internal + +import net.corda.core.crypto.SecureHash +import net.corda.core.internal.* +import net.corda.core.node.NetworkParameters +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.contextLogger +import net.corda.node.services.network.NetworkMapClient +import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME +import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME +import net.corda.nodeapi.internal.network.verifiedNetworkMapCert +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import java.security.cert.X509Certificate + +class NetworkParametersReader(private val trustRoot: X509Certificate, + private val networkMapClient: NetworkMapClient?, + private val baseDirectory: Path) { + companion object { + private val logger = contextLogger() + } + + private val networkParamsFile = baseDirectory / NETWORK_PARAMS_FILE_NAME + private val parametersUpdateFile = baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME + val networkParameters by lazy { retrieveNetworkParameters() } + + private fun retrieveNetworkParameters(): NetworkParameters { + val advertisedParametersHash = networkMapClient?.getNetworkMap()?.networkMap?.networkParameterHash + val signedParametersFromFile = if (networkParamsFile.exists()) { + networkParamsFile.readAll().deserialize>() + } else { + null + } + val parameters = if (advertisedParametersHash != null) { + // TODO On one hand we have node starting without parameters and just accepting them by default, + // on the other we have parameters update process - it needs to be unified. Say you start the node, you don't have matching parameters, + // you get them from network map, but you have to run the approval step. + if (signedParametersFromFile == null) { // Node joins for the first time. + downloadParameters(trustRoot, advertisedParametersHash) + } + else if (signedParametersFromFile.raw.hash == advertisedParametersHash) { // Restarted with the same parameters. + signedParametersFromFile.verifiedNetworkMapCert(trustRoot) + } else { // Update case. + readParametersUpdate(advertisedParametersHash, signedParametersFromFile.raw.hash).verifiedNetworkMapCert(trustRoot) + } + } else { // No compatibility zone configured. Node should proceed with parameters from file. + signedParametersFromFile?.verifiedNetworkMapCert(trustRoot) ?: throw IllegalArgumentException("Couldn't find network parameters file and compatibility zone wasn't configured") + } + logger.info("Loaded network parameters: $parameters") + return parameters + } + + private fun readParametersUpdate(advertisedParametersHash: SecureHash, previousParametersHash: SecureHash): SignedDataWithCert { + if (!parametersUpdateFile.exists()) { + throw IllegalArgumentException("Node uses parameters with hash: $previousParametersHash " + + "but network map is advertising: ${advertisedParametersHash}.\n" + + "Please update node to use correct network parameters file.") + } + val signedUpdatedParameters = parametersUpdateFile.readAll().deserialize>() + if (signedUpdatedParameters.raw.hash != advertisedParametersHash) { + throw IllegalArgumentException("Both network parameters and network parameters update files don't match" + + "parameters advertised by network map.\n" + + "Please update node to use correct network parameters file.") + } + parametersUpdateFile.moveTo(networkParamsFile, StandardCopyOption.REPLACE_EXISTING) + return signedUpdatedParameters + } + + // Used only when node joins for the first time. + private fun downloadParameters(trustRoot: X509Certificate, parametersHash: SecureHash): NetworkParameters { + logger.info("No network-parameters file found. Expecting network parameters to be available from the network map.") + val networkMapClient = checkNotNull(networkMapClient) { + "Node hasn't been configured to connect to a network map from which to get the network parameters" + } + val signedParams = networkMapClient.getNetworkParameters(parametersHash) + val verifiedParams = signedParams.verifiedNetworkMapCert(trustRoot) + signedParams.serialize().open().copyTo(baseDirectory / NETWORK_PARAMS_FILE_NAME) + return verifiedParams + } +} diff --git a/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt index 1264935c26..29ac3dc845 100644 --- a/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt +++ b/node/src/main/kotlin/net/corda/node/internal/RpcAuthorisationProxy.kt @@ -9,6 +9,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.DataFeed +import net.corda.core.messaging.ParametersUpdateInfo import net.corda.core.node.NodeInfo import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.NetworkMapCache @@ -20,6 +21,13 @@ import java.security.PublicKey // TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140 class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val context: () -> RpcAuthContext) : CordaRPCOps { + override fun networkParametersFeed(): DataFeed = guard("networkParametersFeed") { + implementation.networkParametersFeed() + } + + override fun acceptNewNetworkParameters(parametersHash: SecureHash) = guard("acceptNewNetworkParameters") { + implementation.acceptNewNetworkParameters(parametersHash) + } override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash = guard("uploadAttachmentWithMetadata") { implementation.uploadAttachmentWithMetadata(jar, uploader, filename) diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index e0094054a1..f1c9d95db9 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -20,6 +20,7 @@ import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.cordapp.CordappProviderInternal import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.MessagingService +import net.corda.node.services.network.NetworkMapUpdater import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -64,6 +65,7 @@ interface ServiceHubInternal : ServiceHub { val networkService: MessagingService val database: CordaPersistence val configuration: NodeConfiguration + val networkMapUpdater: NetworkMapUpdater override val cordappProvider: CordappProviderInternal override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { require(txs.any()) { "No transactions passed in for recording" } diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt index 39b4a2eb31..2b56d933c4 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -2,11 +2,13 @@ package net.corda.node.services.network import com.google.common.util.concurrent.MoreExecutors import net.corda.core.crypto.SecureHash -import net.corda.core.internal.SignedDataWithCert -import net.corda.core.internal.checkOkResponse -import net.corda.core.internal.openHttpConnection -import net.corda.core.internal.responseAs +import net.corda.core.crypto.SignedData +import net.corda.core.internal.* +import net.corda.core.messaging.DataFeed +import net.corda.core.messaging.ParametersUpdateInfo +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger import net.corda.core.utilities.minutes @@ -16,40 +18,42 @@ import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.utilities.NamedThreadFactory import net.corda.node.utilities.registration.cacheControl import net.corda.nodeapi.internal.SignedNodeInfo +import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.NetworkMap -import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.nodeapi.internal.network.ParametersUpdate import net.corda.nodeapi.internal.network.verifiedNetworkMapCert -import okhttp3.CacheControl -import okhttp3.Headers import rx.Subscription +import rx.subjects.PublishSubject import java.io.BufferedReader import java.io.Closeable import java.net.URL +import java.nio.file.Path +import java.nio.file.StandardCopyOption import java.security.cert.X509Certificate import java.time.Duration import java.util.concurrent.Executors import java.util.concurrent.TimeUnit -class NetworkMapClient(compatibilityZoneURL: URL, private val trustedRoot: X509Certificate) { +class NetworkMapClient(compatibilityZoneURL: URL, val trustedRoot: X509Certificate) { companion object { private val logger = contextLogger() } - private val networkMapUrl = URL("$compatibilityZoneURL/network-map") fun publish(signedNodeInfo: SignedNodeInfo) { val publishURL = URL("$networkMapUrl/publish") logger.trace { "Publishing NodeInfo to $publishURL." } - publishURL.openHttpConnection().apply { - doOutput = true - requestMethod = "POST" - setRequestProperty("Content-Type", "application/octet-stream") - outputStream.use { signedNodeInfo.serialize().open().copyTo(it) } - checkOkResponse() - } + publishURL.post(signedNodeInfo.serialize()) logger.trace { "Published NodeInfo to $publishURL successfully." } } + fun ackNetworkParametersUpdate(signedParametersHash: SignedData) { + val ackURL = URL("$networkMapUrl/ack-parameters") + logger.trace { "Sending network parameters with hash ${signedParametersHash.raw.deserialize()} approval to $ackURL." } + ackURL.post(signedParametersHash.serialize()) + logger.trace { "Sent network parameters approval to $ackURL successfully." } + } + fun getNetworkMap(): NetworkMapResponse { logger.trace { "Fetching network map update from $networkMapUrl." } val connection = networkMapUrl.openHttpConnection() @@ -90,12 +94,26 @@ data class NetworkMapResponse(val networkMap: NetworkMap, val cacheMaxAge: Durat class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, private val fileWatcher: NodeInfoWatcher, private val networkMapClient: NetworkMapClient?, - private val currentParametersHash: SecureHash) : Closeable { + private val currentParametersHash: SecureHash, + private val baseDirectory: Path) : Closeable { companion object { private val logger = contextLogger() private val retryInterval = 1.minutes } + private var newNetworkParameters: Pair>? = null + + fun track(): DataFeed { + val currentUpdateInfo = newNetworkParameters?.let { + ParametersUpdateInfo(it.first.newParametersHash, it.second.verified(), it.first.description, it.first.updateDeadline) + } + return DataFeed( + currentUpdateInfo, + parametersUpdatesTrack + ) + } + + private val parametersUpdatesTrack: PublishSubject = PublishSubject.create() private val executor = Executors.newSingleThreadScheduledExecutor(NamedThreadFactory("Network Map Updater Thread", Executors.defaultThreadFactory())) private var fileWatcherSubscription: Subscription? = null @@ -130,8 +148,9 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, override fun run() { val nextScheduleDelay = try { val (networkMap, cacheTimeout) = networkMapClient.getNetworkMap() - // TODO NetworkParameters updates are not implemented yet. Every mismatch should result in node shutdown. + networkMap.parametersUpdate?.let { handleUpdateNetworkParameters(it) } if (currentParametersHash != networkMap.networkParameterHash) { + // TODO This needs special handling (node omitted update process/didn't accept new parameters or didn't restart on updateDeadline) logger.error("Node is using parameters with hash: $currentParametersHash but network map is advertising: ${networkMap.networkParameterHash}.\n" + "Please update node to use correct network parameters file.\"") System.exit(1) @@ -181,4 +200,32 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, } executor.submit(task) } + + private fun handleUpdateNetworkParameters(update: ParametersUpdate) { + if (update.newParametersHash == newNetworkParameters?.first?.newParametersHash) { // This update was handled already. + return + } + val newParameters = networkMapClient?.getNetworkParameters(update.newParametersHash) + if (newParameters != null) { + logger.info("Downloaded new network parameters: $newParameters from the update: $update") + newNetworkParameters = Pair(update, newParameters) + parametersUpdatesTrack.onNext(ParametersUpdateInfo(update.newParametersHash, newParameters.verifiedNetworkMapCert(networkMapClient!!.trustedRoot), update.description, update.updateDeadline)) + } + } + + fun acceptNewNetworkParameters(parametersHash: SecureHash, sign: (SecureHash) -> SignedData) { + networkMapClient ?: throw IllegalStateException("Network parameters updates are not support without compatibility zone configured") + // TODO This scenario will happen if node was restarted and didn't download parameters yet, but we accepted them. Add persisting of newest parameters from update. + val (_, newParams) = newNetworkParameters ?: throw IllegalArgumentException("Couldn't find parameters update for the hash: $parametersHash") + val newParametersHash = newParams.verifiedNetworkMapCert(networkMapClient.trustedRoot).serialize().hash // We should check that we sign the right data structure hash. + if (parametersHash == newParametersHash) { + // The latest parameters have priority. + newParams.serialize() + .open() + .copyTo(baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME, StandardCopyOption.REPLACE_EXISTING) + networkMapClient.ackNetworkParametersUpdate(sign(parametersHash)) + } else { + throw IllegalArgumentException("Refused to accept parameters with hash $parametersHash because network map advertises update with hash $newParametersHash. Please check newest version") + } + } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 1eafc11b00..3421c8a9a2 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -7,6 +7,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate +import net.corda.core.node.NotaryInfo import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.schemas.NodeInfoSchemaV1 @@ -23,7 +24,6 @@ import net.corda.core.utilities.loggerFor import net.corda.node.services.api.NetworkMapCacheBaseInternal import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.utilities.NonInvalidatingCache -import net.corda.nodeapi.internal.network.NotaryInfo import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction diff --git a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt index 3b5df29df8..0ece39f948 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NetworkParametersTest.kt @@ -7,9 +7,9 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.flows.CashIssueFlow import net.corda.node.services.config.NotaryConfig -import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.core.node.NetworkParameters import net.corda.nodeapi.internal.network.NetworkParametersCopier -import net.corda.nodeapi.internal.network.NotaryInfo +import net.corda.core.node.NotaryInfo import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt index e9f791782e..1edd35db01 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapClientTest.kt @@ -1,12 +1,15 @@ package net.corda.node.services.network +import net.corda.core.crypto.Crypto import net.corda.core.crypto.sha256 +import net.corda.core.internal.* import net.corda.core.serialization.serialize import net.corda.core.utilities.seconds import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.DEV_ROOT_CA import net.corda.testing.core.SerializationEnvironmentRule +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.TestNodeInfoBuilder import net.corda.testing.internal.createNodeInfoAndSigned @@ -20,6 +23,8 @@ import org.junit.Rule import org.junit.Test import java.io.IOException import java.net.URL +import java.time.Instant +import java.time.temporal.ChronoUnit import kotlin.test.assertEquals class NetworkMapClientTest { @@ -90,4 +95,23 @@ class NetworkMapClientTest { fun `get hostname string from http response correctly`() { assertEquals("test.host.name", networkMapClient.myPublicHostname()) } + + @Test + fun `handle parameters update`() { + val nextParameters = testNetworkParameters(emptyList(), epoch = 2) + val originalNetworkParameterHash = server.networkParameters.serialize().hash + val nextNetworkParameterHash = nextParameters.serialize().hash + val description = "Test parameters" + server.scheduleParametersUpdate(nextParameters, description, Instant.now().plus(1, ChronoUnit.DAYS)) + val (networkMap) = networkMapClient.getNetworkMap() + assertEquals(networkMap.networkParameterHash, originalNetworkParameterHash) + assertEquals(networkMap.parametersUpdate?.description, description) + assertEquals(networkMap.parametersUpdate?.newParametersHash, nextNetworkParameterHash) + assertEquals(networkMapClient.getNetworkParameters(originalNetworkParameterHash).verified(), server.networkParameters) + assertEquals(networkMapClient.getNetworkParameters(nextNetworkParameterHash).verified(), nextParameters) + val keyPair = Crypto.generateKeyPair() + val signedHash = nextNetworkParameterHash.serialize().sign(keyPair) + networkMapClient.ackNetworkParametersUpdate(signedHash) + assertEquals(nextNetworkParameterHash, server.latestParametersAccepted(keyPair.public)) + } } diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt index 1f1478eba0..8609520c73 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt @@ -7,19 +7,27 @@ import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.times import com.nhaarman.mockito_kotlin.verify import net.corda.cordform.CordformNode.NODE_INFO_DIRECTORY +import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.div -import net.corda.core.internal.uncheckedCast +import net.corda.core.internal.* +import net.corda.core.messaging.ParametersUpdateInfo +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.millis import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.nodeapi.internal.SignedNodeInfo +import net.corda.nodeapi.internal.createDevNetworkMapCa +import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair +import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME import net.corda.nodeapi.internal.network.NetworkMap -import net.corda.testing.core.ALICE_NAME -import net.corda.testing.core.SerializationEnvironmentRule +import net.corda.nodeapi.internal.network.ParametersUpdate +import net.corda.nodeapi.internal.network.verifiedNetworkMapCert +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.core.* import net.corda.testing.internal.TestNodeInfoBuilder import net.corda.testing.internal.createNodeInfoAndSigned import org.assertj.core.api.Assertions.assertThat @@ -27,8 +35,11 @@ import org.junit.After import org.junit.Rule import org.junit.Test import rx.schedulers.TestScheduler +import java.time.Instant +import java.time.temporal.ChronoUnit import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals class NetworkMapUpdaterTest { @Rule @@ -39,13 +50,16 @@ class NetworkMapUpdaterTest { private val baseDir = fs.getPath("/node") private val networkMapCache = createMockNetworkMapCache() private val nodeInfoMap = ConcurrentHashMap() + private val networkParamsMap = HashMap() + private val networkMapCa: CertificateAndKeyPair = createDevNetworkMapCa() private val cacheExpiryMs = 100 private val networkMapClient = createMockNetworkMapClient() private val scheduler = TestScheduler() private val networkParametersHash = SecureHash.randomSHA256() private val fileWatcher = NodeInfoWatcher(baseDir, scheduler) - private val updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient, networkParametersHash) + private val updater = NetworkMapUpdater(networkMapCache, fileWatcher, networkMapClient, networkParametersHash, baseDir) private val nodeInfoBuilder = TestNodeInfoBuilder() + private var parametersUpdate: ParametersUpdate? = null @After fun cleanUp() { @@ -180,18 +194,73 @@ class NetworkMapUpdaterTest { assertThat(networkMapCache.allNodeHashes).containsOnly(fileNodeInfo.serialize().hash) } + @Test + fun `emit new parameters update info on parameters update from network map`() { + val paramsFeed = updater.track() + val snapshot = paramsFeed.snapshot + val updates = paramsFeed.updates.bufferUntilSubscribed() + assertEquals(null, snapshot) + val newParameters = testNetworkParameters(emptyList(), epoch = 2) + val updateDeadline = Instant.now().plus(1, ChronoUnit.DAYS) + scheduleParametersUpdate(newParameters, "Test update", updateDeadline) + updater.subscribeToNetworkMap() + updates.expectEvents(isStrict = false) { + sequence( + expect { update: ParametersUpdateInfo -> + assertThat(update.updateDeadline == updateDeadline) + assertThat(update.description == "Test update") + assertThat(update.hash == newParameters.serialize().hash) + assertThat(update.parameters == newParameters) + } + ) + } + } + + @Test + fun `ack network parameters update`() { + val newParameters = testNetworkParameters(emptyList(), epoch = 314) + scheduleParametersUpdate(newParameters, "Test update", Instant.MIN) + updater.subscribeToNetworkMap() + // TODO: Remove sleep in unit test. + Thread.sleep(2L * cacheExpiryMs) + val newHash = newParameters.serialize().hash + val keyPair = Crypto.generateKeyPair() + updater.acceptNewNetworkParameters(newHash, { hash -> hash.serialize().sign(keyPair)}) + verify(networkMapClient).ackNetworkParametersUpdate(any()) + val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME + val signedNetworkParams = updateFile.readAll().deserialize>() + val paramsFromFile = signedNetworkParams.verifiedNetworkMapCert(DEV_ROOT_CA.certificate) + assertEquals(newParameters, paramsFromFile) + } + + private fun scheduleParametersUpdate(nextParameters: NetworkParameters, description: String, updateDeadline: Instant) { + val nextParamsHash = nextParameters.serialize().hash + networkParamsMap[nextParamsHash] = nextParameters + parametersUpdate = ParametersUpdate(nextParamsHash, description, updateDeadline) + } + private fun createMockNetworkMapClient(): NetworkMapClient { return mock { + on { trustedRoot }.then { + DEV_ROOT_CA.certificate + } on { publish(any()) }.then { val signedNodeInfo: SignedNodeInfo = uncheckedCast(it.arguments[0]) nodeInfoMap.put(signedNodeInfo.verified().serialize().hash, signedNodeInfo) } on { getNetworkMap() }.then { - NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), networkParametersHash), cacheExpiryMs.millis) + NetworkMapResponse(NetworkMap(nodeInfoMap.keys.toList(), networkParametersHash, parametersUpdate), cacheExpiryMs.millis) } on { getNodeInfo(any()) }.then { nodeInfoMap[it.arguments[0]]?.verified() } + on { getNetworkParameters(any()) }.then { + val paramsHash: SecureHash = uncheckedCast(it.arguments[0]) + networkParamsMap[paramsHash]?.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) + } + on { ackNetworkParametersUpdate(any()) }.then { + Unit + } } } diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt new file mode 100644 index 0000000000..b6b9e107cd --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkParametersReaderTest.kt @@ -0,0 +1,63 @@ +package net.corda.node.services.network + +import com.google.common.jimfs.Configuration +import com.google.common.jimfs.Jimfs +import net.corda.core.internal.* +import net.corda.core.node.NetworkParameters +import net.corda.core.serialization.deserialize +import net.corda.core.utilities.seconds +import net.corda.node.internal.NetworkParametersReader +import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME +import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME +import net.corda.nodeapi.internal.network.NetworkParametersCopier +import net.corda.nodeapi.internal.network.verifiedNetworkMapCert +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.core.DEV_ROOT_CA +import net.corda.testing.core.SerializationEnvironmentRule +import net.corda.testing.driver.PortAllocation +import net.corda.testing.node.internal.network.NetworkMapServer +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.net.URL +import kotlin.test.assertEquals +import kotlin.test.assertFalse + +class NetworkParametersReaderTest { + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule(true) + + private val cacheTimeout = 100000.seconds + + private lateinit var server: NetworkMapServer + private lateinit var networkMapClient: NetworkMapClient + + @Before + fun setUp() { + server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort()) + val hostAndPort = server.start() + networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"), DEV_ROOT_CA.certificate) + } + + @After + fun tearDown() { + server.close() + } + + @Test + fun `read correct set of parameters from file`() { + val fs = Jimfs.newFileSystem(Configuration.unix()) + val baseDirectory = fs.getPath("/node").createDirectories() + val oldParameters = testNetworkParameters(emptyList(), epoch = 1) + NetworkParametersCopier(oldParameters).install(baseDirectory) + NetworkParametersCopier(server.networkParameters, update = true).install(baseDirectory) // Parameters update file. + val parameters = NetworkParametersReader(DEV_ROOT_CA.certificate, networkMapClient, baseDirectory).networkParameters + assertFalse((baseDirectory / NETWORK_PARAMS_UPDATE_FILE_NAME).exists()) + assertEquals(server.networkParameters, parameters) + // Parameters from update should be moved to `network-parameters` file. + val parametersFromFile = (baseDirectory / NETWORK_PARAMS_FILE_NAME).readAll().deserialize>().verifiedNetworkMapCert(DEV_ROOT_CA.certificate) + assertEquals(server.networkParameters, parametersFromFile) + } +} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 1715b01662..1b68a4444a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -42,7 +42,7 @@ import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.network.NetworkParametersCopier -import net.corda.nodeapi.internal.network.NotaryInfo +import net.corda.core.node.NotaryInfo import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.core.DUMMY_NOTARY_NAME 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 0b1ac2d4b5..46d5731e9a 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 @@ -37,7 +37,7 @@ import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NodeInfoFilesCopier -import net.corda.nodeapi.internal.network.NotaryInfo +import net.corda.core.node.NotaryInfo import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index 52975bbdb3..7b8d6a4e77 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -1,6 +1,7 @@ package net.corda.testing.node.internal.network import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SignedData import net.corda.core.internal.signWithCert import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize @@ -9,8 +10,9 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair +import net.corda.core.node.NetworkParameters import net.corda.nodeapi.internal.network.NetworkMap -import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.nodeapi.internal.network.ParametersUpdate import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.handler.HandlerCollection @@ -21,6 +23,7 @@ import org.glassfish.jersey.servlet.ServletContainer import java.io.Closeable import java.io.InputStream import java.net.InetSocketAddress +import java.security.PublicKey import java.security.SignatureException import java.time.Duration import java.time.Instant @@ -46,6 +49,8 @@ class NetworkMapServer(private val cacheTimeout: Duration, field = networkParameters } private val service = InMemoryNetworkMapService() + private var parametersUpdate: ParametersUpdate? = null + private var nextNetworkParameters: NetworkParameters? = null init { server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply { @@ -80,6 +85,21 @@ class NetworkMapServer(private val cacheTimeout: Duration, service.removeNodeInfo(nodeInfo) } + fun scheduleParametersUpdate(nextParameters: NetworkParameters, description: String, updateDeadline: Instant) { + nextNetworkParameters = nextParameters + parametersUpdate = ParametersUpdate(nextParameters.serialize().hash, description, updateDeadline) + } + + fun advertiseNewParameters() { + networkParameters = checkNotNull(nextNetworkParameters) { "Schedule parameters update first" } + nextNetworkParameters = null + parametersUpdate = null + } + + fun latestParametersAccepted(publicKey: PublicKey): SecureHash? { + return service.latestAcceptedParametersMap[publicKey] + } + override fun close() { server.stop() } @@ -87,6 +107,7 @@ class NetworkMapServer(private val cacheTimeout: Duration, @Path("network-map") inner class InMemoryNetworkMapService { private val nodeInfoMap = mutableMapOf() + val latestAcceptedParametersMap = mutableMapOf() private val signedNetParams by lazy { networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) } @@ -108,10 +129,20 @@ class NetworkMapServer(private val cacheTimeout: Duration, }.build() } + @POST + @Path("ack-parameters") + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + fun ackNetworkParameters(input: InputStream): Response { + val signedParametersHash = input.readBytes().deserialize>() + val hash = signedParametersHash.verified() + latestAcceptedParametersMap[signedParametersHash.sig.by] = hash + return ok().build() + } + @GET @Produces(MediaType.APPLICATION_OCTET_STREAM) fun getNetworkMap(): Response { - val networkMap = NetworkMap(nodeInfoMap.keys.toList(), signedNetParams.raw.hash) + val networkMap = NetworkMap(nodeInfoMap.keys.toList(), signedNetParams.raw.hash, parametersUpdate) val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) return Response.ok(signedNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${cacheTimeout.seconds}").build() } @@ -137,8 +168,14 @@ class NetworkMapServer(private val cacheTimeout: Duration, @Path("network-parameters/{var}") @Produces(MediaType.APPLICATION_OCTET_STREAM) fun getNetworkParameter(@PathParam("var") hash: String): Response { - require(signedNetParams.raw.hash == SecureHash.parse(hash)) - return Response.ok(signedNetParams.serialize().bytes).build() + val requestedHash = SecureHash.parse(hash) + val requestedParameters = if (requestedHash == signedNetParams.raw.hash) { + signedNetParams + } else if (requestedHash == nextNetworkParameters?.serialize()?.hash) { + nextNetworkParameters?.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) + } else null + requireNotNull(requestedParameters) + return Response.ok(requestedParameters!!.serialize().bytes).build() } @GET diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt index 3d448d9188..b0215cf83c 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt @@ -1,7 +1,7 @@ package net.corda.testing.common.internal -import net.corda.nodeapi.internal.network.NetworkParameters -import net.corda.nodeapi.internal.network.NotaryInfo +import net.corda.core.node.NetworkParameters +import net.corda.core.node.NotaryInfo import java.time.Instant fun testNetworkParameters( diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index fddcfdf19a..37faeae80a 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -10,9 +10,9 @@ import net.corda.core.internal.noneOrSingle import net.corda.core.utilities.NetworkHostAndPort import net.corda.demobench.plugin.CordappController import net.corda.demobench.pty.R3Pty -import net.corda.nodeapi.internal.network.NetworkParameters +import net.corda.core.node.NetworkParameters import net.corda.nodeapi.internal.network.NetworkParametersCopier -import net.corda.nodeapi.internal.network.NotaryInfo +import net.corda.core.node.NotaryInfo import net.corda.nodeapi.internal.DevIdentityGenerator import tornadofx.* import java.io.IOException From 4a68145f231dd4206cdcdded0c5abcb2957829ad Mon Sep 17 00:00:00 2001 From: Anthony Keenan Date: Thu, 8 Feb 2018 14:38:15 +0000 Subject: [PATCH 028/114] CORDA-939 Adding documentation changes requested from v3 release branch PR (#2480) * Adding documentation changes requested from v3 release branch PR * Further doc changes as requested --- docs/source/changelog.rst | 8 ++++++++ .../main/kotlin/net/corda/testing/node/NodeTestUtils.kt | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index afe5cd3215..1e38ca9fee 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -170,6 +170,14 @@ UNRELEASED * Move to a message based control of peer to peer bridge formation to allow for future out of process bridging components. This removes the legacy Artemis bridges completely, so the ``useAMQPBridges`` configuration property has been removed. +* A ``CordaInternal`` attribute has been added to identify properties that are not intended to form part of the + public api and as such are not intended for public use. This is alongside the existing ``DoNotImplement`` attribute for classes which + provide Corda functionality to user applications, but should not be implemented by consumers, and any classes which + are defined in ``.internal`` packages, which are also not for public use. + +* Marked ``stateMachine`` on ``FlowLogic`` as ``CordaInternal`` to make clear that is it not part of the public api and is + only for internal use + .. _changelog_v1: Release 1.0 diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt index 1561564756..dcf47f96b3 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt @@ -6,11 +6,9 @@ import net.corda.core.concurrent.CordaFuture import net.corda.core.context.Actor import net.corda.core.context.AuthServiceId import net.corda.core.context.InvocationContext -import net.corda.core.context.InvocationOrigin import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.FlowStateMachine import net.corda.core.node.ServiceHub import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.transactions.TransactionBuilder @@ -66,4 +64,8 @@ fun testContext(owningLegalIdentity: CordaX500Name = CordaX500Name("Test Company */ fun StartedNodeServices.newContext() = testContext(myInfo.chooseIdentity().name) +/** + * Starts an already constructed flow. Note that you must be on the server thread to call this method. [InvocationContext] + * has origin [Origin.RPC] and actor with id "Only For Testing". + */ fun StartedNodeServices.startFlow(logic: FlowLogic): CordaFuture = startFlow(logic, newContext()).getOrThrow().resultFuture \ No newline at end of file From ff972508bd8649ff981fec23834262186e0efa99 Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Thu, 8 Feb 2018 15:49:33 +0000 Subject: [PATCH 029/114] CORDA-986 and CORDA-985 CompositeKey and Signature verification performance fixes (#2467) --- .../net/corda/core/crypto/CompositeKey.kt | 37 ++++++++++------ .../core/transactions/SignedTransaction.kt | 43 +++++++++++-------- .../transactions/TransactionWithSignatures.kt | 7 ++- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt index 1fe4103d4f..98bac0dae0 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt @@ -1,6 +1,5 @@ package net.corda.core.crypto -import net.corda.core.crypto.CompositeKey.NodeAndWeight import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.exactAdd import net.corda.core.utilities.sequence @@ -12,7 +11,7 @@ import java.util.* /** * A tree data structure that enables the representation of composite public keys, which are used to represent - * the signing requirements for multisignature scenarios such as RAFT notary services. A composite key is a list + * the signing requirements for multi-signature scenarios such as RAFT notary services. A composite key is a list * of leaf keys and their contributing weight, and each leaf can be a conventional single key or a composite key. * Keys contribute their weight to the total if they are matched by the signature. * @@ -53,9 +52,19 @@ class CompositeKey private constructor(val threshold: Int, children: List({ -it.weight }, { it.node.encoded.sequence() }) } - val children: List = children.sorted() + /** + * Τhe order of the children may not be the same to what was provided in the builder. + */ + val children: List = children.sortedWith(descWeightComparator) init { // TODO: replace with the more extensive, but slower, checkValidity() test. @@ -103,9 +112,9 @@ class CompositeKey private constructor(val threshold: Int, children: List() visitedMap.put(this, true) cycleDetection(visitedMap) // Graph cycle testing on the root node. @@ -143,6 +152,7 @@ class CompositeKey private constructor(val threshold: Int, children: List): Boolean { - if (keysToCheck.any { it is CompositeKey }) return false - val totalWeight = children.map { (node, weight) -> + var totalWeight = 0 + children.forEach { (node, weight) -> if (node is CompositeKey) { - if (node.checkFulfilledBy(keysToCheck)) weight else 0 + if (node.checkFulfilledBy(keysToCheck)) totalWeight += weight } else { - if (keysToCheck.contains(node)) weight else 0 + if (node in keysToCheck) totalWeight += weight } - }.sum() - return totalWeight >= threshold + if (totalWeight >= threshold) return true + } + return false } /** @@ -201,8 +212,8 @@ class CompositeKey private constructor(val threshold: Int, children: List): Boolean { // We validate keys only when checking if they're matched, as this checks subkeys as a result. // Doing these checks at deserialization/construction time would result in duplicate checks. - if (!validated) - checkValidity() // TODO: remove when checkValidity() will be eventually invoked during/after deserialization. + checkValidity() + if (keysToCheck.any { it is CompositeKey }) return false return checkFulfilledBy(keysToCheck) } diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt index 015e745987..f6068c71f9 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt @@ -68,15 +68,15 @@ data class SignedTransaction(val txBits: SerializedBytes, */ fun buildFilteredTransaction(filtering: Predicate) = tx.buildFilteredTransaction(filtering) - /** Helper to access the inputs of the contained transaction */ + /** Helper to access the inputs of the contained transaction. */ val inputs: List get() = transaction.inputs - /** Helper to access the notary of the contained transaction */ + /** Helper to access the notary of the contained transaction. */ val notary: Party? get() = transaction.notary override val requiredSigningKeys: Set get() = tx.requiredSigningKeys override fun getKeyDescriptions(keys: Set): ArrayList { - // TODO: We need a much better way of structuring this data + // TODO: We need a much better way of structuring this data. val descriptions = ArrayList() this.tx.commands.forEach { command -> if (command.signers.any { it in keys }) @@ -134,8 +134,18 @@ data class SignedTransaction(val txBits: SerializedBytes, @JvmOverloads @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class) fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true): LedgerTransaction { - checkSignaturesAreValid() - if (checkSufficientSignatures) verifyRequiredSignatures() + // TODO: We could probably optimise the below by + // a) not throwing if threshold is eventually satisfied, but some of the rest of the signatures are failing. + // b) omit verifying signatures when threshold requirement is met. + // c) omit verifying signatures from keys not included in [requiredSigningKeys]. + // For the above to work, [checkSignaturesAreValid] should take the [requiredSigningKeys] as input + // and probably combine logic from signature validation and key-fulfilment + // in [TransactionWithSignatures.verifySignaturesExcept]. + if (checkSufficientSignatures) { + verifyRequiredSignatures() // It internally invokes checkSignaturesAreValid(). + } else { + checkSignaturesAreValid() + } return tx.toLedgerTransaction(services) } @@ -153,28 +163,25 @@ data class SignedTransaction(val txBits: SerializedBytes, @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class) fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) { if (isNotaryChangeTransaction()) { - verifyNotaryChangeTransaction(checkSufficientSignatures, services) + verifyNotaryChangeTransaction(services, checkSufficientSignatures) } else { - verifyRegularTransaction(checkSufficientSignatures, services) + verifyRegularTransaction(services, checkSufficientSignatures) } } - /** - * TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised - * from the attachment is trusted. This will require some partial serialisation work to not load the ContractState - * objects from the TransactionState. - */ - private fun verifyRegularTransaction(checkSufficientSignatures: Boolean, services: ServiceHub) { - checkSignaturesAreValid() - if (checkSufficientSignatures) verifyRequiredSignatures() - val ltx = tx.toLedgerTransaction(services) - // TODO: allow non-blocking verification + // TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised + // from the attachment is trusted. This will require some partial serialisation work to not load the ContractState + // objects from the TransactionState. + private fun verifyRegularTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) { + val ltx = toLedgerTransaction(services, checkSufficientSignatures) + // TODO: allow non-blocking verification. services.transactionVerifierService.verify(ltx).getOrThrow() } - private fun verifyNotaryChangeTransaction(checkSufficientSignatures: Boolean, services: ServiceHub) { + private fun verifyNotaryChangeTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) { val ntx = resolveNotaryChangeTransaction(services) if (checkSufficientSignatures) ntx.verifyRequiredSignatures() + else checkSignaturesAreValid() } fun isNotaryChangeTransaction() = transaction is NotaryChangeWireTransaction 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 8999a62056..77ceb3840b 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt @@ -11,7 +11,7 @@ import java.security.PublicKey import java.security.SignatureException import java.util.* -/** An interface for transactions containing signatures, with logic for signature verification */ +/** An interface for transactions containing signatures, with logic for signature verification. */ @DoNotImplement interface TransactionWithSignatures : NamedByHash { /** @@ -21,7 +21,7 @@ interface TransactionWithSignatures : NamedByHash { */ val sigs: List - /** Specifies all the public keys that require signatures for the transaction to be valid */ + /** Specifies all the public keys that require signatures for the transaction to be valid. */ val requiredSigningKeys: Set /** @@ -65,11 +65,10 @@ interface TransactionWithSignatures : NamedByHash { */ @Throws(SignatureException::class) fun verifySignaturesExcept(allowedToBeMissing: Collection) { - checkSignaturesAreValid() - val needed = getMissingSigners() - allowedToBeMissing if (needed.isNotEmpty()) throw SignaturesMissingException(needed.toNonEmptySet(), getKeyDescriptions(needed), id) + checkSignaturesAreValid() } /** From 2986e2f5a9559a2dc7bdcaab80e8a4274a3c6b1d Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 8 Feb 2018 16:50:28 +0000 Subject: [PATCH 030/114] Provides more information about the default initiating subflows. Minor tweaks. --- docs/source/api-flows.rst | 38 +++++++++++++++++--------------- docs/source/api-identity.rst | 3 +++ docs/source/running-a-node.rst | 2 +- docs/source/tutorial-cordapp.rst | 2 +- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/docs/source/api-flows.rst b/docs/source/api-flows.rst index 4a0e284621..f42661aa85 100644 --- a/docs/source/api-flows.rst +++ b/docs/source/api-flows.rst @@ -473,7 +473,6 @@ explicit in the ``initiateFlow`` function call. To port existing code: Subflows -------- - Subflows are pieces of reusable flows that may be run by calling ``FlowLogic.subFlow``. There are two broad categories of subflows, inlined and initiating ones. The main difference lies in the counter-flow's starting method, initiating ones initiate counter-flows automatically, while inlined ones expect some parent counter-flow to run the inlined @@ -481,7 +480,6 @@ counter-part. Inlined subflows ^^^^^^^^^^^^^^^^ - Inlined subflows inherit their calling flow's type when initiating a new session with a counterparty. For example, say we have flow A calling an inlined subflow B, which in turn initiates a session with a party. The FlowLogic type used to determine which counter-flow should be kicked off will be A, not B. Note that this means that the other side of this @@ -499,7 +497,6 @@ In the code inlined subflows appear as regular ``FlowLogic`` instances, `without Initiating subflows ^^^^^^^^^^^^^^^^^^^ - Initiating subflows are ones annotated with the ``@InitiatingFlow`` annotation. When such a flow initiates a session its type will be used to determine which ``@InitiatedBy`` flow to kick off on the counterparty. @@ -508,32 +505,38 @@ An example is the ``@InitiatingFlow InitiatorFlow``/``@InitiatedBy ResponderFlow .. note:: Initiating flows are versioned separately from their parents. Core initiating subflows -^^^^^^^^^^^^^^^^^^^^^^^^ - +~~~~~~~~~~~~~~~~~~~~~~~~ Corda-provided initiating subflows are a little different to standard ones as they are versioned together with the platform, and their initiated counter-flows are registered explicitly, so there is no need for the ``InitiatedBy`` annotation. -An example is the ``FinalityFlow``/``FinalityHandler`` flow pair. +Library flows +^^^^^^^^^^^^^ +Corda installs four initiating subflow pairs on each node by default: -Built-in subflows -^^^^^^^^^^^^^^^^^ +* ``FinalityFlow``/``FinalityHandler``, which should be used to notarise and record a transaction and broadcast it to + all relevant parties +* ``NotaryChangeFlow``/``NotaryChangeHandler``, which should be used to change a state's notary +* ``ContractUpgradeFlow.Initiate``/``ContractUpgradeHandler``, which should be used to change a state's contract +* ``SwapIdentitiesFlow``/``SwapIdentitiesHandler``, which is used to exchange confidential identities with a + counterparty -Corda provides a number of built-in flows that should be used for handling common tasks. The most important are: +.. warning:: ``SwapIdentitiesFlow``/``SwapIdentitiesHandler`` are only installed if the ``confidential-identities`` module + is included. The ``confidential-identities`` module is still not stabilised, so the + ``SwapIdentitiesFlow``/``SwapIdentitiesHandler`` API may change in future releases. See :doc:`corda-api`. + +Corda also provides a number of built-in inlined subflows that should be used for handling common tasks. The most +important are: * ``CollectSignaturesFlow`` (inlined), which should be used to collect a transaction's required signatures -* ``FinalityFlow`` (initiating), which should be used to notarise and record a transaction as well as to broadcast it to - all relevant parties * ``SendTransactionFlow`` (inlined), which should be used to send a signed transaction if it needed to be resolved on the other side. * ``ReceiveTransactionFlow`` (inlined), which should be used receive a signed transaction -* ``ContractUpgradeFlow`` (initiating), which should be used to change a state's contract -* ``NotaryChangeFlow`` (initiating), which should be used to change a state's notary -Let's look at three very common examples. +Let's look at some of these flows in more detail. FinalityFlow -^^^^^^^^^^^^ +~~~~~~~~~~~~ ``FinalityFlow`` allows us to notarise the transaction and get it recorded in the vault of the participants of all the transaction's states: @@ -571,7 +574,7 @@ Only one party has to call ``FinalityFlow`` for a given transaction to be record **not** need to be called by each participant individually. CollectSignaturesFlow/SignTransactionFlow -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The list of parties who need to sign a transaction is dictated by the transaction's commands. Once we've signed a transaction ourselves, we can automatically gather the signatures of the other required signers using ``CollectSignaturesFlow``: @@ -608,7 +611,7 @@ transaction and provide their signature if they are satisfied: :dedent: 12 SendTransactionFlow/ReceiveTransactionFlow -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Verifying a transaction received from a counterparty also requires verification of every transaction in its dependency chain. This means the receiving party needs to be able to ask the sender all the details of the chain. The sender will use ``SendTransactionFlow`` for sending the transaction and then for processing all subsequent @@ -663,7 +666,6 @@ We can also send and receive a ``StateAndRef`` dependency chain and automaticall Why inlined subflows? ^^^^^^^^^^^^^^^^^^^^^ - Inlined subflows provide a way to share commonly used flow code `while forcing users to create a parent flow`. Take for example ``CollectSignaturesFlow``. Say we made it an initiating flow that automatically kicks off ``SignTransactionFlow`` that signs the transaction. This would mean malicious nodes can just send any old transaction to diff --git a/docs/source/api-identity.rst b/docs/source/api-identity.rst index 3ab041d6b2..77f6896c99 100644 --- a/docs/source/api-identity.rst +++ b/docs/source/api-identity.rst @@ -3,6 +3,9 @@ API: Identity .. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-identity`. +.. warning:: The ``confidential-identities`` module is still not stabilised, so this API may change in future releases. + See :doc:`corda-api`. + .. contents:: Party diff --git a/docs/source/running-a-node.rst b/docs/source/running-a-node.rst index 34fde7652b..e4d44e0726 100644 --- a/docs/source/running-a-node.rst +++ b/docs/source/running-a-node.rst @@ -19,7 +19,7 @@ Start the nodes with ``runnodes`` by running the following command from the root * Linux/macOS: ``build/nodes/runnodes`` * Windows: ``call build\nodes\runnodes.bat`` -.. warn:: On macOS, do not click/change focus until all the node terminal windows have opened, or some processes may +.. warning:: On macOS, do not click/change focus until all the node terminal windows have opened, or some processes may fail to start. If you receive an ``OutOfMemoryError`` exception when interacting with the nodes, you need to increase the amount of diff --git a/docs/source/tutorial-cordapp.rst b/docs/source/tutorial-cordapp.rst index 186f250d80..67c9e25a54 100644 --- a/docs/source/tutorial-cordapp.rst +++ b/docs/source/tutorial-cordapp.rst @@ -215,7 +215,7 @@ Start the nodes by running the following command from the root of the ``cordapp- * Unix/Mac OSX: ``kotlin-source/build/nodes/runnodes`` * Windows: ``call kotlin-source\build\nodes\runnodes.bat`` -.. warn:: On Unix/Mac OSX, do not click/change focus until all seven additional terminal windows have opened, or some +.. warning:: On Unix/Mac OSX, do not click/change focus until all seven additional terminal windows have opened, or some nodes may fail to start. For each node, the ``runnodes`` script creates a node tab/window: From 1902a4f11eac7fac60458f6ace39f9dc1f8543df Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Fri, 9 Feb 2018 11:54:07 +0000 Subject: [PATCH 031/114] CORDA-973 Refactoring for serialization compression support (#2466) * Use constant for empty byte array * Less byte array copying * Fix InputStreamSerializer trailing garbage * More OO kryo streams * Introduce SerializationMagic * Introduce non-copying slice on ByteSequence --- .ci/api-current.txt | 8 +- .../internal/KryoClientSerializationScheme.kt | 8 +- core/build.gradle | 2 +- .../net/corda/core/contracts/Structures.kt | 2 +- .../net/corda/core/internal/InternalUtils.kt | 3 + .../core/serialization/SerializationAPI.kt | 9 +-- .../net/corda/core/utilities/ByteArrays.kt | 79 +++++++++--------- .../net/corda/core/crypto/Base58Test.kt | 3 +- .../net/corda/core/crypto/CryptoUtilsTest.kt | 31 +++---- .../corda/core/utilities/ByteArraysTest.kt | 45 +++++++++++ .../corda/core/utilities/EncodingUtilsTest.kt | 8 +- .../internal/network/NetworkBootstrapper.kt | 8 +- .../internal/serialization/ClientContexts.kt | 9 +-- .../serialization/SerializationFormat.kt | 12 +++ .../serialization/SerializationScheme.kt | 53 +++++------- .../internal/serialization/ServerContexts.kt | 16 ++-- .../internal/serialization/SharedContexts.kt | 12 ++- .../amqp/AMQPSerializationScheme.kt | 17 ++-- .../amqp/DeserializationInput.kt | 16 +--- .../internal/serialization/amqp/Schema.kt | 6 +- .../serialization/amqp/SerializationOutput.kt | 2 +- .../OpaqueBytesSubSequenceSerializer.kt | 4 +- .../kryo/DefaultKryoCustomizer.kt | 3 +- .../internal/serialization/kryo/Kryo.kt | 6 +- .../kryo/KryoSerializationScheme.kt | 80 +++++++------------ .../serialization/kryo/KryoStreams.kt | 43 ++++++++++ .../ForbiddenLambdaSerializationTests.java | 6 +- .../LambdaCheckpointSerializationTest.java | 2 +- .../internal/crypto/X509UtilitiesTest.kt | 6 +- .../ContractAttachmentSerializerTest.kt | 6 +- .../serialization/CordaClassResolverTests.kt | 7 +- .../internal/serialization/KryoTests.kt | 21 +++-- .../serialization/ListsSerializationTest.kt | 4 +- .../serialization/MapsSerializationTest.kt | 4 +- .../serialization/SerializationTokenTest.kt | 4 +- .../serialization/SetsSerializationTest.kt | 4 +- .../amqp/OverridePKSerializerTest.kt | 5 +- .../serialization/kryo/KryoStreamsTest.kt | 60 ++++++++++++++ .../KryoServerSerializationScheme.kt | 8 +- .../identity/PersistentIdentityService.kt | 3 +- .../keys/PersistentKeyManagementService.kt | 6 +- .../services/messaging/P2PMessagingClient.kt | 6 +- .../persistence/DBCheckpointStorage.kt | 5 +- .../persistence/DBTransactionStorage.kt | 3 +- .../PersistentUniquenessProvider.kt | 3 +- .../transactions/RaftUniquenessProvider.kt | 4 +- .../node/messaging/InMemoryMessagingTests.kt | 10 +-- tools/explorer/build.gradle | 1 - .../net/corda/verifier/GeneratedLedger.kt | 3 +- .../kotlin/net/corda/verifier/Verifier.kt | 13 +-- 50 files changed, 394 insertions(+), 285 deletions(-) create mode 100644 core/src/test/kotlin/net/corda/core/utilities/ByteArraysTest.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 8fff5bb205..5ae28f1fe9 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -3150,8 +3150,8 @@ public final class net.corda.core.utilities.ByteArrays extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence copy() public boolean equals(Object) @org.jetbrains.annotations.NotNull public abstract byte[] getBytes() - public abstract int getOffset() - public abstract int getSize() + public final int getOffset() + public final int getSize() public int hashCode() @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.ByteSequence of(byte[]) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.ByteSequence of(byte[], int) @@ -3274,8 +3274,6 @@ public static final class net.corda.core.utilities.NonEmptySet$iterator$1 extend @net.corda.core.serialization.CordaSerializable public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utilities.ByteSequence public (byte[]) @org.jetbrains.annotations.NotNull public final byte[] getBytes() - public int getOffset() - public int getSize() public static final net.corda.core.utilities.OpaqueBytes$Companion Companion ## public static final class net.corda.core.utilities.OpaqueBytes$Companion extends java.lang.Object @@ -3283,8 +3281,6 @@ public static final class net.corda.core.utilities.OpaqueBytes$Companion extends @net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.OpaqueBytesSubSequence extends net.corda.core.utilities.ByteSequence public (byte[], int, int) @org.jetbrains.annotations.NotNull public byte[] getBytes() - public int getOffset() - public int getSize() ## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.ProgressTracker extends java.lang.Object public final void endWithError(Throwable) diff --git a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt index 5a6adc58b9..67eab59f15 100644 --- a/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt +++ b/client/rpc/src/main/kotlin/net/corda/client/rpc/internal/KryoClientSerializationScheme.kt @@ -2,22 +2,22 @@ package net.corda.client.rpc.internal import com.esotericsoftware.kryo.pool.KryoPool import net.corda.core.serialization.SerializationContext +import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv -import net.corda.core.utilities.ByteSequence import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.nodeapi.internal.serialization.kryo.RPCKryo class KryoClientSerializationScheme : AbstractKryoSerializationScheme() { - override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { - return byteSequence == KryoHeaderV0_1 && (target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P) + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return magic == kryoMagic && (target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P) } override fun rpcClientKryoPool(context: SerializationContext): KryoPool { diff --git a/core/build.gradle b/core/build.gradle index bd443598d7..5965a1cbc8 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -104,7 +104,7 @@ dependencies { // Apache JEXL: An embeddable expression evaluation library. // This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API. compile "org.apache.commons:commons-jexl3:3.0" - + compile 'commons-lang:commons-lang:2.6' // For JSON compile "com.fasterxml.jackson.core:jackson-databind:${jackson_version}" diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index cc1abb0d56..42b8eff91f 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -46,7 +46,7 @@ interface NamedByHash { @CordaSerializable data class Issued(val issuer: PartyAndReference, val product: P) { init { - require(issuer.reference.bytes.size <= MAX_ISSUER_REF_SIZE) { "Maximum issuer reference size is $MAX_ISSUER_REF_SIZE." } + require(issuer.reference.size <= MAX_ISSUER_REF_SIZE) { "Maximum issuer reference size is $MAX_ISSUER_REF_SIZE." } } override fun toString() = "$product issued by $issuer" } diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 32283dafcf..18dcced593 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -26,6 +26,7 @@ import java.lang.reflect.Field import java.math.BigDecimal import java.net.HttpURLConnection import java.net.URL +import java.nio.ByteBuffer import java.nio.charset.Charset import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.* @@ -372,3 +373,5 @@ inline fun SerializedBytes.sign(signer: (SerializedBytes) -> Dig inline fun SerializedBytes.sign(keyPair: KeyPair): SignedData { return SignedData(this, keyPair.sign(this.bytes)) } + +fun ByteBuffer.copyBytes() = ByteArray(remaining()).also { get(it) } diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index 5b5aa35a6f..82ee0d402a 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -98,9 +98,7 @@ abstract class SerializationFactory { val currentFactory: SerializationFactory? get() = _currentFactory.get() } } - -typealias VersionHeader = ByteSequence - +typealias SerializationMagic = ByteSequence /** * Parameters to serialization and deserialization. */ @@ -108,7 +106,7 @@ interface SerializationContext { /** * When serializing, use the format this header sequence represents. */ - val preferredSerializationVersion: VersionHeader + val preferredSerializationVersion: SerializationMagic /** * The class loader to use for deserialization. */ @@ -161,7 +159,7 @@ interface SerializationContext { /** * Helper method to return a new context based on this context but with serialization using the format this header sequence represents. */ - fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext + fun withPreferredSerializationVersion(magic: SerializationMagic): SerializationContext /** * The use case that we are serializing for, since it influences the implementations chosen. @@ -225,6 +223,7 @@ fun T.serialize(serializationFactory: SerializationFactory = Serializa * A type safe wrapper around a byte array that contains a serialised object. You can call [SerializedBytes.deserialize] * to get the original object back. */ +@Suppress("unused") class SerializedBytes(bytes: ByteArray) : OpaqueBytes(bytes) { // It's OK to use lazy here because SerializedBytes is configured to use the ImmutableClassSerializer. val hash: SecureHash by lazy { bytes.sha256() } diff --git a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt index ecbad03375..75c9f02680 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ByteArrays.kt @@ -4,59 +4,43 @@ package net.corda.core.utilities import net.corda.core.serialization.CordaSerializable import java.io.ByteArrayInputStream +import java.io.OutputStream +import java.lang.Math.max +import java.lang.Math.min +import java.nio.ByteBuffer import javax.xml.bind.DatatypeConverter /** * An abstraction of a byte array, with offset and size that does no copying of bytes unless asked to. * * The data of interest typically starts at position [offset] within the [bytes] and is [size] bytes long. + * + * @property offset The start position of the sequence within the byte array. + * @property size The number of bytes this sequence represents. */ @CordaSerializable -sealed class ByteSequence : Comparable { - constructor() { - this._bytes = COPY_BYTES - } - - /** - * This constructor allows to bypass calls to [bytes] for functions in this class if the implementation - * of [bytes] makes a copy of the underlying [ByteArray] (as [OpaqueBytes] does for safety). This improves - * performance. It is recommended to use this constructor rather than the default constructor. - */ - constructor(uncopiedBytes: ByteArray) { - this._bytes = uncopiedBytes - } - +sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val size: Int) : Comparable { /** * The underlying bytes. Some implementations may choose to make a copy of the underlying [ByteArray] for * security reasons. For example, [OpaqueBytes]. */ abstract val bytes: ByteArray - /** - * The number of bytes this sequence represents. - */ - abstract val size: Int - /** - * The start position of the sequence within the byte array. - */ - abstract val offset: Int - - private val _bytes: ByteArray - get() = if (field === COPY_BYTES) bytes else field /** Returns a [ByteArrayInputStream] of the bytes */ fun open() = ByteArrayInputStream(_bytes, offset, size) /** - * Create a sub-sequence backed by the same array. + * Create a sub-sequence, that may be backed by a new byte array. * * @param offset The offset within this sequence to start the new sequence. Note: not the offset within the backing array. * @param size The size of the intended sub sequence. */ + @Suppress("MemberVisibilityCanPrivate") fun subSequence(offset: Int, size: Int): ByteSequence { require(offset >= 0) require(offset + size <= this.size) // Intentionally use bytes rather than _bytes, to mirror the copy-or-not behaviour of that property. - return if (offset == 0 && size == this.size) this else of(bytes, this.offset + offset, size) + return if (offset == 0 && size == this.size) this else OpaqueBytesSubSequence(bytes, this.offset + offset, size) } companion object { @@ -69,23 +53,40 @@ sealed class ByteSequence : Comparable { fun of(bytes: ByteArray, offset: Int = 0, size: Int = bytes.size): ByteSequence { return OpaqueBytesSubSequence(bytes, offset, size) } - - private val COPY_BYTES: ByteArray = ByteArray(0) } /** * Take the first n bytes of this sequence as a sub-sequence. See [subSequence] for further semantics. */ - fun take(n: Int): ByteSequence { - require(size >= n) - return subSequence(0, n) + fun take(n: Int): ByteSequence = subSequence(0, n) + + /** + * A new read-only [ByteBuffer] view of this sequence or part of it. + * If [start] or [end] are negative then [IllegalArgumentException] is thrown, otherwise they are clamped if necessary. + * This method cannot be used to get bytes before [offset] or after [offset]+[size], and never makes a new array. + */ + fun slice(start: Int = 0, end: Int = size): ByteBuffer { + require(start >= 0) + require(end >= 0) + val clampedStart = min(start, size) + val clampedEnd = min(end, size) + return ByteBuffer.wrap(_bytes, offset + clampedStart, max(0, clampedEnd - clampedStart)).asReadOnlyBuffer() } + /** Write this sequence to an [OutputStream]. */ + fun writeTo(output: OutputStream) = output.write(_bytes, offset, size) + + /** Write this sequence to a [ByteBuffer]. */ + fun putTo(buffer: ByteBuffer): ByteBuffer = buffer.put(_bytes, offset, size) + /** * Copy this sequence, complete with new backing array. This can be helpful to break references to potentially * large backing arrays from small sub-sequences. */ - fun copy(): ByteSequence = of(_bytes.copyOfRange(offset, offset + size)) + fun copy(): ByteSequence = of(copyBytes()) + + /** Same as [copy] but returns just the new byte array. */ + fun copyBytes(): ByteArray = _bytes.copyOfRange(offset, offset + size) /** * Compare byte arrays byte by byte. Arrays that are shorter are deemed less than longer arrays if all the bytes @@ -133,7 +134,7 @@ sealed class ByteSequence : Comparable { return result } - override fun toString(): String = "[${_bytes.copyOfRange(offset, offset + size).toHexString()}]" + override fun toString(): String = "[${copyBytes().toHexString()}]" } /** @@ -141,7 +142,7 @@ sealed class ByteSequence : Comparable { * In an ideal JVM this would be a value type and be completely overhead free. Project Valhalla is adding such * functionality to Java, but it won't arrive for a few years yet! */ -open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes) { +open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes, 0, bytes.size) { companion object { /** * Create [OpaqueBytes] from a sequence of [Byte] values. @@ -156,8 +157,8 @@ open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes) { /** * The bytes are always cloned so that this object becomes immutable. This has been done - * to prevent tampering with entities such as [SecureHash] and [PrivacySalt], as well as - * preserve the integrity of our hash constants [zeroHash] and [allOnesHash]. + * to prevent tampering with entities such as [net.corda.core.crypto.SecureHash] and [net.corda.core.contracts.PrivacySalt], as well as + * preserve the integrity of our hash constants [net.corda.core.crypto.SecureHash.zeroHash] and [net.corda.core.crypto.SecureHash.allOnesHash]. * * Cloning like this may become a performance issue, depending on whether or not the JIT * compiler is ever able to optimise away the clone. In which case we may need to revisit @@ -165,8 +166,6 @@ open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes) { */ override final val bytes: ByteArray = bytes get() = field.clone() - override val size: Int = bytes.size - override val offset: Int = 0 } /** @@ -188,7 +187,7 @@ fun String.parseAsHex(): ByteArray = DatatypeConverter.parseHexBinary(this) /** * Class is public for serialization purposes */ -class OpaqueBytesSubSequence(override val bytes: ByteArray, override val offset: Int, override val size: Int) : ByteSequence(bytes) { +class OpaqueBytesSubSequence(override val bytes: ByteArray, offset: Int, size: Int) : ByteSequence(bytes, offset, size) { init { require(offset >= 0 && offset < bytes.size) require(size >= 0 && size <= bytes.size) diff --git a/core/src/test/kotlin/net/corda/core/crypto/Base58Test.kt b/core/src/test/kotlin/net/corda/core/crypto/Base58Test.kt index 09e2d4e0bf..75ef3a8d09 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/Base58Test.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/Base58Test.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.junit.Test import java.math.BigInteger import java.util.* @@ -26,7 +27,7 @@ class Base58Test { assertEquals("1111111", Base58.encode(zeroBytes7)) // test empty encode - assertEquals("", Base58.encode(ByteArray(0))) + assertEquals("", Base58.encode(EMPTY_BYTE_ARRAY)) } @Test diff --git a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt index 587700fc32..a8f156b77a 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt @@ -8,6 +8,7 @@ import net.i2p.crypto.eddsa.math.GroupElement import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey @@ -77,7 +78,7 @@ class CryptoUtilsTest { // test for empty data signing try { - Crypto.doSign(privKey, ByteArray(0)) + Crypto.doSign(privKey, EMPTY_BYTE_ARRAY) fail() } catch (e: Exception) { // expected @@ -85,7 +86,7 @@ class CryptoUtilsTest { // test for empty source data when verifying try { - Crypto.doVerify(pubKey, testBytes, ByteArray(0)) + Crypto.doVerify(pubKey, testBytes, EMPTY_BYTE_ARRAY) fail() } catch (e: Exception) { // expected @@ -93,7 +94,7 @@ class CryptoUtilsTest { // test for empty signed data when verifying try { - Crypto.doVerify(pubKey, ByteArray(0), testBytes) + Crypto.doVerify(pubKey, EMPTY_BYTE_ARRAY, testBytes) fail() } catch (e: Exception) { // expected @@ -132,7 +133,7 @@ class CryptoUtilsTest { // test for empty data signing try { - Crypto.doSign(privKey, ByteArray(0)) + Crypto.doSign(privKey, EMPTY_BYTE_ARRAY) fail() } catch (e: Exception) { // expected @@ -140,7 +141,7 @@ class CryptoUtilsTest { // test for empty source data when verifying try { - Crypto.doVerify(pubKey, testBytes, ByteArray(0)) + Crypto.doVerify(pubKey, testBytes, EMPTY_BYTE_ARRAY) fail() } catch (e: Exception) { // expected @@ -148,7 +149,7 @@ class CryptoUtilsTest { // test for empty signed data when verifying try { - Crypto.doVerify(pubKey, ByteArray(0), testBytes) + Crypto.doVerify(pubKey, EMPTY_BYTE_ARRAY, testBytes) fail() } catch (e: Exception) { // expected @@ -187,7 +188,7 @@ class CryptoUtilsTest { // test for empty data signing try { - Crypto.doSign(privKey, ByteArray(0)) + Crypto.doSign(privKey, EMPTY_BYTE_ARRAY) fail() } catch (e: Exception) { // expected @@ -195,7 +196,7 @@ class CryptoUtilsTest { // test for empty source data when verifying try { - Crypto.doVerify(pubKey, testBytes, ByteArray(0)) + Crypto.doVerify(pubKey, testBytes, EMPTY_BYTE_ARRAY) fail() } catch (e: Exception) { // expected @@ -203,7 +204,7 @@ class CryptoUtilsTest { // test for empty signed data when verifying try { - Crypto.doVerify(pubKey, ByteArray(0), testBytes) + Crypto.doVerify(pubKey, EMPTY_BYTE_ARRAY, testBytes) fail() } catch (e: Exception) { // expected @@ -242,7 +243,7 @@ class CryptoUtilsTest { // test for empty data signing try { - Crypto.doSign(privKey, ByteArray(0)) + Crypto.doSign(privKey, EMPTY_BYTE_ARRAY) fail() } catch (e: Exception) { // expected @@ -250,7 +251,7 @@ class CryptoUtilsTest { // test for empty source data when verifying try { - Crypto.doVerify(pubKey, testBytes, ByteArray(0)) + Crypto.doVerify(pubKey, testBytes, EMPTY_BYTE_ARRAY) fail() } catch (e: Exception) { // expected @@ -258,7 +259,7 @@ class CryptoUtilsTest { // test for empty signed data when verifying try { - Crypto.doVerify(pubKey, ByteArray(0), testBytes) + Crypto.doVerify(pubKey, EMPTY_BYTE_ARRAY, testBytes) fail() } catch (e: Exception) { // expected @@ -297,7 +298,7 @@ class CryptoUtilsTest { // test for empty data signing try { - Crypto.doSign(privKey, ByteArray(0)) + Crypto.doSign(privKey, EMPTY_BYTE_ARRAY) fail() } catch (e: Exception) { // expected @@ -305,7 +306,7 @@ class CryptoUtilsTest { // test for empty source data when verifying try { - Crypto.doVerify(pubKey, testBytes, ByteArray(0)) + Crypto.doVerify(pubKey, testBytes, EMPTY_BYTE_ARRAY) fail() } catch (e: Exception) { // expected @@ -313,7 +314,7 @@ class CryptoUtilsTest { // test for empty signed data when verifying try { - Crypto.doVerify(pubKey, ByteArray(0), testBytes) + Crypto.doVerify(pubKey, EMPTY_BYTE_ARRAY, testBytes) fail() } catch (e: Exception) { // expected diff --git a/core/src/test/kotlin/net/corda/core/utilities/ByteArraysTest.kt b/core/src/test/kotlin/net/corda/core/utilities/ByteArraysTest.kt new file mode 100644 index 0000000000..416e64c960 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/utilities/ByteArraysTest.kt @@ -0,0 +1,45 @@ +package net.corda.core.utilities + +import net.corda.core.internal.declaredField +import org.assertj.core.api.Assertions.catchThrowable +import org.junit.Assert.assertSame +import org.junit.Test +import java.nio.ByteBuffer +import java.nio.ReadOnlyBufferException +import kotlin.test.assertEquals + +class ByteArraysTest { + @Test + fun `slice works`() { + byteArrayOf(9, 9, 0, 1, 2, 3, 4, 9, 9).let { + sliceWorksImpl(it, OpaqueBytesSubSequence(it, 2, 5)) + } + byteArrayOf(0, 1, 2, 3, 4).let { + sliceWorksImpl(it, OpaqueBytes(it)) + } + } + + private fun sliceWorksImpl(array: ByteArray, seq: ByteSequence) { + // Python-style negative indices can be implemented later if needed: + assertSame(IllegalArgumentException::class.java, catchThrowable { seq.slice(-1) }.javaClass) + assertSame(IllegalArgumentException::class.java, catchThrowable { seq.slice(end = -1) }.javaClass) + fun check(expected: ByteArray, actual: ByteBuffer) { + assertEquals(ByteBuffer.wrap(expected), actual) + assertSame(ReadOnlyBufferException::class.java, catchThrowable { actual.array() }.javaClass) + assertSame(array, actual.declaredField(ByteBuffer::class, "hb").value) + } + check(byteArrayOf(0, 1, 2, 3, 4), seq.slice()) + check(byteArrayOf(0, 1, 2, 3, 4), seq.slice(0, 5)) + check(byteArrayOf(0, 1, 2, 3, 4), seq.slice(0, 6)) + check(byteArrayOf(0, 1, 2, 3), seq.slice(0, 4)) + check(byteArrayOf(1, 2, 3), seq.slice(1, 4)) + check(byteArrayOf(1, 2, 3, 4), seq.slice(1, 5)) + check(byteArrayOf(1, 2, 3, 4), seq.slice(1, 6)) + check(byteArrayOf(4), seq.slice(4)) + check(byteArrayOf(), seq.slice(5)) + check(byteArrayOf(), seq.slice(6)) + check(byteArrayOf(2), seq.slice(2, 3)) + check(byteArrayOf(), seq.slice(2, 2)) + check(byteArrayOf(), seq.slice(2, 1)) + } +} diff --git a/core/src/test/kotlin/net/corda/core/utilities/EncodingUtilsTest.kt b/core/src/test/kotlin/net/corda/core/utilities/EncodingUtilsTest.kt index e3a6d0abaf..23c719fa24 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/EncodingUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/EncodingUtilsTest.kt @@ -1,6 +1,7 @@ package net.corda.core.utilities import net.corda.core.crypto.AddressFormatException +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.junit.Test import kotlin.test.assertEquals import kotlin.test.fail @@ -23,10 +24,9 @@ class EncodingUtilsTest { @Test fun `empty encoding`() { - val emptyByteArray = ByteArray(0) - assertEquals("", emptyByteArray.toBase58()) - assertEquals("", emptyByteArray.toBase64()) - assertEquals("", emptyByteArray.toHex()) + assertEquals("", EMPTY_BYTE_ARRAY.toBase58()) + assertEquals("", EMPTY_BYTE_ARRAY.toBase64()) + assertEquals("", EMPTY_BYTE_ARRAY.toHex()) } @Test diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index 2db900832c..d97b86afd6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt @@ -10,9 +10,9 @@ import net.corda.core.node.NodeInfo import net.corda.core.node.NotaryInfo import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.deserialize +import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal._contextSerializationEnv -import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.nodeapi.internal.SignedNodeInfo @@ -20,7 +20,7 @@ import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -198,8 +198,8 @@ class NetworkBootstrapper { } private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() { - override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { - return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return magic == kryoMagic && target == SerializationContext.UseCase.P2P } override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt index dc3712d56e..3c2eb8fa76 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ClientContexts.kt @@ -4,8 +4,8 @@ package net.corda.nodeapi.internal.serialization import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults -import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.amqp.amqpMagic +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic /* * Serialisation contexts for the client. @@ -13,14 +13,13 @@ import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 * servers from trying to instantiate any of them. */ -val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, +val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(kryoMagic, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, SerializationContext.UseCase.RPCClient) - -val AMQP_RPC_CLIENT_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0, +val AMQP_RPC_CLIENT_CONTEXT = SerializationContextImpl(amqpMagic, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt new file mode 100644 index 0000000000..6414efbb17 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationFormat.kt @@ -0,0 +1,12 @@ +package net.corda.nodeapi.internal.serialization + +import net.corda.core.utilities.ByteSequence +import net.corda.core.utilities.OpaqueBytes +import java.nio.ByteBuffer + +class CordaSerializationMagic(bytes: ByteArray) : OpaqueBytes(bytes) { + private val bufferView = slice() + fun consume(data: ByteSequence): ByteBuffer? { + return if (data.slice(end = size) == bufferView) data.slice(size) else null + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt index 304340f342..95dcc9b603 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt @@ -4,9 +4,12 @@ import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash +import net.corda.core.internal.copyBytes import net.corda.core.serialization.* import net.corda.core.utilities.ByteSequence import net.corda.nodeapi.internal.AttachmentsClassLoader +import net.corda.nodeapi.internal.serialization.amqp.amqpMagic +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import org.slf4j.LoggerFactory import java.io.NotSerializableException import java.util.* @@ -15,17 +18,7 @@ import java.util.concurrent.ExecutionException val attachmentsClassLoaderEnabledPropertyName = "attachments.class.loader.enabled" -object NotSupportedSerializationScheme : SerializationScheme { - private fun doThrow(): Nothing = throw UnsupportedOperationException("Serialization scheme not supported.") - - override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean = doThrow() - - override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T = doThrow() - - override fun serialize(obj: T, context: SerializationContext): SerializedBytes = doThrow() -} - -data class SerializationContextImpl(override val preferredSerializationVersion: VersionHeader, +data class SerializationContextImpl(override val preferredSerializationVersion: SerializationMagic, override val deserializationClassLoader: ClassLoader, override val whitelist: ClassWhitelist, override val properties: Map, @@ -76,14 +69,14 @@ data class SerializationContextImpl(override val preferredSerializationVersion: }) } - override fun withPreferredSerializationVersion(versionHeader: VersionHeader) = copy(preferredSerializationVersion = versionHeader) + override fun withPreferredSerializationVersion(magic: SerializationMagic) = copy(preferredSerializationVersion = magic) } -private const val HEADER_SIZE: Int = 8 - -fun ByteSequence.obtainHeaderSignature(): VersionHeader = take(HEADER_SIZE).copy() - open class SerializationFactoryImpl : SerializationFactory() { + companion object { + private val magicSize = sequenceOf(kryoMagic, amqpMagic).map { it.size }.distinct().single() + } + private val creator: List = Exception().stackTrace.asList() private val registeredSchemes: MutableCollection = Collections.synchronizedCollection(mutableListOf()) @@ -91,19 +84,17 @@ open class SerializationFactoryImpl : SerializationFactory() { private val logger = LoggerFactory.getLogger(javaClass) // TODO: This is read-mostly. Probably a faster implementation to be found. - private val schemes: ConcurrentHashMap, SerializationScheme> = ConcurrentHashMap() + private val schemes: ConcurrentHashMap, SerializationScheme> = ConcurrentHashMap() - private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): Pair { - // truncate sequence to 8 bytes, and make sure it's a copy to avoid holding onto large ByteArrays - val lookupKey = byteSequence.obtainHeaderSignature() to target - val scheme = schemes.computeIfAbsent(lookupKey) { - registeredSchemes - .filter { scheme -> scheme.canDeserializeVersion(it.first, it.second) } - .forEach { return@computeIfAbsent it } + private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): Pair { + // truncate sequence to at most magicSize, and make sure it's a copy to avoid holding onto large ByteArrays + val magic = CordaSerializationMagic(byteSequence.slice(end = magicSize).copyBytes()) + val lookupKey = magic to target + return schemes.computeIfAbsent(lookupKey) { + registeredSchemes.filter { it.canDeserializeVersion(magic, target) }.forEach { return@computeIfAbsent it } // XXX: Not single? logger.warn("Cannot find serialization scheme for: $lookupKey, registeredSchemes are: $registeredSchemes") - NotSupportedSerializationScheme - } - return scheme to lookupKey.first + throw UnsupportedOperationException("Serialization scheme not supported.") + } to magic } @Throws(NotSerializableException::class) @@ -115,9 +106,9 @@ open class SerializationFactoryImpl : SerializationFactory() { override fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext { return asCurrent { withCurrentContext(context) { - val (scheme, versionHeader) = schemeFor(byteSequence, context.useCase) + val (scheme, magic) = schemeFor(byteSequence, context.useCase) val deserializedObject = scheme.deserialize(byteSequence, clazz, context) - ObjectWithCompatibleContext(deserializedObject, context.withPreferredSerializationVersion(versionHeader)) + ObjectWithCompatibleContext(deserializedObject, context.withPreferredSerializationVersion(magic)) } } } @@ -149,9 +140,7 @@ open class SerializationFactoryImpl : SerializationFactory() { interface SerializationScheme { - // byteSequence expected to just be the 8 bytes necessary for versioning - fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean - + fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean @Throws(NotSerializableException::class) fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt index 53595b9aa7..a2bfa64628 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/ServerContexts.kt @@ -5,8 +5,8 @@ package net.corda.nodeapi.internal.serialization import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults -import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.amqp.amqpMagic +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic object QuasarWhitelist : ClassWhitelist { override fun hasListed(type: Class<*>): Boolean = true @@ -22,29 +22,25 @@ object QuasarWhitelist : ClassWhitelist { * MUST be kept separate! */ -val KRYO_RPC_SERVER_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, +val KRYO_RPC_SERVER_CONTEXT = SerializationContextImpl(kryoMagic, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, SerializationContext.UseCase.RPCServer) - -val KRYO_STORAGE_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, +val KRYO_STORAGE_CONTEXT = SerializationContextImpl(kryoMagic, SerializationDefaults.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.Storage) - - -val AMQP_STORAGE_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0, +val AMQP_STORAGE_CONTEXT = SerializationContextImpl(amqpMagic, SerializationDefaults.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.Storage) - -val AMQP_RPC_SERVER_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0, +val AMQP_RPC_SERVER_CONTEXT = SerializationContextImpl(amqpMagic, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SharedContexts.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SharedContexts.kt index 20dcdd7f54..25e4e278a1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SharedContexts.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SharedContexts.kt @@ -4,8 +4,8 @@ package net.corda.nodeapi.internal.serialization import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationDefaults -import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.amqp.amqpMagic +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic /* * Serialisation contexts shared by the server and client. @@ -15,21 +15,19 @@ import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 * MUST be kept separate from these ones! */ -val KRYO_P2P_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, +val KRYO_P2P_CONTEXT = SerializationContextImpl(kryoMagic, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), true, SerializationContext.UseCase.P2P) - -val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, +val KRYO_CHECKPOINT_CONTEXT = SerializationContextImpl(kryoMagic, SerializationDefaults.javaClass.classLoader, QuasarWhitelist, emptyMap(), true, SerializationContext.UseCase.Checkpoint) - -val AMQP_P2P_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0, +val AMQP_P2P_CONTEXT = SerializationContextImpl(amqpMagic, SerializationDefaults.javaClass.classLoader, GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), emptyMap(), diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt index 00a7825d06..8d07afcace 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPSerializationScheme.kt @@ -4,6 +4,7 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.cordapp.Cordapp import net.corda.core.serialization.* +import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.core.utilities.ByteSequence import net.corda.nodeapi.internal.serialization.DefaultWhitelist import net.corda.nodeapi.internal.serialization.MutableClassWhitelist @@ -12,7 +13,7 @@ import java.security.PublicKey import java.util.* import java.util.concurrent.ConcurrentHashMap -val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == AmqpHeaderV1_0 +val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == amqpMagic fun SerializerFactory.addToWhitelist(vararg types: Class<*>) { require(types.toSet().size == types.size) { @@ -106,7 +107,7 @@ abstract class AbstractAMQPSerializationScheme(val cordappLoader: List) return SerializationOutput(serializerFactory).serialize(obj) } - protected fun canDeserializeVersion(byteSequence: ByteSequence): Boolean = byteSequence == AmqpHeaderV1_0 + protected fun canDeserializeVersion(magic: CordaSerializationMagic) = magic == amqpMagic } // TODO: This will eventually cover server RPC as well and move to node module, but for now this is not implemented @@ -119,9 +120,9 @@ class AMQPServerSerializationScheme(cordapps: List = emptyList()) : Abs TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } - override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { - return (canDeserializeVersion(byteSequence) && - (target == SerializationContext.UseCase.P2P || target == SerializationContext.UseCase.Storage)) + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return canDeserializeVersion(magic) && + (target == SerializationContext.UseCase.P2P || target == SerializationContext.UseCase.Storage) } } @@ -136,9 +137,9 @@ class AMQPClientSerializationScheme(cordapps: List = emptyList()) : Abs throw UnsupportedOperationException() } - override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { - return (canDeserializeVersion(byteSequence) && - (target == SerializationContext.UseCase.P2P || target == SerializationContext.UseCase.Storage)) + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return canDeserializeVersion(magic) && + (target == SerializationContext.UseCase.P2P || target == SerializationContext.UseCase.Storage) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt index ac61820218..da555798e1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializationInput.kt @@ -13,7 +13,6 @@ import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.TypeVariable import java.lang.reflect.WildcardType -import java.nio.ByteBuffer data class ObjectAndEnvelope(val obj: T, val envelope: Envelope) @@ -58,19 +57,12 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) { deserializeAndReturnEnvelope(bytes, T::class.java) @Throws(NotSerializableException::class) - internal fun getEnvelope(bytes: ByteSequence): Envelope { + internal fun getEnvelope(byteSequence: ByteSequence): Envelope { // Check that the lead bytes match expected header - val headerSize = AmqpHeaderV1_0.size - if (bytes.take(headerSize) != AmqpHeaderV1_0) { - throw NotSerializableException("Serialization header does not match.") - } - + val dataBytes = amqpMagic.consume(byteSequence) ?: throw NotSerializableException("Serialization header does not match.") val data = Data.Factory.create() - val size = data.decode(ByteBuffer.wrap(bytes.bytes, bytes.offset + headerSize, bytes.size - headerSize)) - if (size.toInt() != bytes.size - headerSize) { - throw NotSerializableException("Unexpected size of data") - } - + val expectedSize = dataBytes.remaining() + if (data.decode(dataBytes) != expectedSize.toLong()) throw NotSerializableException("Unexpected size of data") return Envelope.get(data) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index e309882d7b..f5edd923c9 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -3,7 +3,7 @@ package net.corda.nodeapi.internal.serialization.amqp import com.google.common.hash.Hasher import com.google.common.hash.Hashing import net.corda.core.internal.uncheckedCast -import net.corda.core.utilities.OpaqueBytes +import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toBase64 import org.apache.qpid.proton.amqp.DescribedType @@ -18,9 +18,7 @@ import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterFiel import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema const val DESCRIPTOR_DOMAIN: String = "net.corda" - -// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB -val AmqpHeaderV1_0: OpaqueBytes = OpaqueBytes("corda\u0001\u0000\u0000".toByteArray()) +val amqpMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(1, 0, 0)) /** * This and the classes below are OO representations of the AMQP XML schema described in the specification. Their diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt index 94b17f6efd..f6b9972ec7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt @@ -69,7 +69,7 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory } val bytes = ByteArray(data.encodedSize().toInt() + 8) val buf = ByteBuffer.wrap(bytes) - buf.put(AmqpHeaderV1_0.bytes) + amqpMagic.putTo(buf) data.encode(buf) return SerializedBytes(bytes) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OpaqueBytesSubSequenceSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OpaqueBytesSubSequenceSerializer.kt index 6744828bcc..ec10ee72f8 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OpaqueBytesSubSequenceSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/OpaqueBytesSubSequenceSerializer.kt @@ -13,8 +13,6 @@ import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory class OpaqueBytesSubSequenceSerializer(factory: SerializerFactory) : CustomSerializer.Proxy(OpaqueBytesSubSequence::class.java, OpaqueBytes::class.java, factory) { override val additionalSerializers: Iterable> = emptyList() - - override fun toProxy(obj: OpaqueBytesSubSequence): OpaqueBytes = OpaqueBytes(obj.copy().bytes) - + override fun toProxy(obj: OpaqueBytesSubSequence): OpaqueBytes = OpaqueBytes(obj.copyBytes()) override fun fromProxy(proxy: OpaqueBytes): OpaqueBytesSubSequence = OpaqueBytesSubSequence(proxy.bytes, proxy.offset, proxy.size) } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt index 5470dd2cf0..a8d2e48649 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt @@ -44,7 +44,6 @@ import org.objenesis.strategy.StdInstantiatorStrategy import org.slf4j.Logger import sun.security.ec.ECPublicKeyImpl import sun.security.provider.certpath.X509CertPath -import sun.security.x509.X509CertImpl import java.io.BufferedInputStream import java.io.ByteArrayOutputStream import java.io.FileInputStream @@ -200,7 +199,7 @@ object DefaultKryoCustomizer { private object ContractAttachmentSerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: ContractAttachment) { if (kryo.serializationContext() != null) { - output.writeBytes(obj.attachment.id.bytes) + obj.attachment.id.writeTo(output) } else { val buffer = ByteArrayOutputStream() obj.attachment.open().use { it.copyTo(buffer) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt index 18b598919c..2cc255438f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt @@ -84,8 +84,8 @@ import kotlin.reflect.jvm.javaType */ object SerializedBytesSerializer : Serializer>() { override fun write(kryo: Kryo, output: Output, obj: SerializedBytes) { - output.writeVarInt(obj.bytes.size, true) - output.writeBytes(obj.bytes) + output.writeVarInt(obj.size, true) + obj.writeTo(output) } override fun read(kryo: Kryo, input: Input, type: Class>): SerializedBytes { @@ -186,7 +186,7 @@ object InputStreamSerializer : Serializer() { output.writeInt(numberOfBytesRead, true) output.writeBytes(buffer, 0, numberOfBytesRead) } else { - output.writeInt(0) + output.writeInt(0, true) break } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt index 737a330bba..719d982db3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoSerializationScheme.kt @@ -1,27 +1,25 @@ package net.corda.nodeapi.internal.serialization.kryo import java.util.concurrent.ConcurrentHashMap -import java.io.ByteArrayOutputStream import co.paralleluniverse.fibers.Fiber import co.paralleluniverse.io.serialization.kryo.KryoSerializer import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.Serializer +import com.esotericsoftware.kryo.io.ByteBufferInputStream import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.pool.KryoPool import com.esotericsoftware.kryo.serializers.ClosureSerializer import net.corda.core.internal.uncheckedCast -import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.ByteSequence import net.corda.core.serialization.* -import net.corda.core.internal.LazyPool +import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.nodeapi.internal.serialization.CordaClassResolver import net.corda.nodeapi.internal.serialization.SerializationScheme import java.security.PublicKey -// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB -val KryoHeaderV0_1: OpaqueBytes = OpaqueBytes("corda\u0000\u0000\u0001".toByteArray(Charsets.UTF_8)) +val kryoMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(0, 0, 1)) private object AutoCloseableSerialisationDetector : Serializer() { override fun write(kryo: Kryo, output: Output, closeable: AutoCloseable) { @@ -73,65 +71,41 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme { } } - private fun withContext(kryo: Kryo, context: SerializationContext, block: (Kryo) -> T): T { - kryo.context.ensureCapacity(context.properties.size) - context.properties.forEach { kryo.context.put(it.key, it.value) } - try { - return block(kryo) - } finally { - kryo.context.clear() + private fun SerializationContext.kryo(task: Kryo.() -> T): T { + return getPool(this).run { kryo -> + kryo.context.ensureCapacity(properties.size) + properties.forEach { kryo.context.put(it.key, it.value) } + try { + kryo.task() + } finally { + kryo.context.clear() + } } } override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - val pool = getPool(context) - val headerSize = KryoHeaderV0_1.size - val header = byteSequence.take(headerSize) - if (header != KryoHeaderV0_1) { - throw KryoException("Serialized bytes header does not match expected format.") - } - Input(byteSequence.bytes, byteSequence.offset + headerSize, byteSequence.size - headerSize).use { input -> - return pool.run { kryo -> - withContext(kryo, context) { - if (context.objectReferencesEnabled) { - uncheckedCast(kryo.readClassAndObject(input)) - } else { - kryo.withoutReferences { uncheckedCast(kryo.readClassAndObject(input)) } - } + val dataBytes = kryoMagic.consume(byteSequence) ?: throw KryoException("Serialized bytes header does not match expected format.") + return context.kryo { + kryoInput(ByteBufferInputStream(dataBytes)) { + if (context.objectReferencesEnabled) { + uncheckedCast(readClassAndObject(this)) + } else { + withoutReferences { uncheckedCast(readClassAndObject(this)) } } } } } override fun serialize(obj: T, context: SerializationContext): SerializedBytes { - val pool = getPool(context) - return pool.run { kryo -> - withContext(kryo, context) { - serializeOutputStreamPool.run { stream -> - serializeBufferPool.run { buffer -> - Output(buffer).use { - it.outputStream = stream - it.writeBytes(KryoHeaderV0_1.bytes) - if (context.objectReferencesEnabled) { - kryo.writeClassAndObject(it, obj) - } else { - kryo.withoutReferences { kryo.writeClassAndObject(it, obj) } - } - } - SerializedBytes(stream.toByteArray()) - } + return context.kryo { + SerializedBytes(kryoOutput { + kryoMagic.writeTo(this) + if (context.objectReferencesEnabled) { + writeClassAndObject(this, obj) + } else { + withoutReferences { writeClassAndObject(this, obj) } } - } + }) } } } - -private val serializeBufferPool = LazyPool( - newInstance = { ByteArray(64 * 1024) } -) - -private val serializeOutputStreamPool = LazyPool( - clear = ByteArrayOutputStream::reset, - shouldReturnToPool = { it.size() < 256 * 1024 }, // Discard if it grew too large - newInstance = { ByteArrayOutputStream(64 * 1024) } -) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt new file mode 100644 index 0000000000..9a34131a30 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt @@ -0,0 +1,43 @@ +package net.corda.nodeapi.internal.serialization.kryo + +import com.esotericsoftware.kryo.io.Input +import com.esotericsoftware.kryo.io.Output +import net.corda.core.internal.LazyPool +import java.io.* + +private val serializationBufferPool = LazyPool( + newInstance = { ByteArray(64 * 1024) }) +private val serializeOutputStreamPool = LazyPool( + clear = ByteArrayOutputStream::reset, + shouldReturnToPool = { it.size() < 256 * 1024 }, // Discard if it grew too large + newInstance = { ByteArrayOutputStream(64 * 1024) }) + +internal fun kryoInput(underlying: InputStream, task: Input.() -> T): T { + return serializationBufferPool.run { + Input(it).use { input -> + input.inputStream = underlying + input.task() + } + } +} + +internal fun kryoOutput(task: Output.() -> T): ByteArray { + return serializeOutputStreamPool.run { underlying -> + serializationBufferPool.run { + Output(it).use { output -> + output.outputStream = underlying + output.task() + } + } + underlying.toByteArray() // Must happen after close, to allow ZIP footer to be written for example. + } +} + +internal fun Output.substitute(transform: (OutputStream) -> OutputStream) { + flush() + outputStream = transform(outputStream) +} + +internal fun Input.substitute(transform: (InputStream) -> InputStream) { + inputStream = transform(SequenceInputStream(ByteArrayInputStream(buffer.copyOfRange(position(), limit())), inputStream)) +} diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java index 317f9d2aea..265cbf098b 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/ForbiddenLambdaSerializationTests.java @@ -33,8 +33,7 @@ public final class ForbiddenLambdaSerializationTests { EnumSet contexts = EnumSet.complementOf(EnumSet.of(SerializationContext.UseCase.Checkpoint)); contexts.forEach(ctx -> { - SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); - + SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); String value = "Hey"; Callable target = (Callable & Serializable) () -> value; @@ -56,8 +55,7 @@ public final class ForbiddenLambdaSerializationTests { EnumSet contexts = EnumSet.complementOf(EnumSet.of(SerializationContext.UseCase.Checkpoint)); contexts.forEach(ctx -> { - SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); - + SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx); String value = "Hey"; Callable target = () -> value; diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java index 9e84dcc49e..0203c498f4 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/LambdaCheckpointSerializationTest.java @@ -26,7 +26,7 @@ public final class LambdaCheckpointSerializationTest { @Before public void setup() { factory = testSerialization.getSerializationFactory(); - context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoHeaderV0_1(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint); + context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(), this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, SerializationContext.UseCase.Checkpoint); } @Test diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index c37d6b6eb1..2f3e7420fd 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -14,7 +14,7 @@ import net.corda.nodeapi.internal.createDevKeyStores import net.corda.nodeapi.internal.serialization.AllWhitelist import net.corda.nodeapi.internal.serialization.SerializationContextImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.TestIdentity @@ -314,7 +314,7 @@ class X509UtilitiesTest { @Test fun `serialize - deserialize X509Certififcate`() { val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } - val context = SerializationContextImpl(KryoHeaderV0_1, + val context = SerializationContextImpl(kryoMagic, javaClass.classLoader, AllWhitelist, emptyMap(), @@ -329,7 +329,7 @@ class X509UtilitiesTest { @Test fun `serialize - deserialize X509CertPath`() { val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } - val context = SerializationContextImpl(KryoHeaderV0_1, + val context = SerializationContextImpl(kryoMagic, javaClass.classLoader, AllWhitelist, emptyMap(), diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt index 383c90e196..e39c2e87a9 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ContractAttachmentSerializerTest.kt @@ -7,6 +7,7 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Assert.assertArrayEquals @@ -35,7 +36,7 @@ class ContractAttachmentSerializerTest { @Test fun `write contract attachment and read it back`() { - val contractAttachment = ContractAttachment(GeneratedAttachment(ByteArray(0)), DummyContract.PROGRAM_ID) + val contractAttachment = ContractAttachment(GeneratedAttachment(EMPTY_BYTE_ARRAY), DummyContract.PROGRAM_ID) // no token context so will serialize the whole attachment val serialized = contractAttachment.serialize(factory, context) val deserialized = serialized.deserialize(factory, context) @@ -88,8 +89,7 @@ class ContractAttachmentSerializerTest { @Test fun `check attachment in deserialize is lazy loaded when using token context`() { - val attachment = GeneratedAttachment(ByteArray(0)) - + val attachment = GeneratedAttachment(EMPTY_BYTE_ARRAY) // don't importAttachment in mockService val contractAttachment = ContractAttachment(attachment, DummyContract.PROGRAM_ID) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt index 1743d9ee46..a66f3038b4 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt @@ -11,7 +11,7 @@ import net.corda.core.serialization.* import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.nodeapi.internal.AttachmentsClassLoaderTests import net.corda.nodeapi.internal.serialization.kryo.CordaKryo -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.testing.services.MockAttachmentStorage import net.corda.testing.internal.rigorousMock import org.junit.Rule @@ -108,9 +108,8 @@ class CordaClassResolverTests { val emptyMapClass = mapOf().javaClass } - private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P) - private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P) - + private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P) + private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(kryoMagic, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P) @Test fun `Annotation on enum works for specialised entries`() { CordaClassResolver(emptyWhitelistContext).getRegistration(Foo.Bar::class.java) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt index 7cc408b4ea..5d54a38fbe 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/KryoTests.kt @@ -13,14 +13,13 @@ import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.sequence import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.services.persistence.NodeAttachmentService -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.testing.core.ALICE_NAME -import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Assert.assertArrayEquals import org.junit.Before -import org.junit.Rule import org.junit.Test import org.slf4j.LoggerFactory import java.io.ByteArrayInputStream @@ -28,6 +27,7 @@ import java.io.InputStream import java.time.Instant import java.util.* import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -36,16 +36,13 @@ class KryoTests { private val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey } - @Rule - @JvmField - val testSerialization = SerializationEnvironmentRule() private lateinit var factory: SerializationFactory private lateinit var context: SerializationContext @Before fun setup() { factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } - context = SerializationContextImpl(KryoHeaderV0_1, + context = SerializationContextImpl(kryoMagic, javaClass.classLoader, AllWhitelist, emptyMap(), @@ -150,6 +147,14 @@ class KryoTests { assertEquals(-1, readRubbishStream.read()) } + @Test + fun `InputStream serialisation does not write trailing garbage`() { + val byteArrays = listOf("123", "456").map { it.toByteArray() } + val streams = byteArrays.map { it.inputStream() }.serialize(factory, context).deserialize(factory, context).iterator() + byteArrays.forEach { assertArrayEquals(it, streams.next().readBytes()) } + assertFalse(streams.hasNext()) + } + @Test fun `serialize - deserialize SignableData`() { val testString = "Hello World" @@ -249,7 +254,7 @@ class KryoTests { } Tmp() val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) } - val context = SerializationContextImpl(KryoHeaderV0_1, + val context = SerializationContextImpl(kryoMagic, javaClass.classLoader, AllWhitelist, emptyMap(), diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt index e19fe31a3c..87b1b50a27 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt @@ -7,7 +7,7 @@ import net.corda.node.services.statemachine.SessionData import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput import net.corda.nodeapi.internal.serialization.amqp.Envelope import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.testing.internal.amqpSpecific import net.corda.testing.internal.kryoSpecific import net.corda.testing.core.SerializationEnvironmentRule @@ -68,7 +68,7 @@ class ListsSerializationTest { val nameID = 0 val serializedForm = emptyList().serialize() val output = ByteArrayOutputStream().apply { - write(KryoHeaderV0_1.bytes) + kryoMagic.writeTo(this) write(DefaultClassResolver.NAME + 2) write(nameID) write(javaEmptyListClass.name.toAscii()) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt index 577ca6b5f9..d1b9af493c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt @@ -7,7 +7,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.node.services.statemachine.SessionData -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.internal.amqpSpecific import net.corda.testing.internal.kryoSpecific @@ -78,7 +78,7 @@ class MapsSerializationTest { val nameID = 0 val serializedForm = emptyMap().serialize() val output = ByteArrayOutputStream().apply { - write(KryoHeaderV0_1.bytes) + kryoMagic.writeTo(this) write(DefaultClassResolver.NAME + 2) write(nameID) write(javaEmptyMapClass.name.toAscii()) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt index cd10488152..17495cb360 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt @@ -7,7 +7,7 @@ import net.corda.core.serialization.* import net.corda.core.utilities.OpaqueBytes import net.corda.nodeapi.internal.serialization.kryo.CordaKryo import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.testing.internal.rigorousMock import net.corda.testing.core.SerializationEnvironmentRule import org.assertj.core.api.Assertions.assertThat @@ -98,7 +98,7 @@ class SerializationTokenTest { val kryo: Kryo = DefaultKryoCustomizer.customize(CordaKryo(CordaClassResolver(this.context))) val stream = ByteArrayOutputStream() Output(stream).use { - it.write(KryoHeaderV0_1.bytes) + kryoMagic.writeTo(it) kryo.writeClass(it, SingletonSerializeAsToken::class.java) kryo.writeObject(it, emptyList()) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt index 65828eeebf..fb18178b36 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt @@ -5,7 +5,7 @@ import com.esotericsoftware.kryo.util.DefaultClassResolver import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.node.services.statemachine.SessionData -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.testing.internal.kryoSpecific import net.corda.testing.core.SerializationEnvironmentRule import org.junit.Assert.assertArrayEquals @@ -55,7 +55,7 @@ class SetsSerializationTest { val nameID = 0 val serializedForm = emptySet().serialize() val output = ByteArrayOutputStream().apply { - write(KryoHeaderV0_1.bytes) + kryoMagic.writeTo(this) write(DefaultClassResolver.NAME + 2) write(nameID) write(javaEmptySetClass.name.toAscii()) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt index 2b6ee350f6..a08bcb5a3c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/OverridePKSerializerTest.kt @@ -1,7 +1,7 @@ package net.corda.nodeapi.internal.serialization.amqp import net.corda.core.serialization.SerializationContext -import net.corda.core.utilities.ByteSequence +import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT import org.apache.qpid.proton.codec.Data import org.assertj.core.api.Assertions @@ -30,8 +30,7 @@ class OverridePKSerializerTest { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } - override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean = true - + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) = true override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt new file mode 100644 index 0000000000..ac9779f828 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreamsTest.kt @@ -0,0 +1,60 @@ +package net.corda.nodeapi.internal.serialization.kryo + +import org.junit.Assert.assertArrayEquals +import org.junit.Test +import java.io.* +import java.util.* +import java.util.zip.DeflaterOutputStream +import java.util.zip.InflaterInputStream +import kotlin.test.assertEquals + +class KryoStreamsTest { + class NegOutputStream(private val stream: OutputStream) : OutputStream() { + override fun write(b: Int) = stream.write(-b) + } + + class NegInputStream(private val stream: InputStream) : InputStream() { + override fun read() = stream.read().let { + if (it != -1) 0xff and -it else -1 + } + } + + @Test + fun `substitute output works`() { + assertArrayEquals(byteArrayOf(100, -101), kryoOutput { + write(100) + substitute(::NegOutputStream) + write(101) + }) + } + + @Test + fun `substitute input works`() { + kryoInput(byteArrayOf(100, 101).inputStream()) { + assertEquals(100, read()) + substitute(::NegInputStream) + assertEquals(-101, read().toByte()) + assertEquals(-1, read()) + } + } + + @Test + fun `zip round-trip`() { + val data = ByteArray(12345).also { Random(0).nextBytes(it) } + val encoded = kryoOutput { + write(data) + substitute(::DeflaterOutputStream) + write(data) + substitute(::DeflaterOutputStream) // Potentially useful if a different codec. + write(data) + } + kryoInput(encoded.inputStream()) { + assertArrayEquals(data, readBytes(data.size)) + substitute(::InflaterInputStream) + assertArrayEquals(data, readBytes(data.size)) + substitute(::InflaterInputStream) + assertArrayEquals(data, readBytes(data.size)) + assertEquals(-1, read()) + } + } +} diff --git a/node/src/main/kotlin/net/corda/node/serialization/KryoServerSerializationScheme.kt b/node/src/main/kotlin/net/corda/node/serialization/KryoServerSerializationScheme.kt index 21beb36b47..2941bc479a 100644 --- a/node/src/main/kotlin/net/corda/node/serialization/KryoServerSerializationScheme.kt +++ b/node/src/main/kotlin/net/corda/node/serialization/KryoServerSerializationScheme.kt @@ -2,16 +2,16 @@ package net.corda.node.serialization import com.esotericsoftware.kryo.pool.KryoPool import net.corda.core.serialization.SerializationContext -import net.corda.core.utilities.ByteSequence +import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.node.services.messaging.RpcServerObservableSerializer import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.nodeapi.internal.serialization.kryo.RPCKryo class KryoServerSerializationScheme : AbstractKryoSerializationScheme() { - override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { - return byteSequence == KryoHeaderV0_1 && target != SerializationContext.UseCase.RPCClient + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return magic == kryoMagic && target != SerializationContext.UseCase.RPCClient } override fun rpcClientKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException() diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 9c77d45ded..307e034fda 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -16,6 +16,7 @@ import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import java.security.InvalidAlgorithmParameterException import java.security.PublicKey import java.security.cert.* @@ -73,7 +74,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, @Lob @Column(name = "identity_value") - var identity: ByteArray = ByteArray(0) + var identity: ByteArray = EMPTY_BYTE_ARRAY ) @Entity diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index c72a20fcd8..02018515d7 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -8,6 +8,7 @@ import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.bouncycastle.operator.ContentSigner import java.security.KeyPair import java.security.PrivateKey @@ -37,11 +38,10 @@ class PersistentKeyManagementService(val identityService: IdentityServiceInterna @Lob @Column(name = "public_key") - var publicKey: ByteArray = ByteArray(0), - + var publicKey: ByteArray = EMPTY_BYTE_ARRAY, @Lob @Column(name = "private_key") - var privateKey: ByteArray = ByteArray(0) + var privateKey: ByteArray = EMPTY_BYTE_ARRAY ) { constructor(publicKey: PublicKey, privateKey: PrivateKey) : this(publicKey.toStringShort(), publicKey.encoded, privateKey.encoded) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index 52195fd72c..dd7358bad4 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -40,6 +40,7 @@ import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.ClientConsumer import org.apache.activemq.artemis.api.core.client.ClientMessage import org.apache.activemq.artemis.api.core.client.ClientSession +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import rx.Subscription import java.security.PublicKey import java.time.Instant @@ -196,11 +197,10 @@ class P2PMessagingClient(config: NodeConfiguration, @Lob @Column - var message: ByteArray = ByteArray(0), - + var message: ByteArray = EMPTY_BYTE_ARRAY, @Lob @Column - var recipients: ByteArray = ByteArray(0) + var recipients: ByteArray = EMPTY_BYTE_ARRAY ) fun start() { diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt index 5e92461c18..6507b86c47 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBCheckpointStorage.kt @@ -5,6 +5,7 @@ import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.currentDBSession +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Id @@ -24,13 +25,13 @@ class DBCheckpointStorage : CheckpointStorage { @Lob @Column(name = "checkpoint_value") - var checkpoint: ByteArray = ByteArray(0) + var checkpoint: ByteArray = EMPTY_BYTE_ARRAY ) override fun addCheckpoint(checkpoint: Checkpoint) { currentDBSession().save(DBCheckpoint().apply { checkpointId = checkpoint.id.toString() - this.checkpoint = checkpoint.serializedFiber.bytes + this.checkpoint = checkpoint.serializedFiber.bytes // XXX: Is copying the byte array necessary? }) } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt index 893579af03..9661de7792 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt @@ -13,6 +13,7 @@ import net.corda.node.utilities.* import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import rx.Observable import rx.subjects.PublishSubject import java.util.* @@ -34,7 +35,7 @@ class DBTransactionStorage(cacheSizeBytes: Long) : WritableTransactionStorage, S @Lob @Column(name = "transaction_value") - var transaction: ByteArray = ByteArray(0) + var transaction: ByteArray = EMPTY_BYTE_ARRAY ) private companion object { diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt index c007475d26..507669e91c 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt @@ -13,6 +13,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.contextLogger import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.hibernate.annotations.Type import java.io.Serializable import java.util.* @@ -45,7 +46,7 @@ class PersistentUniquenessProvider : UniquenessProvider, SingletonSerializeAsTok @Column(name = "requesting_party_key", length = 255) @Type(type = "corda-wrapper-binary") - var owningKey: ByteArray = ByteArray(0) + var owningKey: ByteArray = EMPTY_BYTE_ARRAY ) : Serializable @Entity diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 16590715fd..fd0a20de0f 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -33,6 +33,7 @@ import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import java.nio.file.Path import java.util.concurrent.CompletableFuture import javax.annotation.concurrent.ThreadSafe @@ -76,8 +77,7 @@ class RaftUniquenessProvider(private val transportConfiguration: NodeSSLConfigur @Lob @Column(name = "state_value") - var value: ByteArray = ByteArray(0), - + var value: ByteArray = EMPTY_BYTE_ARRAY, @Column(name = "state_index") var index: Long = 0 ) diff --git a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt index f049b040e0..0f57a4dc3c 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt @@ -6,6 +6,7 @@ import net.corda.node.services.messaging.TopicStringValidator import net.corda.node.services.messaging.createMessage import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockNetwork +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.junit.After import org.junit.Before import org.junit.Test @@ -93,9 +94,8 @@ class InMemoryMessagingTests { node1.network.addMessageHandler("valid_message") { _, _ -> received++ } - - val invalidMessage = node2.network.createMessage("invalid_message", data = ByteArray(0)) - val validMessage = node2.network.createMessage("valid_message", data = ByteArray(0)) + val invalidMessage = node2.network.createMessage("invalid_message", data = EMPTY_BYTE_ARRAY) + val validMessage = node2.network.createMessage("valid_message", data = EMPTY_BYTE_ARRAY) node2.network.send(invalidMessage, node1.network.myAddress) mockNet.runNetwork() assertEquals(0, received) @@ -106,8 +106,8 @@ class InMemoryMessagingTests { // Here's the core of the test; previously the unhandled message would cause runNetwork() to abort early, so // this would fail. Make fresh messages to stop duplicate uniqueMessageId causing drops - val invalidMessage2 = node2.network.createMessage("invalid_message", data = ByteArray(0)) - val validMessage2 = node2.network.createMessage("valid_message", data = ByteArray(0)) + val invalidMessage2 = node2.network.createMessage("invalid_message", data = EMPTY_BYTE_ARRAY) + val validMessage2 = node2.network.createMessage("valid_message", data = EMPTY_BYTE_ARRAY) node2.network.send(invalidMessage2, node1.network.myAddress) node2.network.send(validMessage2, node1.network.myAddress) mockNet.runNetwork() diff --git a/tools/explorer/build.gradle b/tools/explorer/build.gradle index 9eddf905ca..5b62086ed8 100644 --- a/tools/explorer/build.gradle +++ b/tools/explorer/build.gradle @@ -50,7 +50,6 @@ dependencies { // Controls FX: more java FX components http://fxexperience.com/controlsfx/ compile 'org.controlsfx:controlsfx:8.40.12' - compile 'commons-lang:commons-lang:2.6' // This provide com.apple.eawt stub for non-mac system. compile 'com.yuvimasory:orange-extensions:1.3.0' } diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt index fe79e5d5a1..afc019ed9a 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt @@ -12,6 +12,7 @@ import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.WireTransaction import net.corda.nodeapi.internal.serialization.GeneratedAttachment import net.corda.testing.contracts.DummyContract +import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import java.math.BigInteger import java.security.PublicKey import java.util.* @@ -37,7 +38,7 @@ data class GeneratedLedger( companion object { val empty = GeneratedLedger(emptyList(), emptyMap(), emptySet(), emptySet()) - val contractAttachment = ContractAttachment(GeneratedAttachment(ByteArray(0) { 0 }), DummyContract.PROGRAM_ID) + val contractAttachment = ContractAttachment(GeneratedAttachment(EMPTY_BYTE_ARRAY), DummyContract.PROGRAM_ID) } fun resolveWireTransaction(transaction: WireTransaction): LedgerTransaction { diff --git a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt index 81571dd722..947c88ee31 100644 --- a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt +++ b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt @@ -5,6 +5,7 @@ import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions import net.corda.core.internal.div import net.corda.core.serialization.SerializationContext +import net.corda.nodeapi.internal.serialization.CordaSerializationMagic import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.utilities.* @@ -17,10 +18,10 @@ import net.corda.nodeapi.internal.config.getValue import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.serialization.* import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme -import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 +import net.corda.nodeapi.internal.serialization.amqp.amqpMagic import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme -import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 +import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import org.apache.activemq.artemis.api.core.client.ActiveMQClient import java.nio.file.Path import java.nio.file.Paths @@ -96,8 +97,8 @@ class Verifier { } private object KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() { - override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { - return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return magic == kryoMagic && target == SerializationContext.UseCase.P2P } override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException() @@ -105,8 +106,8 @@ class Verifier { } private object AMQPVerifierSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) { - override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { - return (byteSequence == AmqpHeaderV1_0 && (target == SerializationContext.UseCase.P2P)) + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return magic == amqpMagic && target == SerializationContext.UseCase.P2P } override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException() From c8cf46c65781497c211b88a11e828e95192e9e60 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Fri, 9 Feb 2018 14:48:45 +0000 Subject: [PATCH 032/114] CORDA-961 Wire up and enforce max transaction size (#2465) * wire up and enforce max transaction size * fixup after rebase moved network parameter from AbstractNode to NodeProperties * removed TODO * fix broken import * address PR issues * remove API breaking change address PR issue * added max transaction size to driver and mock network. address PR issues * fix failing test * added TODO * fix verifier test * fix spring driver build error --- .ci/api-current.txt | 12 +- .../net/corda/core/contracts/Attachment.kt | 5 + .../corda/core/internal/AbstractAttachment.kt | 4 + .../corda/core/internal/GlobalProperties.kt | 14 ++ .../net/corda/core/node/NetworkParameters.kt | 1 - .../core/transactions/WireTransaction.kt | 20 ++- .../corda/core/contracts/StructuresTests.kt | 1 + .../AttachmentSerializationTest.kt | 1 + docs/source/changelog.rst | 3 + docs/source/network-map.rst | 1 + .../net/corda/node/internal/AbstractNode.kt | 23 +-- .../kotlin/net/corda/node/internal/Node.kt | 1 + .../transactions/MaxTransactionSizeTests.kt | 135 ++++++++++++++++++ .../net/corda/test/spring/SpringDriver.kt | 2 + .../kotlin/net/corda/testing/driver/Driver.kt | 11 +- .../kotlin/net/corda/testing/node/MockNode.kt | 11 +- .../net/corda/testing/node/MockServices.kt | 2 + .../net/corda/testing/node/NodeTestUtils.kt | 3 + .../testing/node/internal/DriverDSLImpl.kt | 15 +- .../corda/testing/node/internal/RPCDriver.kt | 8 +- .../common/internal/ParametersUtilities.kt | 3 +- .../corda/demobench/model/NodeController.kt | 2 +- .../net/corda/verifier/VerifierDriver.kt | 8 +- 23 files changed, 251 insertions(+), 35 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/internal/GlobalProperties.kt create mode 100644 node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 5ae28f1fe9..e3c153013e 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -280,6 +280,7 @@ public static final class net.corda.core.contracts.AmountTransfer$Companion exte @net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash public abstract void extractFile(String, java.io.OutputStream) @org.jetbrains.annotations.NotNull public abstract List getSigners() + public abstract int getSize() @org.jetbrains.annotations.NotNull public abstract java.io.InputStream open() @org.jetbrains.annotations.NotNull public abstract jar.JarInputStream openAsJAR() ## @@ -347,6 +348,7 @@ public final class net.corda.core.contracts.ComponentGroupEnum extends java.lang @org.jetbrains.annotations.NotNull public final String getContract() @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public List getSigners() + public int getSize() @org.jetbrains.annotations.NotNull public java.io.InputStream open() @org.jetbrains.annotations.NotNull public jar.JarInputStream openAsJAR() ## @@ -3584,7 +3586,7 @@ public static final class net.corda.client.jackson.StringToMethodCallParser$Unpa ## public final class net.corda.testing.driver.Driver extends java.lang.Object public static final Object driver(net.corda.testing.driver.DriverParameters, kotlin.jvm.functions.Function1) - public static final Object driver(net.corda.testing.driver.DriverParameters, boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, kotlin.jvm.functions.Function1) + public static final Object driver(net.corda.testing.driver.DriverParameters, boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, int, kotlin.jvm.functions.Function1) ## @net.corda.core.DoNotImplement public interface net.corda.testing.driver.DriverDSL @org.jetbrains.annotations.NotNull public abstract java.nio.file.Path baseDirectory(net.corda.core.identity.CordaX500Name) @@ -3599,11 +3601,12 @@ public final class net.corda.testing.driver.Driver extends java.lang.Object ## public final class net.corda.testing.driver.DriverParameters extends java.lang.Object public () - public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy) + public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, int) public final boolean component1() @org.jetbrains.annotations.NotNull public final List component10() @org.jetbrains.annotations.NotNull public final List component11() @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy component12() + public final int component13() @org.jetbrains.annotations.NotNull public final java.nio.file.Path component2() @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation component3() @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation component4() @@ -3612,13 +3615,14 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O public final boolean component7() public final boolean component8() public final boolean component9() - @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy) + @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.DriverParameters copy(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, Map, boolean, boolean, boolean, boolean, List, List, net.corda.testing.driver.JmxPolicy, int) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation getDebugPortAllocation() @org.jetbrains.annotations.NotNull public final java.nio.file.Path getDriverDirectory() @org.jetbrains.annotations.NotNull public final List getExtraCordappPackagesToScan() public final boolean getInitialiseSerialization() @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.JmxPolicy getJmxPolicy() + public final int getMaxTransactionSize() @org.jetbrains.annotations.NotNull public final List getNotarySpecs() @org.jetbrains.annotations.NotNull public final net.corda.testing.driver.PortAllocation getPortAllocation() public final boolean getStartNodesInProcess() @@ -3892,7 +3896,7 @@ public final class net.corda.testing.node.MockKeyManagementService extends net.c public class net.corda.testing.node.MockNetwork extends java.lang.Object public (List) public (List, net.corda.testing.node.MockNetworkParameters) - public (List, net.corda.testing.node.MockNetworkParameters, boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, kotlin.jvm.functions.Function1, boolean, List) + public (List, net.corda.testing.node.MockNetworkParameters, boolean, boolean, net.corda.testing.node.InMemoryMessagingNetwork$ServicePeerAllocationStrategy, kotlin.jvm.functions.Function1, boolean, List, int) @org.jetbrains.annotations.NotNull public final net.corda.testing.node.MockNetwork$MockNode addressToNode(net.corda.core.messaging.MessageRecipients) @org.jetbrains.annotations.NotNull public final java.nio.file.Path baseDirectory(int) @org.jetbrains.annotations.NotNull public final net.corda.node.internal.StartedNode createNode(net.corda.testing.node.MockNodeParameters) 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 641e730417..654fd51a89 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt @@ -50,4 +50,9 @@ interface Attachment : NamedByHash { * Can be empty, for example non-contract attachments won't be necessarily be signed. */ val signers: List + + /** + * Attachment size in bytes. + */ + val size: Int } diff --git a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt index 07c82039df..d48b98f290 100644 --- a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt @@ -27,6 +27,10 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment { } protected val attachmentData: ByteArray by lazy(dataLoader) + + // TODO: read file size information from metadata instead of loading the data. + override val size: Int get() = attachmentData.size + override fun open(): InputStream = attachmentData.inputStream() override val signers by lazy { // Can't start with empty set if we're doing intersections. Logically the null means "all possible signers": diff --git a/core/src/main/kotlin/net/corda/core/internal/GlobalProperties.kt b/core/src/main/kotlin/net/corda/core/internal/GlobalProperties.kt new file mode 100644 index 0000000000..bedac0269a --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/GlobalProperties.kt @@ -0,0 +1,14 @@ +package net.corda.core.internal + +import net.corda.core.node.NetworkParameters + +// TODO: This will cause problems when we run tests in parallel, make each node have its own properties. +object GlobalProperties { + private var _networkParameters: NetworkParameters? = null + + var networkParameters: NetworkParameters + get() = checkNotNull(_networkParameters) { "Property 'networkParameters' has not been initialised." } + set(value) { + _networkParameters = value + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt index f7076a8316..cca7c1433b 100644 --- a/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt +++ b/core/src/main/kotlin/net/corda/core/node/NetworkParameters.kt @@ -17,7 +17,6 @@ import java.time.Instant */ // TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network. // It needs separate design. -// TODO Currently maxTransactionSize is not wired. @CordaSerializable data class NetworkParameters( val minimumPlatformVersion: Int, diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index 7112369667..d973152262 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -5,6 +5,7 @@ import net.corda.core.contracts.ComponentGroupEnum.* import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.internal.Emoji +import net.corda.core.internal.GlobalProperties import net.corda.core.node.ServicesForResolution import net.corda.core.node.services.AttachmentId import net.corda.core.serialization.CordaSerializable @@ -118,7 +119,24 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr val contractAttachments = findAttachmentContracts(resolvedInputs, resolveContractAttachment, resolveAttachment) // Order of attachments is important since contracts may refer to indexes so only append automatic attachments val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct() - return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt) + val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt) + checkTransactionSize(ltx) + return ltx + } + + private fun checkTransactionSize(ltx: LedgerTransaction) { + var remainingTransactionSize = GlobalProperties.networkParameters.maxTransactionSize + + fun minus(size: Int) { + require(remainingTransactionSize > size) { "Transaction exceeded network's maximum transaction size limit : ${GlobalProperties.networkParameters.maxTransactionSize} bytes." } + remainingTransactionSize -= size + } + + // Check attachment size first as they are most likely to go over the limit. + ltx.attachments.forEach { minus(it.size) } + minus(ltx.inputs.serialize().size) + minus(ltx.commands.serialize().size) + minus(ltx.outputs.serialize().size) } /** diff --git a/core/src/test/kotlin/net/corda/core/contracts/StructuresTests.kt b/core/src/test/kotlin/net/corda/core/contracts/StructuresTests.kt index 47f69945dd..7afd26a416 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/StructuresTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/StructuresTests.kt @@ -31,6 +31,7 @@ class AttachmentTest { override val id get() = throw UnsupportedOperationException() override fun open() = inputStream override val signers get() = throw UnsupportedOperationException() + override val size: Int = 512 } try { attachment.openAsJAR() diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index 81b70a6c2b..56959a0cec 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -114,6 +114,7 @@ class AttachmentSerializationTest { private class CustomAttachment(override val id: SecureHash, internal val customContent: String) : Attachment { override fun open() = throw UnsupportedOperationException("Not implemented.") override val signers get() = throw UnsupportedOperationException() + override val size get() = throw UnsupportedOperationException() } private class CustomAttachmentLogic(serverIdentity: Party, private val attachmentId: SecureHash, private val customContent: String) : ClientLogic(serverIdentity) { diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 1e38ca9fee..bccaf5c085 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -75,6 +75,9 @@ UNRELEASED * Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This was needed to allow changes to the schema. + * Introduced max transaction size limit on transactions. The max transaction size parameter is set by the compatibility zone + operator. The parameter is distributed to Corda nodes by network map service as part of the ``NetworkParameters``. + * Support for external user credentials data source and password encryption [CORDA-827]. * Exporting additional JMX metrics (artemis, hibernate statistics) and loading Jolokia agent at JVM startup when using diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst index 0c8da04e1f..7ec1d0e53c 100644 --- a/docs/source/network-map.rst +++ b/docs/source/network-map.rst @@ -71,6 +71,7 @@ The current set of network parameters: :maxMessageSize: Maximum allowed size in bytes of an individual message sent over the wire. Note that attachments are a special case and may be fragmented for streaming transfer, however, an individual transaction or flow message may not be larger than this value. +:maxTransactionSize: Maximum allowed size in bytes of a transaction. This is the size of the transaction object and its attachments. :modifiedTime: The time when the network parameters were last modified by the compatibility zone operator. :epoch: Version number of the network parameters. Starting from 1, this will always increment whenever any of the parameters change. diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 94309e4c5f..142150d8d0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -13,13 +13,19 @@ import net.corda.core.flows.* import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.* +import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.GlobalProperties +import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.openFuture +import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.* import net.corda.core.node.* import net.corda.core.node.services.* -import net.corda.core.serialization.* +import net.corda.core.serialization.SerializationWhitelist +import net.corda.core.serialization.SerializeAsToken +import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.serialization.serialize import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug @@ -118,7 +124,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // low-performance prototyping period. protected abstract val serverThread: AffinityExecutor - protected lateinit var networkParameters: NetworkParameters private val cordappServices = MutableClassToInstanceMap.create() private val flowFactories = ConcurrentHashMap>, InitiatedFlowFactory<*>>() @@ -166,7 +171,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, initCertificate() val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) - return initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database -> + return initialiseDatabasePersistence(schemaService, makeIdentityService(identity.certificate)) { database -> // TODO The fact that we need to specify an empty list of notaries just to generate our node info looks like // a code smell. val persistentNetworkMapCache = PersistentNetworkMapCache(database, notaries = emptyList()) @@ -189,13 +194,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null) val identityService = makeIdentityService(identity.certificate) networkMapClient = configuration.compatibilityZoneURL?.let { NetworkMapClient(it, identityService.trustRoot) } - networkParameters = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory).networkParameters - check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { + GlobalProperties.networkParameters = NetworkParametersReader(identityService.trustRoot, networkMapClient, configuration.baseDirectory).networkParameters + check(GlobalProperties.networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node's platform version is lower than network's required minimumPlatformVersion" } // Do all of this in a database transaction so anything that might need a connection has one. val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService, identityService) { database -> - val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, networkParameters.notaries).start(), identityService) + val networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, GlobalProperties.networkParameters.notaries).start(), identityService) val (keyPairs, info) = initNodeInfo(networkMapCache, identity, identityKeyPair) identityService.loadIdentities(info.legalIdentitiesAndCerts) val transactionStorage = makeTransactionStorage(database, configuration.transactionCacheSizeBytes) @@ -234,7 +239,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, networkMapUpdater = NetworkMapUpdater(services.networkMapCache, NodeInfoWatcher(configuration.baseDirectory, getRxIoScheduler(), Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)), networkMapClient, - networkParameters.serialize().hash, + GlobalProperties.networkParameters.serialize().hash, configuration.baseDirectory) runOnStop += networkMapUpdater::close @@ -520,7 +525,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, * Builds node internal, advertised, and plugin services. * Returns a list of tokenizable services to be added to the serialisation context. */ - private fun makeServices(keyPairs: Set, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, database: CordaPersistence, info: NodeInfo, identityService: IdentityServiceInternal, networkMapCache: NetworkMapCacheInternal): MutableList { + private fun makeServices(keyPairs: Set, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, database: CordaPersistence, info: NodeInfo, identityService: IdentityServiceInternal, networkMapCache: NetworkMapCacheInternal): MutableList { checkpointStorage = DBCheckpointStorage() val metrics = MetricRegistry() attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound) diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 8d5dbad49b..1911b89df9 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -2,6 +2,7 @@ package net.corda.node.internal import com.codahale.metrics.JmxReporter import net.corda.core.concurrent.CordaFuture +import net.corda.core.internal.GlobalProperties.networkParameters import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.div diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt new file mode 100644 index 0000000000..7b6a077b8d --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/transactions/MaxTransactionSizeTests.kt @@ -0,0 +1,135 @@ +package net.corda.node.services.transactions + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.internal.InputStreamAndHash +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.getOrThrow +import net.corda.node.services.api.StartedNodeServices +import net.corda.testing.contracts.DummyContract +import net.corda.testing.contracts.DummyState +import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.BOB_NAME +import net.corda.testing.core.dummyCommand +import net.corda.testing.core.singleIdentity +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters +import net.corda.testing.node.startFlow +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class MaxTransactionSizeTests { + private lateinit var mockNet: MockNetwork + private lateinit var notaryServices: StartedNodeServices + private lateinit var aliceServices: StartedNodeServices + private lateinit var notary: Party + private lateinit var alice: Party + private lateinit var bob: Party + + @Before + fun setup() { + mockNet = MockNetwork(listOf("net.corda.testing.contracts", "net.corda.node.services.transactions"), maxTransactionSize = 3_000_000) + val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) + val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) + notaryServices = mockNet.defaultNotaryNode.services + aliceServices = aliceNode.services + notary = mockNet.defaultNotaryIdentity + alice = aliceNode.info.singleIdentity() + bob = bobNode.info.singleIdentity() + } + + @After + fun cleanUp() { + mockNet.stopNodes() + } + + @Test + fun `check transaction will fail when exceed max transaction size limit`() { + // These 4 attachments yield a transaction that's got ~ 4mb, which will exceed the 3mb max transaction size limit + val bigFile1 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 0) + val bigFile2 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 1) + val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 2) + val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 3) + val flow = aliceServices.database.transaction { + val hash1 = aliceServices.attachments.importAttachment(bigFile1.inputStream) + val hash2 = aliceServices.attachments.importAttachment(bigFile2.inputStream) + val hash3 = aliceServices.attachments.importAttachment(bigFile3.inputStream) + val hash4 = aliceServices.attachments.importAttachment(bigFile4.inputStream) + assertEquals(hash1, bigFile1.sha256) + SendLargeTransactionFlow(notary, bob, hash1, hash2, hash3, hash4) + } + val exception = assertFailsWith { + val future = aliceServices.startFlow(flow) + mockNet.runNetwork() + future.getOrThrow() + } + assertThat(exception).hasMessageContaining("Transaction exceeded network's maximum transaction size limit") + } + + @Test + fun `check transaction will be rejected by counterparty when exceed max transaction size limit`() { + // These 4 attachments yield a transaction that's got ~ 4mb, which will exceed the 3mb max transaction size limit + val bigFile1 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 0) + val bigFile2 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 1) + val bigFile3 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 2) + val bigFile4 = InputStreamAndHash.createInMemoryTestZip(1024 * 1024, 3) + val flow = aliceServices.database.transaction { + val hash1 = aliceServices.attachments.importAttachment(bigFile1.inputStream) + val hash2 = aliceServices.attachments.importAttachment(bigFile2.inputStream) + val hash3 = aliceServices.attachments.importAttachment(bigFile3.inputStream) + val hash4 = aliceServices.attachments.importAttachment(bigFile4.inputStream) + assertEquals(hash1, bigFile1.sha256) + SendLargeTransactionFlow(notary, bob, hash1, hash2, hash3, hash4, verify = false) + } + val ex = assertFailsWith { + val future = aliceServices.startFlow(flow) + mockNet.runNetwork() + future.getOrThrow() + } + assertThat(ex).hasMessageContaining("Counterparty flow on O=Bob Plc, L=Rome, C=IT had an internal error and has terminated") + } + + @StartableByRPC + @InitiatingFlow + class SendLargeTransactionFlow(private val notary: Party, + private val otherSide: Party, + private val hash1: SecureHash, + private val hash2: SecureHash, + private val hash3: SecureHash, + private val hash4: SecureHash, + private val verify: Boolean = true) : FlowLogic() { + @Suspendable + override fun call() { + val tx = TransactionBuilder(notary = notary) + .addOutputState(DummyState(), DummyContract.PROGRAM_ID) + .addCommand(dummyCommand(ourIdentity.owningKey)) + .addAttachment(hash1) + .addAttachment(hash2) + .addAttachment(hash3) + .addAttachment(hash4) + val stx = serviceHub.signInitialTransaction(tx, ourIdentity.owningKey) + if (verify) stx.verify(serviceHub, checkSufficientSignatures = false) + // Send to the other side and wait for it to trigger resolution from us. + val otherSideSession = initiateFlow(otherSide) + subFlow(SendTransactionFlow(otherSideSession, stx)) + otherSideSession.receive() + } + } + + @InitiatedBy(SendLargeTransactionFlow::class) + @Suppress("UNUSED") + class ReceiveLargeTransactionFlow(private val otherSide: FlowSession) : FlowLogic() { + @Suspendable + override fun call() { + subFlow(ReceiveTransactionFlow(otherSide)) + // Unblock the other side by sending some dummy object (Unit is fine here as it's a singleton). + otherSide.send(Unit) + } + } +} \ No newline at end of file diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt index cba6ba4da3..4d05a61d85 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt @@ -29,6 +29,7 @@ fun springDriver( startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, notarySpecs: List = defaultParameters.notarySpecs, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, + maxTransactionSize: Int = defaultParameters.maxTransactionSize, dsl: SpringBootDriverDSL.() -> A ): A { return genericDriver( @@ -44,6 +45,7 @@ fun springDriver( extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, driverDslWrapper = { driverDSL: DriverDSLImpl -> SpringBootDriverDSL(driverDSL) }, + maxTransactionSize = maxTransactionSize, coerce = { it }, dsl = dsl ) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 16e6b0d88d..826a49f16c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -147,7 +147,7 @@ data class NodeParameters( data class JmxPolicy(val startJmxHttpServer: Boolean = false, val jmxHttpServerPortAllocation: PortAllocation? = - if (startJmxHttpServer) PortAllocation.Incremental(7005) else null) + if (startJmxHttpServer) PortAllocation.Incremental(7005) else null) /** * [driver] allows one to start up nodes like this: @@ -174,7 +174,7 @@ data class JmxPolicy(val startJmxHttpServer: Boolean = false, * @param useTestClock If true the test clock will be used in Node. * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or * not. Note that this may be overridden in [DriverDSL.startNode]. - * @param waitForAllNodesToFinish If true, the nodes will not shut down automatically after executing the code in the driver DSL block. + * @param waitForAllNodesToFinish If true, the nodes will not shut down automatically after executing the code in the driver DSL block. * It will wait for them to be shut down externally instead. * @param notarySpecs The notaries advertised for this network. These nodes will be started automatically and will be * available from [DriverDSL.notaryHandles]. Defaults to a simple validating notary. @@ -198,6 +198,7 @@ fun driver( notarySpecs: List = defaultParameters.notarySpecs, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, jmxPolicy: JmxPolicy = defaultParameters.jmxPolicy, + maxTransactionSize: Int = defaultParameters.maxTransactionSize, dsl: DriverDSL.() -> A ): A { return genericDriver( @@ -213,7 +214,8 @@ fun driver( notarySpecs = notarySpecs, extraCordappPackagesToScan = extraCordappPackagesToScan, jmxPolicy = jmxPolicy, - compatibilityZone = null + compatibilityZone = null, + maxTransactionSize = maxTransactionSize ), coerce = { it }, dsl = dsl, @@ -249,7 +251,8 @@ data class DriverParameters( val waitForAllNodesToFinish: Boolean = false, val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY_NAME)), val extraCordappPackagesToScan: List = emptyList(), - val jmxPolicy: JmxPolicy = JmxPolicy() + val jmxPolicy: JmxPolicy = JmxPolicy(), + val maxTransactionSize: Int = Int.MAX_VALUE ) { fun setIsDebug(isDebug: Boolean) = copy(isDebug = isDebug) fun setDriverDirectory(driverDirectory: Path) = copy(driverDirectory = driverDirectory) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 1b68a4444a..5db5bb6fcd 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -19,6 +19,7 @@ import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo +import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist @@ -42,16 +43,15 @@ import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.network.NetworkParametersCopier -import net.corda.core.node.NotaryInfo import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig -import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.core.setGlobalSerialization import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.testThreadFactory import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.core.setGlobalSerialization import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.sshd.common.util.security.SecurityUtils import rx.internal.schedulers.CachedThreadScheduler @@ -133,7 +133,8 @@ open class MockNetwork(private val cordappPackages: List, servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, private val defaultFactory: (MockNodeArgs) -> MockNode = defaultParameters.defaultFactory, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, - private val notarySpecs: List = defaultParameters.notarySpecs) { + private val notarySpecs: List = defaultParameters.notarySpecs, + maxTransactionSize: Int = Int.MAX_VALUE) { /** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */ @JvmOverloads constructor(cordappPackages: List, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters) @@ -228,7 +229,7 @@ open class MockNetwork(private val cordappPackages: List, filesystem.getPath("/nodes").createDirectory() val notaryInfos = generateNotaryIdentities() // The network parameters must be serialised before starting any of the nodes - networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos)) + networkParameters = NetworkParametersCopier(testNetworkParameters(notaryInfos, maxTransactionSize = maxTransactionSize)) @Suppress("LeakingThis") notaryNodes = createNotaries() } catch (t: Throwable) { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 5d1eae0305..a33b51879e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -6,6 +6,7 @@ import net.corda.core.crypto.* import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate +import net.corda.core.internal.GlobalProperties import net.corda.core.messaging.DataFeed import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowProgressHandle @@ -31,6 +32,7 @@ import net.corda.node.services.vault.NodeVaultService import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.HibernateConfiguration +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DEV_ROOT_CA import net.corda.testing.core.TestIdentity import net.corda.testing.services.MockAttachmentStorage diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt index dcf47f96b3..008a0c2e81 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeTestUtils.kt @@ -9,11 +9,13 @@ import net.corda.core.context.InvocationContext import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.internal.GlobalProperties import net.corda.core.node.ServiceHub import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.services.api.StartedNodeServices +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import net.corda.testing.core.chooseIdentity @@ -34,6 +36,7 @@ fun ServiceHub.ledger( false } return LedgerDSL(TestLedgerDSLInterpreter(this), notary).apply { + GlobalProperties.networkParameters = testNetworkParameters(emptyList()) if (serializationExists) { script() } else { 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 46d5731e9a..bdd40ada5a 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 @@ -14,6 +14,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* import net.corda.core.internal.concurrent.* import net.corda.core.messaging.CordaRPCOps +import net.corda.core.node.NotaryInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.serialization.deserialize import net.corda.core.toFuture @@ -37,7 +38,6 @@ import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NodeInfoFilesCopier -import net.corda.core.node.NotaryInfo import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME @@ -87,7 +87,8 @@ class DriverDSLImpl( extraCordappPackagesToScan: List, val jmxPolicy: JmxPolicy, val notarySpecs: List, - val compatibilityZone: CompatibilityZoneParams? + val compatibilityZone: CompatibilityZoneParams?, + val maxTransactionSize: Int ) : InternalDriverDSL { private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! @@ -699,7 +700,7 @@ class DriverDSLImpl( * The local version of the network map, which is a bunch of classes that copy the relevant files to the node directories. */ private inner class LocalNetworkMap(notaryInfos: List) { - val networkParametersCopier = NetworkParametersCopier(testNetworkParameters(notaryInfos)) + val networkParametersCopier = NetworkParametersCopier(testNetworkParameters(notaryInfos, maxTransactionSize = maxTransactionSize)) // TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/ // This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system. // Investigate whether we can avoid that. @@ -955,6 +956,7 @@ fun genericDriver( notarySpecs: List, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, jmxPolicy: JmxPolicy = JmxPolicy(), + maxTransactionSize: Int, driverDslWrapper: (DriverDSLImpl) -> D, coerce: (D) -> DI, dsl: DI.() -> A ): A { @@ -972,7 +974,8 @@ fun genericDriver( extraCordappPackagesToScan = extraCordappPackagesToScan, jmxPolicy = jmxPolicy, notarySpecs = notarySpecs, - compatibilityZone = null + compatibilityZone = null, + maxTransactionSize = maxTransactionSize ) ) val shutdownHook = addShutdownHook(driverDsl::shutdown) @@ -1014,6 +1017,7 @@ fun internalDriver( notarySpecs: List = DriverParameters().notarySpecs, extraCordappPackagesToScan: List = DriverParameters().extraCordappPackagesToScan, jmxPolicy: JmxPolicy = DriverParameters().jmxPolicy, + maxTransactionSize: Int = DriverParameters().maxTransactionSize, compatibilityZone: CompatibilityZoneParams? = null, dsl: DriverDSLImpl.() -> A ): A { @@ -1030,7 +1034,8 @@ fun internalDriver( notarySpecs = notarySpecs, extraCordappPackagesToScan = extraCordappPackagesToScan, jmxPolicy = jmxPolicy, - compatibilityZone = compatibilityZone + compatibilityZone = compatibilityZone, + maxTransactionSize = maxTransactionSize ), coerce = { it }, dsl = dsl, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index e41c1916ce..585f170fd3 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -106,8 +106,9 @@ fun rpcDriver( notarySpecs: List = emptyList(), externalTrace: Trace? = null, jmxPolicy: JmxPolicy = JmxPolicy(), + maxTransactionSize: Int = Int.MAX_VALUE, dsl: RPCDriverDSL.() -> A -) : A { +): A { return genericDriver( driverDsl = RPCDriverDSL( DriverDSLImpl( @@ -122,7 +123,8 @@ fun rpcDriver( extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, jmxPolicy = jmxPolicy, - compatibilityZone = null + compatibilityZone = null, + maxTransactionSize = maxTransactionSize ), externalTrace ), coerce = { it }, @@ -434,7 +436,7 @@ data class RPCDriverDSL( minLargeMessageSize = MAX_MESSAGE_SIZE isUseGlobalPools = false } - val rpcSecurityManager = RPCSecurityManagerImpl.fromUserList(users = listOf(InternalUser(rpcUser.username, rpcUser.password, rpcUser.permissions)) , id = AuthServiceId("TEST_SECURITY_MANAGER")) + val rpcSecurityManager = RPCSecurityManagerImpl.fromUserList(users = listOf(InternalUser(rpcUser.username, rpcUser.password, rpcUser.permissions)), id = AuthServiceId("TEST_SECURITY_MANAGER")) val rpcServer = RPCServer( ops, rpcUser.username, diff --git a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt index b0215cf83c..ea089257c2 100644 --- a/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt +++ b/testing/test-common/src/main/kotlin/net/corda/testing/common/internal/ParametersUtilities.kt @@ -9,7 +9,8 @@ fun testNetworkParameters( minimumPlatformVersion: Int = 1, modifiedTime: Instant = Instant.now(), maxMessageSize: Int = 10485760, - maxTransactionSize: Int = 40000, + // TODO: Make this configurable and consistence across driver, bootstrapper, demobench and NetworkMapServer + maxTransactionSize: Int = Int.MAX_VALUE, epoch: Int = 1 ): NetworkParameters { return NetworkParameters( diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index 37faeae80a..1c53fdd82f 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -143,7 +143,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { notaries = listOf(NotaryInfo(identity, config.nodeConfig.notary!!.validating)), modifiedTime = Instant.now(), maxMessageSize = 10485760, - maxTransactionSize = 40000, + maxTransactionSize = Int.MAX_VALUE, epoch = 1 )) notaryIdentity = identity diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index 791df21211..ad436e30d6 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -5,6 +5,7 @@ import com.typesafe.config.ConfigFactory import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.GlobalProperties import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.fork @@ -22,6 +23,7 @@ import net.corda.nodeapi.VerifierApi import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.internal.config.NodeSSLConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.PortAllocation @@ -61,6 +63,7 @@ fun verifierDriver( extraCordappPackagesToScan: List = emptyList(), notarySpecs: List = emptyList(), jmxPolicy: JmxPolicy = JmxPolicy(), + maxTransactionSize: Int = Int.MAX_VALUE, dsl: VerifierDriverDSL.() -> A ) = genericDriver( driverDsl = VerifierDriverDSL( @@ -76,7 +79,8 @@ fun verifierDriver( extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, jmxPolicy = jmxPolicy, - compatibilityZone = null + compatibilityZone = null, + maxTransactionSize = maxTransactionSize ) ), coerce = { it }, @@ -162,6 +166,7 @@ data class VerifierDriverDSL(private val driverDSL: DriverDSLImpl) : InternalDri /** Starts a lightweight verification requestor that implements the Node's Verifier API */ fun startVerificationRequestor(name: CordaX500Name): CordaFuture { val hostAndPort = driverDSL.portAllocation.nextHostAndPort() + GlobalProperties.networkParameters = testNetworkParameters(emptyList(), maxTransactionSize = driverDSL.maxTransactionSize) return driverDSL.executorService.fork { startVerificationRequestorInternal(name, hostAndPort) } @@ -184,6 +189,7 @@ data class VerifierDriverDSL(private val driverDSL: DriverDSLImpl) : InternalDri val securityManager = object : ActiveMQSecurityManager { // We don't need auth, SSL is good enough override fun validateUser(user: String?, password: String?) = true + override fun validateUserAndRole(user: String?, password: String?, roles: MutableSet?, checkType: CheckType?) = true } From 0a88b76e461fc540038553b77a6f2afd95c04566 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 8 Feb 2018 15:13:25 +0000 Subject: [PATCH 033/114] r3corda wire compatibility --- .idea/compiler.xml | 3 +- .../mocknetwork/TutorialMockNetwork.kt | 22 ++- .../serialization/ListsSerializationTest.kt | 8 +- .../serialization/MapsSerializationTest.kt | 4 +- .../serialization/SetsSerializationTest.kt | 10 +- .../services/messaging/P2PMessagingTest.kt | 84 +++++---- .../node/services/messaging/Messaging.kt | 135 ++------------- .../services/messaging/P2PMessagingClient.kt | 81 ++++----- .../messaging/ServiceRequestMessage.kt | 27 --- .../services/statemachine/FlowIORequest.kt | 34 ++-- .../statemachine/FlowSessionInternal.kt | 16 +- .../statemachine/FlowStateMachineImpl.kt | 161 ++++++++++------- .../services/statemachine/SessionMessage.kt | 163 ++++++++++++------ .../statemachine/StateMachineManagerImpl.kt | 69 ++++---- .../node/messaging/InMemoryMessagingTests.kt | 9 +- .../messaging/ArtemisMessagingTest.kt | 2 +- .../statemachine/FlowFrameworkTests.kt | 70 +++++--- .../net/corda/netmap/NetworkMapVisualiser.kt | 18 +- .../testing/node/InMemoryMessagingNetwork.kt | 58 +++---- 19 files changed, 489 insertions(+), 485 deletions(-) delete mode 100644 node/src/main/kotlin/net/corda/node/services/messaging/ServiceRequestMessage.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml index d8d9f1498a..dba1dad5e2 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -72,6 +72,7 @@ + @@ -159,4 +160,4 @@ - + \ No newline at end of file diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt index 6841c5d7fc..1ab3513656 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt @@ -10,12 +10,18 @@ import net.corda.core.identity.Party import net.corda.core.messaging.MessageRecipients import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap import net.corda.node.internal.StartedNode import net.corda.node.services.messaging.Message -import net.corda.node.services.statemachine.SessionData -import net.corda.testing.node.* +import net.corda.node.services.statemachine.DataSessionMessage +import net.corda.node.services.statemachine.ExistingSessionMessage +import net.corda.testing.node.InMemoryMessagingNetwork +import net.corda.testing.node.MessagingServiceSpy +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.setMessagingServiceSpy +import net.corda.testing.node.startFlow import org.junit.After import org.junit.Before import org.junit.Rule @@ -79,12 +85,12 @@ class TutorialMockNetwork { // modify message if it's 1 nodeB.setMessagingServiceSpy(object : MessagingServiceSpy(nodeB.network) { - override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { - val messageData = message.data.deserialize() - - if (messageData is SessionData && messageData.payload.deserialize() == 1) { - val alteredMessageData = SessionData(messageData.recipientSessionId, 99.serialize()).serialize().bytes - messagingService.send(InMemoryMessagingNetwork.InMemoryMessage(message.topicSession, alteredMessageData, message.uniqueMessageId), target, retryId) + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any) { + val messageData = message.data.deserialize() as? ExistingSessionMessage + val payload = messageData?.payload + if (payload is DataSessionMessage && payload.payload.deserialize() == 1) { + val alteredMessageData = messageData.copy(payload = payload.copy(99.serialize())).serialize().bytes + messagingService.send(InMemoryMessagingNetwork.InMemoryMessage(message.topic, OpaqueBytes(alteredMessageData), message.uniqueMessageId), target, retryId) } else { messagingService.send(message, target, retryId) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt index 87b1b50a27..dc53b8fc29 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/ListsSerializationTest.kt @@ -3,7 +3,7 @@ package net.corda.nodeapi.internal.serialization import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.util.DefaultClassResolver import net.corda.core.serialization.* -import net.corda.node.services.statemachine.SessionData +import net.corda.node.services.statemachine.DataSessionMessage import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput import net.corda.nodeapi.internal.serialization.amqp.Envelope import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory @@ -47,17 +47,17 @@ class ListsSerializationTest { @Test fun `check list can be serialized as part of SessionData`() { run { - val sessionData = SessionData(123, listOf(1).serialize()) + val sessionData = DataSessionMessage(listOf(1).serialize()) assertEqualAfterRoundTripSerialization(sessionData) assertEquals(listOf(1), sessionData.payload.deserialize()) } run { - val sessionData = SessionData(123, listOf(1, 2).serialize()) + val sessionData = DataSessionMessage(listOf(1, 2).serialize()) assertEqualAfterRoundTripSerialization(sessionData) assertEquals(listOf(1, 2), sessionData.payload.deserialize()) } run { - val sessionData = SessionData(123, emptyList().serialize()) + val sessionData = DataSessionMessage(emptyList().serialize()) assertEqualAfterRoundTripSerialization(sessionData) assertEquals(emptyList(), sessionData.payload.deserialize()) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt index d1b9af493c..a76bb8a52e 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/MapsSerializationTest.kt @@ -6,7 +6,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.node.services.statemachine.SessionData +import net.corda.node.services.statemachine.DataSessionMessage import net.corda.nodeapi.internal.serialization.kryo.kryoMagic import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.internal.amqpSpecific @@ -41,7 +41,7 @@ class MapsSerializationTest { @Test fun `check list can be serialized as part of SessionData`() { - val sessionData = SessionData(123, smallMap.serialize()) + val sessionData = DataSessionMessage(smallMap.serialize()) assertEqualAfterRoundTripSerialization(sessionData) assertEquals(smallMap, sessionData.payload.deserialize()) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt index fb18178b36..48ba75540e 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SetsSerializationTest.kt @@ -4,10 +4,10 @@ import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.util.DefaultClassResolver import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.node.services.statemachine.SessionData +import net.corda.node.services.statemachine.DataSessionMessage import net.corda.nodeapi.internal.serialization.kryo.kryoMagic -import net.corda.testing.internal.kryoSpecific import net.corda.testing.core.SerializationEnvironmentRule +import net.corda.testing.internal.kryoSpecific import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Rule @@ -34,17 +34,17 @@ class SetsSerializationTest { @Test fun `check set can be serialized as part of SessionData`() { run { - val sessionData = SessionData(123, setOf(1).serialize()) + val sessionData = DataSessionMessage(setOf(1).serialize()) assertEqualAfterRoundTripSerialization(sessionData) assertEquals(setOf(1), sessionData.payload.deserialize()) } run { - val sessionData = SessionData(123, setOf(1, 2).serialize()) + val sessionData = DataSessionMessage(setOf(1, 2).serialize()) assertEqualAfterRoundTripSerialization(sessionData) assertEquals(setOf(1, 2), sessionData.payload.deserialize()) } run { - val sessionData = SessionData(123, emptySet().serialize()) + val sessionData = DataSessionMessage(emptySet().serialize()) assertEqualAfterRoundTripSerialization(sessionData) assertEquals(emptySet(), sessionData.payload.deserialize()) } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt index 987277597d..3cea7b31e6 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt @@ -1,9 +1,9 @@ package net.corda.services.messaging import net.corda.core.concurrent.CordaFuture -import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.concurrent.map +import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.randomOrNull import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient @@ -14,7 +14,9 @@ import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.node.internal.Node import net.corda.node.internal.StartedNode -import net.corda.node.services.messaging.* +import net.corda.node.services.messaging.MessagingService +import net.corda.node.services.messaging.ReceivedMessage +import net.corda.node.services.messaging.send import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.chooseIdentity import net.corda.testing.driver.DriverDSL @@ -27,6 +29,7 @@ import org.junit.Test import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger class P2PMessagingTest { @@ -50,19 +53,12 @@ class P2PMessagingTest { alice.network.getAddressOfParty(getPartyInfo(notaryParty)!!) } - val dummyTopic = "dummy.topic" val responseMessage = "response" - val crashingNodes = simulateCrashingNodes(distributedServiceNodes, dummyTopic, responseMessage) + val crashingNodes = simulateCrashingNodes(distributedServiceNodes, responseMessage) // Send a single request with retry - val responseFuture = with(alice.network) { - val request = TestRequest(replyTo = myAddress) - val responseFuture = onNext(dummyTopic, request.sessionID) - val msg = createMessage(TopicSession(dummyTopic), data = request.serialize().bytes) - send(msg, serviceAddress, retryId = request.sessionID) - responseFuture - } + val responseFuture = alice.receiveFrom(serviceAddress, retryId = 0) crashingNodes.firstRequestReceived.await(5, TimeUnit.SECONDS) // The request wasn't successful. assertThat(responseFuture.isDone).isFalse() @@ -83,19 +79,12 @@ class P2PMessagingTest { alice.network.getAddressOfParty(getPartyInfo(notaryParty)!!) } - val dummyTopic = "dummy.topic" val responseMessage = "response" - val crashingNodes = simulateCrashingNodes(distributedServiceNodes, dummyTopic, responseMessage) - - val sessionId = random63BitValue() + val crashingNodes = simulateCrashingNodes(distributedServiceNodes, responseMessage) // Send a single request with retry - with(alice.network) { - val request = TestRequest(sessionId, myAddress) - val msg = createMessage(TopicSession(dummyTopic), data = request.serialize().bytes) - send(msg, serviceAddress, retryId = request.sessionID) - } + alice.receiveFrom(serviceAddress, retryId = 0) // Wait until the first request is received crashingNodes.firstRequestReceived.await() @@ -108,7 +97,13 @@ class P2PMessagingTest { // Restart the node and expect a response val aliceRestarted = startAlice() - val response = aliceRestarted.network.onNext(dummyTopic, sessionId).getOrThrow() + + val responseFuture = openFuture() + aliceRestarted.network.runOnNextMessage("test.response") { + responseFuture.set(it.data.deserialize()) + } + val response = responseFuture.getOrThrow() + assertThat(crashingNodes.requestsReceived.get()).isGreaterThan(numberOfRequestsReceived) assertThat(response).isEqualTo(responseMessage) } @@ -133,11 +128,12 @@ class P2PMessagingTest { ) /** - * Sets up the [distributedServiceNodes] to respond to [dummyTopic] requests. All nodes will receive requests and - * either ignore them or respond, depending on the value of [CrashingNodes.ignoreRequests], initially set to true. - * This may be used to simulate scenarios where nodes receive request messages but crash before sending back a response. + * Sets up the [distributedServiceNodes] to respond to "test.request" requests. All nodes will receive requests and + * either ignore them or respond to "test.response", depending on the value of [CrashingNodes.ignoreRequests], + * initially set to true. This may be used to simulate scenarios where nodes receive request messages but crash + * before sending back a response. */ - private fun simulateCrashingNodes(distributedServiceNodes: List>, dummyTopic: String, responseMessage: String): CrashingNodes { + private fun simulateCrashingNodes(distributedServiceNodes: List>, responseMessage: String): CrashingNodes { val crashingNodes = CrashingNodes( requestsReceived = AtomicInteger(0), firstRequestReceived = CountDownLatch(1), @@ -146,7 +142,7 @@ class P2PMessagingTest { distributedServiceNodes.forEach { val nodeName = it.info.chooseIdentity().name - it.network.addMessageHandler(dummyTopic) { netMessage, _ -> + it.network.addMessageHandler("test.request") { netMessage, _ -> crashingNodes.requestsReceived.incrementAndGet() crashingNodes.firstRequestReceived.countDown() // The node which receives the first request will ignore all requests @@ -158,7 +154,7 @@ class P2PMessagingTest { } else { println("sending response") val request = netMessage.data.deserialize() - val response = it.network.createMessage(dummyTopic, request.sessionID, responseMessage.serialize().bytes) + val response = it.network.createMessage("test.response", responseMessage.serialize().bytes) it.network.send(response, request.replyTo) } } @@ -188,19 +184,39 @@ class P2PMessagingTest { } private fun StartedNode<*>.respondWith(message: Any) { - network.addMessageHandler(javaClass.name) { netMessage, _ -> + network.addMessageHandler("test.request") { netMessage, _ -> val request = netMessage.data.deserialize() - val response = network.createMessage(javaClass.name, request.sessionID, message.serialize().bytes) + val response = network.createMessage("test.response", message.serialize().bytes) network.send(response, request.replyTo) } } - private fun StartedNode<*>.receiveFrom(target: MessageRecipients): CordaFuture { - val request = TestRequest(replyTo = network.myAddress) - return network.sendRequest(javaClass.name, request, target) + private fun StartedNode<*>.receiveFrom(target: MessageRecipients, retryId: Long? = null): CordaFuture { + val response = openFuture() + network.runOnNextMessage("test.response") { netMessage -> + response.set(netMessage.data.deserialize()) + } + network.send("test.request", TestRequest(replyTo = network.myAddress), target, retryId = retryId) + return response + } + + /** + * Registers a handler for the given topic and session that runs the given callback with the message and then removes + * itself. This is useful for one-shot handlers that aren't supposed to stick around permanently. Note that this callback + * doesn't take the registration object, unlike the callback to [MessagingService.addMessageHandler]. + * + * @param topic identifier for the topic and session to listen for messages arriving on. + */ + inline fun MessagingService.runOnNextMessage(topic: String, crossinline callback: (ReceivedMessage) -> Unit) { + val consumed = AtomicBoolean() + addMessageHandler(topic) { msg, reg -> + removeMessageHandler(reg) + check(!consumed.getAndSet(true)) { "Called more than once" } + check(msg.topic == topic) { "Topic/session mismatch: ${msg.topic} vs $topic" } + callback(msg) + } } @CordaSerializable - private data class TestRequest(override val sessionID: Long = random63BitValue(), - override val replyTo: SingleMessageRecipient) : ServiceRequestMessage + private data class TestRequest(val replyTo: SingleMessageRecipient) } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index 468de4d8f5..6260ea59db 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -1,18 +1,15 @@ package net.corda.node.services.messaging -import net.corda.core.concurrent.CordaFuture +import co.paralleluniverse.fibers.Suspendable import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.core.utilities.ByteSequence import java.time.Instant import java.util.* -import java.util.concurrent.atomic.AtomicBoolean import javax.annotation.concurrent.ThreadSafe /** @@ -27,29 +24,6 @@ import javax.annotation.concurrent.ThreadSafe */ @ThreadSafe interface MessagingService { - companion object { - /** - * Session ID to use for services listening for the first message in a session (before a - * specific session ID has been established). - */ - val DEFAULT_SESSION_ID = 0L - } - - /** - * The provided function will be invoked for each received message whose topic matches the given string. The callback - * will run on threads provided by the messaging service, and the callback is expected to be thread safe as a result. - * - * The returned object is an opaque handle that may be used to un-register handlers later with [removeMessageHandler]. - * The handle is passed to the callback as well, to avoid race conditions whereby the callback wants to unregister - * itself and yet addMessageHandler hasn't returned the handle yet. - * - * @param topic identifier for the general subject of the message, for example "platform.network_map.fetch". - * The topic can be the empty string to match all messages (session ID must be [DEFAULT_SESSION_ID]). - * @param sessionID identifier for the session the message is part of. For services listening before - * a session is established, use [DEFAULT_SESSION_ID]. - */ - fun addMessageHandler(topic: String = "", sessionID: Long = DEFAULT_SESSION_ID, callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit): MessageHandlerRegistration - /** * The provided function will be invoked for each received message whose topic and session matches. The callback * will run on the main server thread provided when the messaging service is constructed, and a database @@ -59,9 +33,9 @@ interface MessagingService { * The handle is passed to the callback as well, to avoid race conditions whereby the callback wants to unregister * itself and yet addMessageHandler hasn't returned the handle yet. * - * @param topicSession identifier for the topic and session to listen for messages arriving on. + * @param topic identifier for the topic to listen for messages arriving on. */ - fun addMessageHandler(topicSession: TopicSession, callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit): MessageHandlerRegistration + fun addMessageHandler(topic: String, callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit): MessageHandlerRegistration /** * Removes a handler given the object returned from [addMessageHandler]. The callback will no longer be invoked once @@ -86,15 +60,13 @@ interface MessagingService { * @param sequenceKey an object that may be used to enable a parallel [MessagingService] implementation. Two * subsequent send()s with the same [sequenceKey] (up to equality) are guaranteed to be delivered in the same * sequence the send()s were called. By default this is chosen conservatively to be [target]. - * @param acknowledgementHandler if non-null this handler will be called once the sent message has been committed by - * the broker. Note that if specified [send] itself may return earlier than the commit. */ + @Suspendable fun send( message: Message, target: MessageRecipients, retryId: Long? = null, - sequenceKey: Any = target, - acknowledgementHandler: (() -> Unit)? = null + sequenceKey: Any = target ) /** A message with a target and sequenceKey specified. */ @@ -110,12 +82,9 @@ interface MessagingService { * implementation. * * @param addressedMessages The list of messages together with the recipients, retry ids and sequence keys. - * @param retryId if provided the message will be scheduled for redelivery until [cancelRedelivery] is called for this id. - * Note that this feature should only be used when the target is an idempotent distributed service, e.g. a notary. - * @param acknowledgementHandler if non-null this handler will be called once all sent messages have been committed - * by the broker. Note that if specified [send] itself may return earlier than the commit. */ - fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)? = null) + @Suspendable + fun send(addressedMessages: List) /** Cancels the scheduled message redelivery for the specified [retryId] */ fun cancelRedelivery(retryId: Long) @@ -123,9 +92,9 @@ interface MessagingService { /** * Returns an initialised [Message] with the current time, etc, already filled in. * - * @param topicSession identifier for the topic and session the message is sent to. + * @param topic identifier for the topic the message is sent to. */ - fun createMessage(topicSession: TopicSession, data: ByteArray, uuid: UUID = UUID.randomUUID()): Message + fun createMessage(topic: String, data: ByteArray, deduplicationId: String = UUID.randomUUID().toString()): Message /** Given information about either a specific node or a service returns its corresponding address */ fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients @@ -134,86 +103,12 @@ interface MessagingService { val myAddress: SingleMessageRecipient } -/** - * Returns an initialised [Message] with the current time, etc, already filled in. - * - * @param topic identifier for the general subject of the message, for example "platform.network_map.fetch". - * Must not be blank. - * @param sessionID identifier for the session the message is part of. For messages sent to services before the - * construction of a session, use [DEFAULT_SESSION_ID]. - */ -fun MessagingService.createMessage(topic: String, sessionID: Long = MessagingService.DEFAULT_SESSION_ID, data: ByteArray): Message - = createMessage(TopicSession(topic, sessionID), data) -/** - * Registers a handler for the given topic and session ID that runs the given callback with the message and then removes - * itself. This is useful for one-shot handlers that aren't supposed to stick around permanently. Note that this callback - * doesn't take the registration object, unlike the callback to [MessagingService.addMessageHandler], as the handler is - * automatically deregistered before the callback runs. - * - * @param topic identifier for the general subject of the message, for example "platform.network_map.fetch". - * The topic can be the empty string to match all messages (session ID must be [DEFAULT_SESSION_ID]). - * @param sessionID identifier for the session the message is part of. For services listening before - * a session is established, use [DEFAULT_SESSION_ID]. - */ -fun MessagingService.runOnNextMessage(topic: String, sessionID: Long, callback: (ReceivedMessage) -> Unit) - = runOnNextMessage(TopicSession(topic, sessionID), callback) - -/** - * Registers a handler for the given topic and session that runs the given callback with the message and then removes - * itself. This is useful for one-shot handlers that aren't supposed to stick around permanently. Note that this callback - * doesn't take the registration object, unlike the callback to [MessagingService.addMessageHandler]. - * - * @param topicSession identifier for the topic and session to listen for messages arriving on. - */ -inline fun MessagingService.runOnNextMessage(topicSession: TopicSession, crossinline callback: (ReceivedMessage) -> Unit) { - val consumed = AtomicBoolean() - addMessageHandler(topicSession) { msg, reg -> - removeMessageHandler(reg) - check(!consumed.getAndSet(true)) { "Called more than once" } - check(msg.topicSession == topicSession) { "Topic/session mismatch: ${msg.topicSession} vs $topicSession" } - callback(msg) - } -} - -/** - * Returns a [CordaFuture] of the next message payload ([Message.data]) which is received on the given topic and sessionId. - * The payload is deserialized to an object of type [M]. Any exceptions thrown will be captured by the future. - */ -fun MessagingService.onNext(topic: String, sessionId: Long): CordaFuture { - val messageFuture = openFuture() - runOnNextMessage(topic, sessionId) { message -> - messageFuture.capture { - uncheckedCast(message.data.deserialize()) - } - } - return messageFuture -} - -fun MessagingService.send(topic: String, sessionID: Long, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID()) { - send(TopicSession(topic, sessionID), payload, to, uuid) -} - -fun MessagingService.send(topicSession: TopicSession, payload: Any, to: MessageRecipients, uuid: UUID = UUID.randomUUID(), retryId: Long? = null) { - send(createMessage(topicSession, payload.serialize().bytes, uuid), to, retryId) -} +fun MessagingService.send(topicSession: String, payload: Any, to: MessageRecipients, deduplicationId: String = UUID.randomUUID().toString(), retryId: Long? = null) + = send(createMessage(topicSession, payload.serialize().bytes, deduplicationId), to, retryId) interface MessageHandlerRegistration -/** - * An identifier for the endpoint [MessagingService] message handlers listen at. - * - * @param topic identifier for the general subject of the message, for example "platform.network_map.fetch". - * The topic can be the empty string to match all messages (session ID must be [DEFAULT_SESSION_ID]). - * @param sessionID identifier for the session the message is part of. For services listening before - * a session is established, use [DEFAULT_SESSION_ID]. - */ -@CordaSerializable -data class TopicSession(val topic: String, val sessionID: Long = MessagingService.DEFAULT_SESSION_ID) { - fun isBlank() = topic.isBlank() && sessionID == MessagingService.DEFAULT_SESSION_ID - override fun toString(): String = "$topic.$sessionID" -} - /** * A message is defined, at this level, to be a (topic, timestamp, byte arrays) triple, where the topic is a string in * Java-style reverse dns form, with "platform." being a prefix reserved by the platform for its own use. Vendor @@ -226,10 +121,10 @@ data class TopicSession(val topic: String, val sessionID: Long = MessagingServic */ @CordaSerializable interface Message { - val topicSession: TopicSession - val data: ByteArray + val topic: String + val data: ByteSequence val debugTimestamp: Instant - val uniqueMessageId: UUID + val uniqueMessageId: String } // TODO Have ReceivedMessage point to the TLS certificate of the peer, and [peer] would simply be the subject DN of that. diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index dd7358bad4..de9bf27f46 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -13,10 +13,7 @@ import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.contextLogger -import net.corda.core.utilities.sequence -import net.corda.core.utilities.trace +import net.corda.core.utilities.* import net.corda.node.VersionInfo import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.config.NodeConfiguration @@ -98,20 +95,19 @@ class P2PMessagingClient(config: NodeConfiguration, // that will handle messages, like a URL) with the terminology used by underlying MQ libraries, to avoid // confusion. private val topicProperty = SimpleString("platform-topic") - private val sessionIdProperty = SimpleString("session-id") private val cordaVendorProperty = SimpleString("corda-vendor") private val releaseVersionProperty = SimpleString("release-version") private val platformVersionProperty = SimpleString("platform-version") private val amqDelayMillis = System.getProperty("amq.delivery.delay.ms", "0").toInt() private val messageMaxRetryCount: Int = 3 - fun createProcessedMessage(): AppendOnlyPersistentMap { + fun createProcessedMessage(): AppendOnlyPersistentMap { return AppendOnlyPersistentMap( - toPersistentEntityKey = { it.toString() }, - fromPersistentEntity = { Pair(UUID.fromString(it.uuid), it.insertionTime) }, - toPersistentEntity = { key: UUID, value: Instant -> + toPersistentEntityKey = { it }, + fromPersistentEntity = { Pair(it.uuid, it.insertionTime) }, + toPersistentEntity = { key: String, value: Instant -> ProcessedMessage().apply { - uuid = key.toString() + uuid = key insertionTime = value } }, @@ -139,9 +135,9 @@ class P2PMessagingClient(config: NodeConfiguration, ) } - private class NodeClientMessage(override val topicSession: TopicSession, override val data: ByteArray, override val uniqueMessageId: UUID) : Message { + private class NodeClientMessage(override val topic: String, override val data: ByteSequence, override val uniqueMessageId: String) : Message { override val debugTimestamp: Instant = Instant.now() - override fun toString() = "$topicSession#${String(data)}" + override fun toString() = "$topic#${String(data.bytes)}" } } @@ -160,7 +156,7 @@ class P2PMessagingClient(config: NodeConfiguration, private val scheduledMessageRedeliveries = ConcurrentHashMap>() /** A registration to handle messages of different types */ - data class Handler(val topicSession: TopicSession, + data class Handler(val topic: String, val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration private val cordaVendor = SimpleString(versionInfo.vendor) @@ -181,7 +177,7 @@ class P2PMessagingClient(config: NodeConfiguration, @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}message_ids") class ProcessedMessage( @Id - @Column(name = "message_id", length = 36) + @Column(name = "message_id", length = 64) var uuid: String = "", @Column(name = "insertion_time") @@ -192,7 +188,7 @@ class P2PMessagingClient(config: NodeConfiguration, @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}message_retry") class RetryMessage( @Id - @Column(name = "message_id", length = 36) + @Column(name = "message_id", length = 64) var key: Long = 0, @Lob @@ -383,14 +379,13 @@ class P2PMessagingClient(config: NodeConfiguration, private fun artemisToCordaMessage(message: ClientMessage): ReceivedMessage? { try { val topic = message.required(topicProperty) { getStringProperty(it) } - val sessionID = message.required(sessionIdProperty) { getLongProperty(it) } val user = requireNotNull(message.getStringProperty(HDR_VALIDATED_USER)) { "Message is not authenticated" } val platformVersion = message.required(platformVersionProperty) { getIntProperty(it) } // Use the magic deduplication property built into Artemis as our message identity too - val uuid = message.required(HDR_DUPLICATE_DETECTION_ID) { UUID.fromString(message.getStringProperty(it)) } - log.info("Received message from: ${message.address} user: $user topic: $topic sessionID: $sessionID uuid: $uuid") + val uuid = message.required(HDR_DUPLICATE_DETECTION_ID) { message.getStringProperty(it) } + log.info("Received message from: ${message.address} user: $user topic: $topic uuid: $uuid") - return ArtemisReceivedMessage(TopicSession(topic, sessionID), CordaX500Name.parse(user), platformVersion, uuid, message) + return ArtemisReceivedMessage(topic, CordaX500Name.parse(user), platformVersion, uuid, message) } catch (e: Exception) { log.error("Unable to process message, ignoring it: $message", e) return null @@ -402,21 +397,21 @@ class P2PMessagingClient(config: NodeConfiguration, return extractor(key) } - private class ArtemisReceivedMessage(override val topicSession: TopicSession, + private class ArtemisReceivedMessage(override val topic: String, override val peer: CordaX500Name, override val platformVersion: Int, - override val uniqueMessageId: UUID, + override val uniqueMessageId: String, private val message: ClientMessage) : ReceivedMessage { - override val data: ByteArray by lazy { ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) } } + override val data: ByteSequence by lazy { OpaqueBytes(ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }) } override val debugTimestamp: Instant get() = Instant.ofEpochMilli(message.timestamp) - override fun toString() = "${topicSession.topic}#${data.sequence()}" + override fun toString() = "$topic#$data" } private fun deliver(msg: ReceivedMessage): Boolean { state.checkNotLocked() // Because handlers is a COW list, the loop inside filter will operate on a snapshot. Handlers being added // or removed whilst the filter is executing will not affect anything. - val deliverTo = handlers.filter { it.topicSession.isBlank() || it.topicSession == msg.topicSession } + val deliverTo = handlers.filter { it.topic.isBlank() || it.topic== msg.topic } try { // This will perform a BLOCKING call onto the executor. Thus if the handlers are slow, we will // be slow, and Artemis can handle that case intelligently. We don't just invoke the handler @@ -429,11 +424,11 @@ class P2PMessagingClient(config: NodeConfiguration, nodeExecutor.fetchFrom { database.transaction { if (msg.uniqueMessageId in processedMessages) { - log.trace { "Discard duplicate message ${msg.uniqueMessageId} for ${msg.topicSession}" } + log.trace { "Discard duplicate message ${msg.uniqueMessageId} for ${msg.topic}" } } else { if (deliverTo.isEmpty()) { // TODO: Implement dead letter queue, and send it there. - log.warn("Received message ${msg.uniqueMessageId} for ${msg.topicSession} that doesn't have any registered handlers yet") + log.warn("Received message ${msg.uniqueMessageId} for ${msg.topic} that doesn't have any registered handlers yet") } else { callHandlers(msg, deliverTo) } @@ -443,7 +438,7 @@ class P2PMessagingClient(config: NodeConfiguration, } } } catch (e: Exception) { - log.error("Caught exception whilst executing message handler for ${msg.topicSession}", e) + log.error("Caught exception whilst executing message handler for ${msg.topic}", e) } return true } @@ -501,7 +496,7 @@ class P2PMessagingClient(config: NodeConfiguration, } } - override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any) { // We have to perform sending on a different thread pool, since using the same pool for messaging and // fibers leads to Netty buffer memory leaks, caused by both Netty and Quasar fiddling with thread-locals. messagingExecutor.fetchFrom { @@ -512,20 +507,18 @@ class P2PMessagingClient(config: NodeConfiguration, putStringProperty(cordaVendorProperty, cordaVendor) putStringProperty(releaseVersionProperty, releaseVersion) putIntProperty(platformVersionProperty, versionInfo.platformVersion) - putStringProperty(topicProperty, SimpleString(message.topicSession.topic)) - putLongProperty(sessionIdProperty, message.topicSession.sessionID) - writeBodyBufferBytes(message.data) + putStringProperty(topicProperty, SimpleString(message.topic)) + writeBodyBufferBytes(message.data.bytes) // Use the magic deduplication property built into Artemis as our message identity too putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(message.uniqueMessageId.toString())) // For demo purposes - if set then add a delay to messages in order to demonstrate that the flows are doing as intended - if (amqDelayMillis > 0 && message.topicSession.topic == StateMachineManagerImpl.sessionTopic.topic) { + if (amqDelayMillis > 0 && message.topic == StateMachineManagerImpl.sessionTopic) { putLongProperty(HDR_SCHEDULED_DELIVERY_TIME, System.currentTimeMillis() + amqDelayMillis) } } log.trace { - "Send to: $mqAddress topic: ${message.topicSession.topic} " + - "sessionID: ${message.topicSession.sessionID} uuid: ${message.uniqueMessageId}" + "Send to: $mqAddress topic: ${message.topic} uuid: ${message.uniqueMessageId}" } artemis.producer.send(mqAddress, artemisMessage) retryId?.let { @@ -539,14 +532,12 @@ class P2PMessagingClient(config: NodeConfiguration, } } } - acknowledgementHandler?.invoke() } - override fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)?) { + override fun send(addressedMessages: List) { for ((message, target, retryId, sequenceKey) in addressedMessages) { - send(message, target, retryId, sequenceKey, null) + send(message, target, retryId, sequenceKey) } - acknowledgementHandler?.invoke() } private fun sendWithRetry(retryCount: Int, address: String, message: ClientMessage, retryId: Long) { @@ -622,15 +613,9 @@ class P2PMessagingClient(config: NodeConfiguration, } override fun addMessageHandler(topic: String, - sessionID: Long, callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit): MessageHandlerRegistration { - return addMessageHandler(TopicSession(topic, sessionID), callback) - } - - override fun addMessageHandler(topicSession: TopicSession, - callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit): MessageHandlerRegistration { - require(!topicSession.isBlank()) { "Topic must not be blank, as the empty topic is a special case." } - val handler = Handler(topicSession, callback) + require(!topic.isBlank()) { "Topic must not be blank, as the empty topic is a special case." } + val handler = Handler(topic, callback) handlers.add(handler) return handler } @@ -639,9 +624,9 @@ class P2PMessagingClient(config: NodeConfiguration, handlers.remove(registration) } - override fun createMessage(topicSession: TopicSession, data: ByteArray, uuid: UUID): Message { + override fun createMessage(topic: String, data: ByteArray, deduplicationId: String): Message { // TODO: We could write an object that proxies directly to an underlying MQ message here and avoid copying. - return NodeClientMessage(topicSession, data, uuid) + return NodeClientMessage(topic, OpaqueBytes(data), deduplicationId) } // TODO Rethink PartyInfo idea and merging PeerAddress/ServiceAddress (the only difference is that Service address doesn't hold host and port) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ServiceRequestMessage.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ServiceRequestMessage.kt deleted file mode 100644 index 15c68a3b66..0000000000 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ServiceRequestMessage.kt +++ /dev/null @@ -1,27 +0,0 @@ -package net.corda.node.services.messaging - -import net.corda.core.concurrent.CordaFuture -import net.corda.core.messaging.MessageRecipients -import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.serialization.CordaSerializable - -/** - * Abstract superclass for request messages sent to services which expect a reply. - */ -@CordaSerializable -interface ServiceRequestMessage { - val sessionID: Long - val replyTo: SingleMessageRecipient -} - -/** - * Sends a [ServiceRequestMessage] to [target] and returns a [CordaFuture] of the response. - * @param R The type of the response. - */ -fun MessagingService.sendRequest(topic: String, - request: ServiceRequestMessage, - target: MessageRecipients): CordaFuture { - val responseFuture = onNext(topic, request.sessionID) - send(topic, MessagingService.DEFAULT_SESSION_ID, request, target) - return responseFuture -} diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowIORequest.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowIORequest.kt index bd29525072..65b24a7046 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowIORequest.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowIORequest.kt @@ -2,6 +2,7 @@ package net.corda.node.services.statemachine import co.paralleluniverse.fibers.Suspendable import net.corda.core.crypto.SecureHash +import net.corda.core.identity.Party import java.time.Instant interface FlowIORequest { @@ -22,29 +23,28 @@ interface SendRequest : SessionedFlowIORequest { val message: SessionMessage } -interface ReceiveRequest : SessionedFlowIORequest, WaitingRequest { - val receiveType: Class +interface ReceiveRequest : SessionedFlowIORequest, WaitingRequest { val userReceiveType: Class<*>? override fun shouldResume(message: ExistingSessionMessage, session: FlowSessionInternal): Boolean = this.session === session } -data class SendAndReceive(override val session: FlowSessionInternal, - override val message: SessionMessage, - override val receiveType: Class, - override val userReceiveType: Class<*>?) : SendRequest, ReceiveRequest { +data class SendAndReceive(override val session: FlowSessionInternal, + override val message: SessionMessage, + override val userReceiveType: Class<*>?) : SendRequest, ReceiveRequest { @Transient override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot() } -data class ReceiveOnly(override val session: FlowSessionInternal, - override val receiveType: Class, - override val userReceiveType: Class<*>?) : ReceiveRequest { +data class ReceiveOnly( + override val session: FlowSessionInternal, + override val userReceiveType: Class<*>? +) : ReceiveRequest { @Transient override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot() } -class ReceiveAll(val requests: List>) : WaitingRequest { +class ReceiveAll(val requests: List) : WaitingRequest { @Transient override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot() @@ -53,8 +53,8 @@ class ReceiveAll(val requests: List>) : WaitingReque } private fun shouldResumeIfRelevant() = requests.all { hasSuccessfulEndMessage(it) } - private fun hasSuccessfulEndMessage(it: ReceiveRequest): Boolean { - return it.session.receivedMessages.map { it.message }.any { it is SessionData || it is SessionEnd } + private fun hasSuccessfulEndMessage(it: ReceiveRequest): Boolean { + return it.session.receivedMessages.map { it.message.payload }.any { it is DataSessionMessage || it is EndSessionMessage } } @Suspendable @@ -70,7 +70,7 @@ class ReceiveAll(val requests: List>) : WaitingReque if (isComplete(receivedMessages)) { receivedMessages } else { - throw IllegalStateException(requests.filter { it.session !in receivedMessages.keys }.map { "Was expecting a ${it.receiveType.simpleName} but instead got nothing for $it." }.joinToString { "\n" }) + throw IllegalStateException(requests.filter { it.session !in receivedMessages.keys }.map { "Was expecting a message but instead got nothing for $it." }.joinToString { "\n" }) } } } @@ -90,15 +90,15 @@ class ReceiveAll(val requests: List>) : WaitingReque } @Suspendable - private fun poll(request: ReceiveRequest): ReceivedSessionMessage<*>? { - return request.session.receivedMessages.poll() + private fun poll(request: ReceiveRequest): ExistingSessionMessage? { + return request.session.receivedMessages.poll()?.message } override fun shouldResume(message: ExistingSessionMessage, session: FlowSessionInternal): Boolean = isRelevant(session) && shouldResumeIfRelevant() private fun isRelevant(session: FlowSessionInternal) = requests.any { it.session === session } - data class RequestMessage(val request: ReceiveRequest, val message: ReceivedSessionMessage<*>) + data class RequestMessage(val request: ReceiveRequest, val message: ExistingSessionMessage) } data class SendOnly(override val session: FlowSessionInternal, override val message: SessionMessage) : SendRequest { @@ -110,7 +110,7 @@ data class WaitForLedgerCommit(val hash: SecureHash, val fiber: FlowStateMachine @Transient override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot() - override fun shouldResume(message: ExistingSessionMessage, session: FlowSessionInternal): Boolean = message is ErrorSessionEnd + override fun shouldResume(message: ExistingSessionMessage, session: FlowSessionInternal): Boolean = message.payload is ErrorSessionMessage } data class Sleep(val until: Instant, val fiber: FlowStateMachineImpl<*>) : FlowIORequest { diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionInternal.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionInternal.kt index dc5b39c6f5..58c134e39c 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionInternal.kt @@ -17,18 +17,28 @@ import java.util.concurrent.ConcurrentLinkedQueue class FlowSessionInternal( val flow: FlowLogic<*>, val flowSession : FlowSession, - val ourSessionId: Long, + val ourSessionId: SessionId, val initiatingParty: Party?, var state: FlowSessionState, var retryable: Boolean = false) { - val receivedMessages = ConcurrentLinkedQueue>() + val receivedMessages = ConcurrentLinkedQueue() val fiber: FlowStateMachineImpl<*> get() = flow.stateMachine as FlowStateMachineImpl<*> override fun toString(): String { return "${javaClass.simpleName}(flow=$flow, ourSessionId=$ourSessionId, initiatingParty=$initiatingParty, state=$state)" } + + fun getPeerSessionId(): SessionId { + val sessionState = state + return when (sessionState) { + is FlowSessionState.Initiated -> sessionState.peerSessionId + else -> throw IllegalStateException("We've somehow held onto a non-initiated session: $this") + } + } } +data class ReceivedSessionMessage(val peerParty: Party, val message: ExistingSessionMessage) + /** * [FlowSessionState] describes the session's state. * @@ -50,7 +60,7 @@ sealed class FlowSessionState { override val sendToParty: Party get() = otherParty } - data class Initiated(val peerParty: Party, val peerSessionId: Long, val context: FlowInfo) : FlowSessionState() { + data class Initiated(val peerParty: Party, val peerSessionId: SessionId, val context: FlowInfo) : FlowSessionState() { override val sendToParty: Party get() = peerParty } } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt index fb3b06c737..7fc71e37c7 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt @@ -9,7 +9,7 @@ import com.google.common.primitives.Primitives import net.corda.core.concurrent.CordaFuture import net.corda.core.context.InvocationContext import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.random63BitValue +import net.corda.core.crypto.newSecureRandom import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate @@ -31,6 +31,7 @@ import net.corda.nodeapi.internal.persistence.contextTransaction import net.corda.nodeapi.internal.persistence.contextTransactionOrNull import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.io.IOException import java.nio.file.Paths import java.sql.SQLException import java.time.Duration @@ -180,7 +181,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, requireNonPrimitive(receiveType) logger.debug { "sendAndReceive(${receiveType.name}, $otherParty, ${payload.toString().abbreviate(300)}) ..." } val session = getConfirmedSessionIfPresent(otherParty, sessionFlow) - val receivedSessionData: ReceivedSessionMessage = if (session == null) { + val receivedSessionMessage: ReceivedSessionMessage = if (session == null) { val newSession = initiateSession(otherParty, sessionFlow, payload, waitForConfirmation = true, retryable = retrySend) // Only do a receive here as the session init has carried the payload receiveInternal(newSession, receiveType) @@ -188,8 +189,20 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, val sendData = createSessionData(session, payload) sendAndReceiveInternal(session, sendData, receiveType) } - logger.debug { "Received ${receivedSessionData.message.payload.toString().abbreviate(300)}" } - return receivedSessionData.checkPayloadIs(receiveType) + val sessionData = receivedSessionMessage.message.checkDataSessionMessage() + logger.debug { "Received ${sessionData.payload.toString().abbreviate(300)}" } + return sessionData.checkPayloadIs(receiveType) + } + + private fun ExistingSessionMessage.checkDataSessionMessage(): DataSessionMessage { + when (payload) { + is DataSessionMessage -> { + return payload + } + else -> { + throw IllegalStateException("Was expecting ${DataSessionMessage::class.java.simpleName} but got ${payload.javaClass.simpleName} instead") + } + } } @Suspendable @@ -200,9 +213,9 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, requireNonPrimitive(receiveType) logger.debug { "receive(${receiveType.name}, $otherParty) ..." } val session = getConfirmedSession(otherParty, sessionFlow) - val sessionData = receiveInternal(session, receiveType) - logger.debug { "Received ${sessionData.message.payload.toString().abbreviate(300)}" } - return sessionData.checkPayloadIs(receiveType) + val receivedSessionMessage = receiveInternal(session, receiveType).message.checkDataSessionMessage() + logger.debug { "Received ${receivedSessionMessage.payload.toString().abbreviate(300)}" } + return receivedSessionMessage.checkPayloadIs(receiveType) } private fun requireNonPrimitive(receiveType: Class<*>) { @@ -219,7 +232,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, // Don't send the payload again if it was already piggy-backed on a session init initiateSession(otherParty, sessionFlow, payload, waitForConfirmation = false) } else { - sendInternal(session, createSessionData(session, payload)) + sendInternal(session, ExistingSessionMessage(session.getPeerSessionId(), createSessionData(session, payload))) } } @@ -236,8 +249,8 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, // If the tx isn't committed then we may have been resumed due to an session ending in an error for (session in openSessions.values) { for (receivedMessage in session.receivedMessages) { - if (receivedMessage.message is ErrorSessionEnd) { - session.erroredEnd(receivedMessage.message) + if (receivedMessage.message.payload is ErrorSessionMessage) { + session.erroredEnd(receivedMessage.message.payload.flowException) } } } @@ -294,16 +307,18 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, @Suspendable override fun receiveAll(sessions: Map>, sessionFlow: FlowLogic<*>): Map> { - val requests = ArrayList>() + val requests = ArrayList() for ((session, receiveType) in sessions) { val sessionInternal = getConfirmedSession(session.counterparty, sessionFlow) - requests.add(ReceiveOnly(sessionInternal, SessionData::class.java, receiveType)) + requests.add(ReceiveOnly(sessionInternal, receiveType)) } val receivedMessages = ReceiveAll(requests).suspendAndExpectReceive(suspend) val result = LinkedHashMap>() for ((sessionInternal, requestAndMessage) in receivedMessages) { - val message = requestAndMessage.message.confirmReceiveType(requestAndMessage.request) - result[sessionInternal.flowSession] = message.checkPayloadIs(requestAndMessage.request.userReceiveType as Class) + val message = requestAndMessage.message.confirmNoError(requestAndMessage.request.session) + result[sessionInternal.flowSession] = message.checkDataSessionMessage().checkPayloadIs( + requestAndMessage.request.userReceiveType as Class + ) } return result } @@ -315,41 +330,46 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, */ @Suspendable private fun FlowSessionInternal.waitForConfirmation() { - val (peerParty, sessionInitResponse) = receiveInternal(this, null) - if (sessionInitResponse is SessionConfirm) { - state = FlowSessionState.Initiated( - peerParty, - sessionInitResponse.initiatedSessionId, - FlowInfo(sessionInitResponse.flowVersion, sessionInitResponse.appName)) - } else { - sessionInitResponse as SessionReject - throw UnexpectedFlowEndException("Party ${state.sendToParty} rejected session request: ${sessionInitResponse.errorMessage}") + val sessionInitResponse = receiveInternal(this, null) + val payload = sessionInitResponse.message.payload + when (payload) { + is ConfirmSessionMessage -> { + state = FlowSessionState.Initiated( + sessionInitResponse. + peerParty, + payload.initiatedSessionId, + payload.initiatedFlowInfo) + } + is RejectSessionMessage -> { + throw UnexpectedFlowEndException("Party ${state.sendToParty} rejected session request: ${payload.message}") + } + else -> { + throw IllegalStateException("Was expecting ${ConfirmSessionMessage::class.java.simpleName} but got ${payload.javaClass.simpleName} instead") + } } } - private fun createSessionData(session: FlowSessionInternal, payload: Any): SessionData { - val sessionState = session.state - val peerSessionId = when (sessionState) { - is FlowSessionState.Initiated -> sessionState.peerSessionId - else -> throw IllegalStateException("We've somehow held onto a non-initiated session: $session") - } - return SessionData(peerSessionId, payload.serialize(context = SerializationDefaults.P2P_CONTEXT)) + private fun createSessionData(session: FlowSessionInternal, payload: Any): DataSessionMessage { + return DataSessionMessage(payload.serialize(context = SerializationDefaults.P2P_CONTEXT)) } @Suspendable private fun sendInternal(session: FlowSessionInternal, message: SessionMessage) = suspend(SendOnly(session, message)) - private inline fun receiveInternal( + @Suspendable + private fun receiveInternal( session: FlowSessionInternal, - userReceiveType: Class<*>?): ReceivedSessionMessage { - return waitForMessage(ReceiveOnly(session, M::class.java, userReceiveType)) + userReceiveType: Class<*>?): ReceivedSessionMessage { + return waitForMessage(ReceiveOnly(session, userReceiveType)) } - private inline fun sendAndReceiveInternal( + @Suspendable + private fun sendAndReceiveInternal( session: FlowSessionInternal, - message: SessionMessage, - userReceiveType: Class<*>?): ReceivedSessionMessage { - return waitForMessage(SendAndReceive(session, message, M::class.java, userReceiveType)) + message: DataSessionMessage, + userReceiveType: Class<*>?): ReceivedSessionMessage { + val sessionMessage = ExistingSessionMessage(session.getPeerSessionId(), message) + return waitForMessage(SendAndReceive(session, sessionMessage, userReceiveType)) } @Suspendable @@ -377,7 +397,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, sessionFlow: FlowLogic<*> ) { logger.trace { "Creating a new session with $otherParty" } - val session = FlowSessionInternal(sessionFlow, flowSession, random63BitValue(), null, FlowSessionState.Uninitiated(otherParty)) + val session = FlowSessionInternal(sessionFlow, flowSession, SessionId.createRandom(newSecureRandom()), null, FlowSessionState.Uninitiated(otherParty)) openSessions[Pair(sessionFlow, otherParty)] = session } @@ -397,7 +417,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, val (version, initiatingFlowClass) = session.flow.javaClass.flowVersionAndInitiatingClass val payloadBytes = firstPayload?.serialize(context = SerializationDefaults.P2P_CONTEXT) logger.info("Initiating flow session with party ${otherParty.name}. Session id for tracing purposes is ${session.ourSessionId}.") - val sessionInit = SessionInit(session.ourSessionId, initiatingFlowClass.name, version, session.flow.javaClass.appName, payloadBytes) + val sessionInit = InitialSessionMessage(session.ourSessionId, newSecureRandom().nextLong(), initiatingFlowClass.name, version, session.flow.javaClass.appName, payloadBytes) sendInternal(session, sessionInit) if (waitForConfirmation) { session.waitForConfirmation() @@ -406,8 +426,10 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - private fun waitForMessage(receiveRequest: ReceiveRequest): ReceivedSessionMessage { - return receiveRequest.suspendAndExpectReceive().confirmReceiveType(receiveRequest) + private fun waitForMessage(receiveRequest: ReceiveRequest): ReceivedSessionMessage { + val receivedMessage = receiveRequest.suspendAndExpectReceive() + receivedMessage.message.confirmNoError(receiveRequest.session) + return receivedMessage } private val suspend : ReceiveAll.Suspend = object : ReceiveAll.Suspend { @@ -418,7 +440,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - private fun ReceiveRequest<*>.suspendAndExpectReceive(): ReceivedSessionMessage<*> { + private fun ReceiveRequest.suspendAndExpectReceive(): ReceivedSessionMessage { val polledMessage = session.receivedMessages.poll() return if (polledMessage != null) { if (this is SendAndReceive) { @@ -431,35 +453,36 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, // Suspend while we wait for a receive suspend(this) session.receivedMessages.poll() ?: - throw IllegalStateException("Was expecting a ${receiveType.simpleName} but instead got nothing for $this") + throw IllegalStateException("Was expecting a message but instead got nothing for $this") } } - private fun ReceivedSessionMessage<*>.confirmReceiveType( - receiveRequest: ReceiveRequest): ReceivedSessionMessage { - val session = receiveRequest.session - val receiveType = receiveRequest.receiveType - if (receiveType.isInstance(message)) { - return uncheckedCast(this) - } else if (message is SessionEnd) { - openSessions.values.remove(session) - if (message is ErrorSessionEnd) { - session.erroredEnd(message) - } else { - val expectedType = receiveRequest.userReceiveType?.name ?: receiveType.simpleName - throw UnexpectedFlowEndException("Counterparty flow on ${session.state.sendToParty} has completed without " + - "sending a $expectedType") + private fun ExistingSessionMessage.confirmNoError(session: FlowSessionInternal): ExistingSessionMessage { + when (payload) { + is ConfirmSessionMessage, + is DataSessionMessage -> { + return this + } + is ErrorSessionMessage -> { + openSessions.values.remove(session) + session.erroredEnd(payload.flowException) + } + is RejectSessionMessage -> { + session.erroredEnd(UnexpectedFlowEndException("Counterparty sent session rejection message at unexpected time with message ${payload.message}")) + } + EndSessionMessage -> { + openSessions.values.remove(session) + throw UnexpectedFlowEndException("Counterparty flow on ${session.state.sendToParty} has completed without " + + "sending data") } - } else { - throw IllegalStateException("Was expecting a ${receiveType.simpleName} but instead got $message for $receiveRequest") } } - private fun FlowSessionInternal.erroredEnd(end: ErrorSessionEnd): Nothing { - if (end.errorResponse != null) { + private fun FlowSessionInternal.erroredEnd(exception: Throwable?): Nothing { + if (exception != null) { @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - (end.errorResponse as java.lang.Throwable).fillInStackTrace() - throw end.errorResponse + exception.fillInStackTrace() + throw exception } else { throw UnexpectedFlowEndException("Counterparty flow on ${state.sendToParty} had an internal error and has terminated") } @@ -560,3 +583,15 @@ val Class>.appName: String "" } } + +fun DataSessionMessage.checkPayloadIs(type: Class): UntrustworthyData { + val payloadData: T = try { + val serializer = SerializationDefaults.SERIALIZATION_FACTORY + serializer.deserialize(payload, type, SerializationDefaults.P2P_CONTEXT) + } catch (ex: Exception) { + throw IOException("Payload invalid", ex) + } + return type.castIfPossible(payloadData)?.let { UntrustworthyData(it) } ?: + throw UnexpectedFlowEndException("We were expecting a ${type.name} but we instead got a " + + "${payloadData.javaClass.name} (${payloadData})") +} diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt index c321d3768a..5481275f05 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/SessionMessage.kt @@ -1,59 +1,122 @@ package net.corda.node.services.statemachine +import net.corda.core.crypto.random63BitValue import net.corda.core.flows.FlowException -import net.corda.core.flows.UnexpectedFlowEndException -import net.corda.core.identity.Party -import net.corda.core.internal.castIfPossible +import net.corda.core.flows.FlowInfo import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SerializedBytes -import net.corda.core.utilities.UntrustworthyData -import java.io.IOException +import java.security.SecureRandom + +/** + * A session between two flows is identified by two session IDs, the initiating and the initiated session ID. + * However after the session has been established the communication is symmetric. From then on we differentiate between + * the two session IDs with "source" ID (the ID from which we receive) and "sink" ID (the ID to which we send). + * + * Flow A (initiating) Flow B (initiated) + * initiatingId=sourceId=0 + * send(Initiate(initiatingId=0)) -----> initiatingId=sinkId=0 + * initiatedId=sourceId=1 + * initiatedId=sinkId=1 <----- send(Confirm(initiatedId=1)) + */ +@CordaSerializable +sealed class SessionMessage + @CordaSerializable -interface SessionMessage - -interface ExistingSessionMessage : SessionMessage { - val recipientSessionId: Long -} - -interface SessionInitResponse : ExistingSessionMessage { - val initiatorSessionId: Long - override val recipientSessionId: Long get() = initiatorSessionId -} - -interface SessionEnd : ExistingSessionMessage - -data class SessionInit(val initiatorSessionId: Long, - val initiatingFlowClass: String, - val flowVersion: Int, - val appName: String, - val firstPayload: SerializedBytes?) : SessionMessage - -data class SessionConfirm(override val initiatorSessionId: Long, - val initiatedSessionId: Long, - val flowVersion: Int, - val appName: String) : SessionInitResponse - -data class SessionReject(override val initiatorSessionId: Long, val errorMessage: String) : SessionInitResponse - -data class SessionData(override val recipientSessionId: Long, val payload: SerializedBytes) : ExistingSessionMessage - -data class NormalSessionEnd(override val recipientSessionId: Long) : SessionEnd - -data class ErrorSessionEnd(override val recipientSessionId: Long, val errorResponse: FlowException?) : SessionEnd - -data class ReceivedSessionMessage(val sender: Party, val message: M) - -fun ReceivedSessionMessage.checkPayloadIs(type: Class): UntrustworthyData { - val payloadData: T = try { - val serializer = SerializationDefaults.SERIALIZATION_FACTORY - serializer.deserialize(message.payload, type, SerializationDefaults.P2P_CONTEXT) - } catch (ex: Exception) { - throw IOException("Payload invalid", ex) +data class SessionId(val toLong: Long) { + companion object { + fun createRandom(secureRandom: SecureRandom) = SessionId(secureRandom.nextLong()) } - return type.castIfPossible(payloadData)?.let { UntrustworthyData(it) } ?: - throw UnexpectedFlowEndException("We were expecting a ${type.name} from $sender but we instead got a " + - "${payloadData.javaClass.name} (${payloadData})") - } + +/** + * The initial message to initiate a session with. + * + * @param initiatorSessionId the session ID of the initiator. On the sending side this is the *source* ID, on the + * receiving side this is the *sink* ID. + * @param initiationEntropy additional randomness to seed the initiated flow's deduplication ID. + * @param initiatorFlowClassName the class name to be used to determine the initiating-initiated mapping on the receiver + * side. + * @param flowVersion the version of the initiating flow. + * @param appName the name of the cordapp defining the initiating flow, or "corda" if it's a core flow. + * @param firstPayload the optional first payload. + */ +data class InitialSessionMessage( + val initiatorSessionId: SessionId, + val initiationEntropy: Long, + val initiatorFlowClassName: String, + val flowVersion: Int, + val appName: String, + val firstPayload: SerializedBytes? +) : SessionMessage() { + override fun toString() = "InitialSessionMessage(" + + "initiatorSessionId=$initiatorSessionId, " + + "initiationEntropy=$initiationEntropy, " + + "initiatorFlowClassName=$initiatorFlowClassName, " + + "appName=$appName, " + + "firstPayload=${firstPayload?.javaClass}" + + ")" +} + +/** + * A message sent when a session has been established already. + * + * @param recipientSessionId the recipient session ID. On the sending side this is the *sink* ID, on the receiving side + * this is the *source* ID. + * @param payload the rest of the message. + */ +data class ExistingSessionMessage( + val recipientSessionId: SessionId, + val payload: ExistingSessionMessagePayload +) : SessionMessage() + +/** + * The payload of an [ExistingSessionMessage] + */ +@CordaSerializable +sealed class ExistingSessionMessagePayload + +/** + * The confirmation message sent by the initiated side. + * @param initiatedSessionId the initiated session ID, the other half of [InitialSessionMessage.initiatorSessionId]. + * This is the *source* ID on the sending(initiated) side, and the *sink* ID on the receiving(initiating) side. + */ +data class ConfirmSessionMessage( + val initiatedSessionId: SessionId, + val initiatedFlowInfo: FlowInfo +) : ExistingSessionMessagePayload() + +/** + * A message containing flow-related data. + * + * @param payload the serialised payload. + */ +data class DataSessionMessage(val payload: SerializedBytes) : ExistingSessionMessagePayload() { + override fun toString() = "DataSessionMessage(payload=${payload.javaClass})" +} + +/** + * A message indicating that an error has happened. + * + * @param flowException the exception that happened. This is null if the error condition wasn't revealed to the + * receiving side. + * @param errorId the ID of the source error. This is always specified to allow posteriori correlation of error conditions. + */ +data class ErrorSessionMessage(val flowException: FlowException?, val errorId: Long) : ExistingSessionMessagePayload() + +/** + * A message indicating that a session initiation has failed. + * + * @param message a message describing the problem to the initator. + * @param errorId an error ID identifying this error condition. + */ +data class RejectSessionMessage(val message: String, val errorId: Long) : ExistingSessionMessagePayload() + +/** + * A message indicating that the flow hosting the session has ended. Note that this message is strictly part of the + * session protocol, the flow may be removed before all counter-flows have ended. + * + * The sole purpose of this message currently is to provide diagnostic in cases where the two communicating flows' + * protocols don't match up, e.g. one is waiting for the other, but the other side has already finished. + */ +object EndSessionMessage : ExistingSessionMessagePayload() diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt index 500529aa5c..f6b7ebfe01 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -13,6 +13,7 @@ import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.context.InvocationContext import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.newSecureRandom import net.corda.core.crypto.random63BitValue import net.corda.core.flows.FlowException import net.corda.core.flows.FlowInfo @@ -37,7 +38,6 @@ import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.config.shouldCheckCheckpoints import net.corda.node.services.messaging.ReceivedMessage -import net.corda.node.services.messaging.TopicSession import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.newNamedSingleThreadExecutor import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -72,7 +72,7 @@ class StateMachineManagerImpl( companion object { private val logger = contextLogger() - internal val sessionTopic = TopicSession("platform.session") + internal val sessionTopic = "platform.session" init { Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable -> @@ -121,8 +121,8 @@ class StateMachineManagerImpl( private val totalStartedFlows = metrics.counter("Flows.Started") private val totalFinishedFlows = metrics.counter("Flows.Finished") - private val openSessions = ConcurrentHashMap() - private val recentlyClosedSessions = ConcurrentHashMap() + private val openSessions = ConcurrentHashMap() + private val recentlyClosedSessions = ConcurrentHashMap() // Context for tokenized services in checkpoints private lateinit var tokenizableServices: List @@ -281,7 +281,7 @@ class StateMachineManagerImpl( if (sender != null) { when (sessionMessage) { is ExistingSessionMessage -> onExistingSessionMessage(sessionMessage, sender) - is SessionInit -> onSessionInit(sessionMessage, message, sender) + is InitialSessionMessage -> onSessionInit(sessionMessage, message, sender) } } else { logger.error("Unknown peer $peer in $sessionMessage") @@ -294,15 +294,15 @@ class StateMachineManagerImpl( session.fiber.pushToLoggingContext() session.fiber.logger.trace { "Received $message on $session from $sender" } if (session.retryable) { - if (message is SessionConfirm && session.state is FlowSessionState.Initiated) { + if (message.payload is ConfirmSessionMessage && session.state is FlowSessionState.Initiated) { session.fiber.logger.trace { "Ignoring duplicate confirmation for session ${session.ourSessionId} – session is idempotent" } return } - if (message !is SessionConfirm) { - serviceHub.networkService.cancelRedelivery(session.ourSessionId) + if (message.payload !is ConfirmSessionMessage) { + serviceHub.networkService.cancelRedelivery(session.ourSessionId.toLong) } } - if (message is SessionEnd) { + if (message.payload is EndSessionMessage || message.payload is ErrorSessionMessage) { openSessions.remove(message.recipientSessionId) } session.receivedMessages += ReceivedSessionMessage(sender, message) @@ -317,9 +317,9 @@ class StateMachineManagerImpl( } else { val peerParty = recentlyClosedSessions.remove(message.recipientSessionId) if (peerParty != null) { - if (message is SessionConfirm) { + if (message.payload is ConfirmSessionMessage) { logger.trace { "Received session confirmation but associated fiber has already terminated, so sending session end" } - sendSessionMessage(peerParty, NormalSessionEnd(message.initiatedSessionId)) + sendSessionMessage(peerParty, ExistingSessionMessage(message.payload.initiatedSessionId, EndSessionMessage)) } else { logger.trace { "Ignoring session end message for already closed session: $message" } } @@ -336,12 +336,12 @@ class StateMachineManagerImpl( return waitingForResponse?.shouldResume(message, session) ?: false } - private fun onSessionInit(sessionInit: SessionInit, receivedMessage: ReceivedMessage, sender: Party) { + private fun onSessionInit(sessionInit: InitialSessionMessage, receivedMessage: ReceivedMessage, sender: Party) { logger.trace { "Received $sessionInit from $sender" } val senderSessionId = sessionInit.initiatorSessionId - fun sendSessionReject(message: String) = sendSessionMessage(sender, SessionReject(senderSessionId, message)) + fun sendSessionReject(message: String) = sendSessionMessage(sender, ExistingSessionMessage(senderSessionId, RejectSessionMessage(message, random63BitValue()))) val (session, initiatedFlowFactory) = try { val initiatedFlowFactory = getInitiatedFlowFactory(sessionInit) @@ -354,11 +354,11 @@ class StateMachineManagerImpl( val session = FlowSessionInternal( flow, flowSession, - random63BitValue(), + SessionId.createRandom(newSecureRandom()), sender, FlowSessionState.Initiated(sender, senderSessionId, FlowInfo(senderFlowVersion, sessionInit.appName))) if (sessionInit.firstPayload != null) { - session.receivedMessages += ReceivedSessionMessage(sender, SessionData(session.ourSessionId, sessionInit.firstPayload)) + session.receivedMessages += ReceivedSessionMessage(sender, ExistingSessionMessage(session.ourSessionId, DataSessionMessage(sessionInit.firstPayload))) } openSessions[session.ourSessionId] = session val context = InvocationContext.peer(sender.name) @@ -386,19 +386,19 @@ class StateMachineManagerImpl( is InitiatedFlowFactory.CorDapp -> initiatedFlowFactory.flowVersion to initiatedFlowFactory.appName } - sendSessionMessage(sender, SessionConfirm(senderSessionId, session.ourSessionId, ourFlowVersion, appName), session.fiber) - session.fiber.logger.debug { "Initiated by $sender using ${sessionInit.initiatingFlowClass}" } + sendSessionMessage(sender, ExistingSessionMessage(senderSessionId, ConfirmSessionMessage(session.ourSessionId, FlowInfo(ourFlowVersion, appName))), session.fiber) + session.fiber.logger.debug { "Initiated by $sender using ${sessionInit.initiatorFlowClassName}" } session.fiber.logger.trace { "Initiated from $sessionInit on $session" } resumeFiber(session.fiber) } - private fun getInitiatedFlowFactory(sessionInit: SessionInit): InitiatedFlowFactory<*> { + private fun getInitiatedFlowFactory(sessionInit: InitialSessionMessage): InitiatedFlowFactory<*> { val initiatingFlowClass = try { - Class.forName(sessionInit.initiatingFlowClass, true, classloader).asSubclass(FlowLogic::class.java) + Class.forName(sessionInit.initiatorFlowClassName, true, classloader).asSubclass(FlowLogic::class.java) } catch (e: ClassNotFoundException) { - throw SessionRejectException("Don't know ${sessionInit.initiatingFlowClass}") + throw SessionRejectException("Don't know ${sessionInit.initiatorFlowClassName}") } catch (e: ClassCastException) { - throw SessionRejectException("${sessionInit.initiatingFlowClass} is not a flow") + throw SessionRejectException("${sessionInit.initiatorFlowClassName} is not a flow") } return serviceHub.getFlowFactory(initiatingFlowClass) ?: throw SessionRejectException("$initiatingFlowClass is not registered") @@ -492,7 +492,7 @@ class StateMachineManagerImpl( private fun FlowSessionInternal.endSession(context: InvocationContext, exception: Throwable?, propagated: Boolean) { val initiatedState = state as? FlowSessionState.Initiated ?: return val sessionEnd = if (exception == null) { - NormalSessionEnd(initiatedState.peerSessionId) + EndSessionMessage } else { val errorResponse = if (exception is FlowException && (!propagated || initiatingParty != null)) { // Only propagate this FlowException if our local flow threw it or it was propagated to us and we only @@ -501,9 +501,9 @@ class StateMachineManagerImpl( } else { null } - ErrorSessionEnd(initiatedState.peerSessionId, errorResponse) + ErrorSessionMessage(errorResponse, 0) } - sendSessionMessage(initiatedState.peerParty, sessionEnd, fiber) + sendSessionMessage(initiatedState.peerParty, ExistingSessionMessage(initiatedState.peerSessionId, sessionEnd), fiber) recentlyClosedSessions[ourSessionId] = initiatedState.peerParty } @@ -573,14 +573,14 @@ class StateMachineManagerImpl( } private fun processSendRequest(ioRequest: SendRequest) { - val retryId = if (ioRequest.message is SessionInit) { + val retryId = if (ioRequest.message is InitialSessionMessage) { with(ioRequest.session) { openSessions[ourSessionId] = this - if (retryable) ourSessionId else null + if (retryable) ourSessionId.toLong else null } } else null sendSessionMessage(ioRequest.session.state.sendToParty, ioRequest.message, ioRequest.session.fiber, retryId) - if (ioRequest !is ReceiveRequest<*>) { + if (ioRequest !is ReceiveRequest) { // We sent a message, but don't expect a response, so re-enter the continuation to let it keep going. resumeFiber(ioRequest.session.fiber) } @@ -625,12 +625,15 @@ class StateMachineManagerImpl( // Handling Kryo and AMQP serialization problems. Unfortunately the two exception types do not share much of a common exception interface. is KryoException, is NotSerializableException -> { - if (message !is ErrorSessionEnd || message.errorResponse == null) throw e - logger.warn("Something in ${message.errorResponse.javaClass.name} is not serialisable. " + - "Instead sending back an exception which is serialisable to ensure session end occurs properly.", e) - // The subclass may have overridden toString so we use that - val exMessage = message.errorResponse.let { if (it.javaClass != FlowException::class.java) it.toString() else it.message } - message.copy(errorResponse = FlowException(exMessage)).serialize() + if (message is ExistingSessionMessage && message.payload is ErrorSessionMessage && message.payload.flowException != null) { + logger.warn("Something in ${message.payload.flowException.javaClass.name} is not serialisable. " + + "Instead sending back an exception which is serialisable to ensure session end occurs properly.", e) + // The subclass may have overridden toString so we use that + val exMessage = message.payload.flowException.message + message.copy(payload = message.payload.copy(flowException = FlowException(exMessage))).serialize() + } else { + throw e + } } else -> throw e } diff --git a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt index 0f57a4dc3c..425d2f0e94 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt @@ -3,7 +3,6 @@ package net.corda.node.messaging import net.corda.core.messaging.AllPossibleRecipients import net.corda.node.services.messaging.Message import net.corda.node.services.messaging.TopicStringValidator -import net.corda.node.services.messaging.createMessage import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockNetwork import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY @@ -51,10 +50,10 @@ class InMemoryMessagingTests { val bits = "test-content".toByteArray() var finalDelivery: Message? = null - node2.network.addMessageHandler { msg, _ -> + node2.network.addMessageHandler("test.topic") { msg, _ -> node2.network.send(msg, node3.network.myAddress) } - node3.network.addMessageHandler { msg, _ -> + node3.network.addMessageHandler("test.topic") { msg, _ -> finalDelivery = msg } @@ -63,7 +62,7 @@ class InMemoryMessagingTests { mockNet.runNetwork(rounds = 1) - assertTrue(Arrays.equals(finalDelivery!!.data, bits)) + assertTrue(Arrays.equals(finalDelivery!!.data.bytes, bits)) } @Test @@ -75,7 +74,7 @@ class InMemoryMessagingTests { val bits = "test-content".toByteArray() var counter = 0 - listOf(node1, node2, node3).forEach { it.network.addMessageHandler { _, _ -> counter++ } } + listOf(node1, node2, node3).forEach { it.network.addMessageHandler("test.topic") { _, _ -> counter++ } } node1.network.send(node2.network.createMessage("test.topic", data = bits), rigorousMock()) mockNet.runNetwork(rounds = 1) assertEquals(3, counter) diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt index 7a464d548b..e17bfe86ac 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt @@ -126,7 +126,7 @@ class ArtemisMessagingTest { messagingClient.send(message, messagingClient.myAddress) val actual: Message = receivedMessages.take() - assertEquals("first msg", String(actual.data)) + assertEquals("first msg", String(actual.data.bytes)) assertNull(receivedMessages.poll(200, MILLISECONDS)) } diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 2ac326ff2a..e16037795a 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -37,8 +37,8 @@ import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStra import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode import net.corda.testing.node.MockNodeParameters -import net.corda.testing.node.pumpReceive import net.corda.testing.node.internal.startFlow +import net.corda.testing.node.pumpReceive import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType @@ -296,14 +296,16 @@ class FlowFrameworkTests { mockNet.runNetwork() assertThatExceptionOfType(UnexpectedFlowEndException::class.java).isThrownBy { resultFuture.getOrThrow() - }.withMessageContaining(String::class.java.name) // Make sure the exception message mentions the type the flow was expecting to receive + } } @Test fun `receiving unexpected session end before entering sendAndReceive`() { bobNode.registerFlowFactory(WaitForOtherSideEndBeforeSendAndReceive::class) { NoOpFlow() } val sessionEndReceived = Semaphore(0) - receivedSessionMessagesObservable().filter { it.message is SessionEnd }.subscribe { sessionEndReceived.release() } + receivedSessionMessagesObservable().filter { + it.message is ExistingSessionMessage && it.message.payload is EndSessionMessage + }.subscribe { sessionEndReceived.release() } val resultFuture = aliceNode.services.startFlow( WaitForOtherSideEndBeforeSendAndReceive(bob, sessionEndReceived)).resultFuture mockNet.runNetwork() @@ -356,7 +358,7 @@ class FlowFrameworkTests { assertSessionTransfers( aliceNode sent sessionInit(ReceiveFlow::class) to bobNode, bobNode sent sessionConfirm() to aliceNode, - bobNode sent erroredEnd() to aliceNode + bobNode sent errorMessage() to aliceNode ) } @@ -389,10 +391,11 @@ class FlowFrameworkTests { assertSessionTransfers( aliceNode sent sessionInit(ReceiveFlow::class) to bobNode, bobNode sent sessionConfirm() to aliceNode, - bobNode sent erroredEnd(erroringFlow.get().exceptionThrown) to aliceNode + bobNode sent errorMessage(erroringFlow.get().exceptionThrown) to aliceNode ) // Make sure the original stack trace isn't sent down the wire - assertThat((receivedSessionMessages.last().message as ErrorSessionEnd).errorResponse!!.stackTrace).isEmpty() + val lastMessage = receivedSessionMessages.last().message as ExistingSessionMessage + assertThat((lastMessage.payload as ErrorSessionMessage).flowException!!.stackTrace).isEmpty() } @Test @@ -438,7 +441,7 @@ class FlowFrameworkTests { aliceNode sent sessionInit(ReceiveFlow::class) to bobNode, bobNode sent sessionConfirm() to aliceNode, bobNode sent sessionData("Hello") to aliceNode, - aliceNode sent erroredEnd() to bobNode + aliceNode sent errorMessage() to bobNode ) } @@ -603,20 +606,20 @@ class FlowFrameworkTests { @Test fun `unknown class in session init`() { - aliceNode.sendSessionMessage(SessionInit(random63BitValue(), "not.a.real.Class", 1, "version", null), bob) + aliceNode.sendSessionMessage(InitialSessionMessage(SessionId(random63BitValue()), 0, "not.a.real.Class", 1, "", null), bob) mockNet.runNetwork() assertThat(receivedSessionMessages).hasSize(2) // Only the session-init and session-reject are expected - val reject = receivedSessionMessages.last().message as SessionReject - assertThat(reject.errorMessage).isEqualTo("Don't know not.a.real.Class") + val lastMessage = receivedSessionMessages.last().message as ExistingSessionMessage + assertThat((lastMessage.payload as RejectSessionMessage).message).isEqualTo("Don't know not.a.real.Class") } @Test fun `non-flow class in session init`() { - aliceNode.sendSessionMessage(SessionInit(random63BitValue(), String::class.java.name, 1, "version", null), bob) + aliceNode.sendSessionMessage(InitialSessionMessage(SessionId(random63BitValue()), 0, String::class.java.name, 1, "", null), bob) mockNet.runNetwork() assertThat(receivedSessionMessages).hasSize(2) // Only the session-init and session-reject are expected - val reject = receivedSessionMessages.last().message as SessionReject - assertThat(reject.errorMessage).isEqualTo("${String::class.java.name} is not a flow") + val lastMessage = receivedSessionMessages.last().message as ExistingSessionMessage + assertThat((lastMessage.payload as RejectSessionMessage).message).isEqualTo("${String::class.java.name} is not a flow") } @Test @@ -682,14 +685,14 @@ class FlowFrameworkTests { return observable.toFuture() } - private fun sessionInit(clientFlowClass: KClass>, flowVersion: Int = 1, payload: Any? = null): SessionInit { - return SessionInit(0, clientFlowClass.java.name, flowVersion, "", payload?.serialize()) + private fun sessionInit(clientFlowClass: KClass>, flowVersion: Int = 1, payload: Any? = null): InitialSessionMessage { + return InitialSessionMessage(SessionId(0), 0, clientFlowClass.java.name, flowVersion, "", payload?.serialize()) } - private fun sessionConfirm(flowVersion: Int = 1) = SessionConfirm(0, 0, flowVersion, "") - private fun sessionData(payload: Any) = SessionData(0, payload.serialize()) - private val normalEnd = NormalSessionEnd(0) - private fun erroredEnd(errorResponse: FlowException? = null) = ErrorSessionEnd(0, errorResponse) + private fun sessionConfirm(flowVersion: Int = 1) = ExistingSessionMessage(SessionId(0), ConfirmSessionMessage(SessionId(0), FlowInfo(flowVersion, ""))) + private fun sessionData(payload: Any) = ExistingSessionMessage(SessionId(0), DataSessionMessage(payload.serialize())) + private val normalEnd = ExistingSessionMessage(SessionId(0), EndSessionMessage) // NormalSessionEnd(0) + private fun errorMessage(errorResponse: FlowException? = null) = ExistingSessionMessage(SessionId(0), ErrorSessionMessage(errorResponse, 0)) private fun StartedNode<*>.sendSessionMessage(message: SessionMessage, destination: Party) { services.networkService.apply { @@ -709,7 +712,9 @@ class FlowFrameworkTests { } private data class SessionTransfer(val from: Int, val message: SessionMessage, val to: MessageRecipients) { - val isPayloadTransfer: Boolean get() = message is SessionData || message is SessionInit && message.firstPayload != null + val isPayloadTransfer: Boolean get() = + message is ExistingSessionMessage && message.payload is DataSessionMessage || + message is InitialSessionMessage && message.firstPayload != null override fun toString(): String = "$from sent $message to $to" } @@ -718,7 +723,7 @@ class FlowFrameworkTests { } private fun Observable.toSessionTransfers(): Observable { - return filter { it.message.topicSession == StateMachineManagerImpl.sessionTopic }.map { + return filter { it.message.topic == StateMachineManagerImpl.sessionTopic }.map { val from = it.sender.id val message = it.message.data.deserialize() SessionTransfer(from, sanitise(message), it.recipients) @@ -726,12 +731,23 @@ class FlowFrameworkTests { } private fun sanitise(message: SessionMessage) = when (message) { - is SessionData -> message.copy(recipientSessionId = 0) - is SessionInit -> message.copy(initiatorSessionId = 0, appName = "") - is SessionConfirm -> message.copy(initiatorSessionId = 0, initiatedSessionId = 0, appName = "") - is NormalSessionEnd -> message.copy(recipientSessionId = 0) - is ErrorSessionEnd -> message.copy(recipientSessionId = 0) - else -> message + is InitialSessionMessage -> message.copy(initiatorSessionId = SessionId(0), initiationEntropy = 0, appName = "") + is ExistingSessionMessage -> { + val payload = message.payload + message.copy( + recipientSessionId = SessionId(0), + payload = when (payload) { + is ConfirmSessionMessage -> payload.copy( + initiatedSessionId = SessionId(0), + initiatedFlowInfo = payload.initiatedFlowInfo.copy(appName = "") + ) + is ErrorSessionMessage -> payload.copy( + errorId = 0 + ) + else -> payload + } + ) + } } private infix fun StartedNode.sent(message: SessionMessage): Pair = Pair(internals.id, message) diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt index c96b08c2f2..bbf21b1ece 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt @@ -15,9 +15,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.utilities.ProgressTracker import net.corda.netmap.VisualiserViewModel.Style import net.corda.netmap.simulation.IRSSimulation -import net.corda.node.services.statemachine.SessionConfirm -import net.corda.node.services.statemachine.SessionEnd -import net.corda.node.services.statemachine.SessionInit +import net.corda.node.services.statemachine.* import net.corda.testing.core.chooseIdentity import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockNetwork @@ -342,12 +340,16 @@ class NetworkMapVisualiser : Application() { private fun transferIsInteresting(transfer: InMemoryMessagingNetwork.MessageTransfer): Boolean { // Loopback messages are boring. if (transfer.sender == transfer.recipients) return false - val message = transfer.message.data.deserialize() + val message = transfer.message.data.deserialize() return when (message) { - is SessionEnd -> false - is SessionConfirm -> false - is SessionInit -> message.firstPayload != null - else -> true + is InitialSessionMessage -> message.firstPayload != null + is ExistingSessionMessage -> when (message.payload) { + is ConfirmSessionMessage -> false + is DataSessionMessage -> true + is ErrorSessionMessage -> true + is RejectSessionMessage -> true + is EndSessionMessage -> false + } } } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index fbfc3cf4d9..f1d45b1028 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -14,11 +14,17 @@ import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.utilities.ByteSequence +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.trace -import net.corda.node.services.messaging.* +import net.corda.node.services.messaging.Message +import net.corda.node.services.messaging.MessageHandlerRegistration +import net.corda.node.services.messaging.MessagingService +import net.corda.node.services.messaging.ReceivedMessage import net.corda.node.utilities.AffinityExecutor import net.corda.nodeapi.internal.persistence.CordaPersistence +import net.corda.testing.node.InMemoryMessagingNetwork.TestMessagingService import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.LoggerFactory import rx.Observable @@ -57,7 +63,7 @@ class InMemoryMessagingNetwork internal constructor( @CordaSerializable data class MessageTransfer(val sender: PeerHandle, val message: Message, val recipients: MessageRecipients) { - override fun toString() = "${message.topicSession} from '$sender' to '$recipients'" + override fun toString() = "${message.topic} from '$sender' to '$recipients'" } // All sent messages are kept here until pumpSend is called, or manuallyPumped is set to false @@ -241,17 +247,17 @@ class InMemoryMessagingNetwork internal constructor( _sentMessages.onNext(transfer) } - data class InMemoryMessage(override val topicSession: TopicSession, - override val data: ByteArray, - override val uniqueMessageId: UUID, - override val debugTimestamp: Instant = Instant.now()) : Message { - override fun toString() = "$topicSession#${String(data)}" + data class InMemoryMessage(override val topic: String, + override val data: ByteSequence, + override val uniqueMessageId: String, + override val debugTimestamp: Instant = Instant.now()) : Message { + override fun toString() = "$topic#${String(data.bytes)}" } - private data class InMemoryReceivedMessage(override val topicSession: TopicSession, - override val data: ByteArray, + private data class InMemoryReceivedMessage(override val topic: String, + override val data: ByteSequence, override val platformVersion: Int, - override val uniqueMessageId: UUID, + override val uniqueMessageId: String, override val debugTimestamp: Instant, override val peer: CordaX500Name) : ReceivedMessage @@ -270,8 +276,7 @@ class InMemoryMessagingNetwork internal constructor( private val peerHandle: PeerHandle, private val executor: AffinityExecutor, private val database: CordaPersistence) : SingletonSerializeAsToken(), TestMessagingService { - private inner class Handler(val topicSession: TopicSession, - val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration + inner class Handler(val topicSession: String, val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration @Volatile private var running = true @@ -282,7 +287,7 @@ class InMemoryMessagingNetwork internal constructor( } private val state = ThreadBox(InnerState()) - private val processedMessages: MutableSet = Collections.synchronizedSet(HashSet()) + private val processedMessages: MutableSet = Collections.synchronizedSet(HashSet()) override val myAddress: PeerHandle get() = peerHandle @@ -304,13 +309,10 @@ class InMemoryMessagingNetwork internal constructor( } } - override fun addMessageHandler(topic: String, sessionID: Long, callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit): MessageHandlerRegistration - = addMessageHandler(TopicSession(topic, sessionID), callback) - - override fun addMessageHandler(topicSession: TopicSession, callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit): MessageHandlerRegistration { + override fun addMessageHandler(topic: String, callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit): MessageHandlerRegistration { check(running) val (handler, transfers) = state.locked { - val handler = Handler(topicSession, callback).apply { handlers.add(this) } + val handler = Handler(topic, callback).apply { handlers.add(this) } val pending = ArrayList() database.transaction { pending.addAll(pendingRedelivery) @@ -328,20 +330,18 @@ class InMemoryMessagingNetwork internal constructor( state.locked { check(handlers.remove(registration as Handler)) } } - override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any) { check(running) msgSend(this, message, target) - acknowledgementHandler?.invoke() if (!sendManuallyPumped) { pumpSend(false) } } - override fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)?) { + override fun send(addressedMessages: List) { for ((message, target, retryId, sequenceKey) in addressedMessages) { - send(message, target, retryId, sequenceKey, null) + send(message, target, retryId, sequenceKey) } - acknowledgementHandler?.invoke() } override fun stop() { @@ -356,8 +356,8 @@ class InMemoryMessagingNetwork internal constructor( override fun cancelRedelivery(retryId: Long) {} /** Returns the given (topic & session, data) pair as a newly created message object. */ - override fun createMessage(topicSession: TopicSession, data: ByteArray, uuid: UUID): Message { - return InMemoryMessage(topicSession, data, uuid) + override fun createMessage(topic: String, data: ByteArray, deduplicationId: String): Message { + return InMemoryMessage(topic, OpaqueBytes(data), deduplicationId) } /** @@ -390,14 +390,14 @@ class InMemoryMessagingNetwork internal constructor( while (deliverTo == null) { val transfer = (if (block) q.take() else q.poll()) ?: return null deliverTo = state.locked { - val matchingHandlers = handlers.filter { it.topicSession.isBlank() || transfer.message.topicSession == it.topicSession } + val matchingHandlers = handlers.filter { it.topicSession.isBlank() || transfer.message.topic == it.topicSession } if (matchingHandlers.isEmpty()) { // Got no handlers for this message yet. Keep the message around and attempt redelivery after a new // handler has been registered. The purpose of this path is to make unit tests that have multi-threading // reliable, as a sender may attempt to send a message to a receiver that hasn't finished setting // up a handler for yet. Most unit tests don't run threaded, but we want to test true parallelism at // least sometimes. - log.warn("Message to ${transfer.message.topicSession} could not be delivered") + log.warn("Message to ${transfer.message.topic} could not be delivered") database.transaction { pendingRedelivery.add(transfer) } @@ -438,8 +438,8 @@ class InMemoryMessagingNetwork internal constructor( } private fun MessageTransfer.toReceivedMessage(): ReceivedMessage = InMemoryReceivedMessage( - message.topicSession, - message.data.copyOf(), // Kryo messes with the buffer so give each client a unique copy + message.topic, + OpaqueBytes(message.data.bytes.copyOf()), // Kryo messes with the buffer so give each client a unique copy 1, message.uniqueMessageId, message.debugTimestamp, From 70399eb2ac45e6604a77d69d3f6c8bccb2af77df Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Fri, 9 Feb 2018 15:20:06 +0000 Subject: [PATCH 034/114] API changes --- .ci/api-current.txt | 82 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 5ae28f1fe9..3e86538b36 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1558,6 +1558,7 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec @net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.AllPossibleRecipients extends net.corda.core.messaging.MessageRecipients ## @net.corda.core.DoNotImplement public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.messaging.RPCOps + public abstract void acceptNewNetworkParameters(net.corda.core.crypto.SecureHash) public abstract void addVaultTransactionNote(net.corda.core.crypto.SecureHash, String) public abstract boolean attachmentExists(net.corda.core.crypto.SecureHash) public abstract void clearNetworkMapCache() @@ -1568,6 +1569,7 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec @kotlin.Deprecated @org.jetbrains.annotations.NotNull public abstract List internalVerifiedTransactionsSnapshot() @net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed networkMapFeed() @org.jetbrains.annotations.NotNull public abstract List networkMapSnapshot() + @net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed networkParametersFeed() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo nodeInfo() @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo nodeInfoFromParty(net.corda.core.identity.AbstractParty) @org.jetbrains.annotations.NotNull public abstract List notaryIdentities() @@ -1658,6 +1660,21 @@ public final class net.corda.core.messaging.CordaRPCOpsKt extends java.lang.Obje ## @net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.MessageRecipients ## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.ParametersUpdateInfo extends java.lang.Object + public (net.corda.core.crypto.SecureHash, net.corda.core.node.NetworkParameters, String, java.time.Instant) + @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters component2() + @org.jetbrains.annotations.NotNull public final String component3() + @org.jetbrains.annotations.NotNull public final java.time.Instant component4() + @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.ParametersUpdateInfo copy(net.corda.core.crypto.SecureHash, net.corda.core.node.NetworkParameters, String, java.time.Instant) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final String getDescription() + @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters getParameters() + @org.jetbrains.annotations.NotNull public final java.time.Instant getUpdateDeadline() + public int hashCode() + public String toString() +## @net.corda.core.DoNotImplement public interface net.corda.core.messaging.RPCOps public abstract int getProtocolVersion() ## @@ -1723,6 +1740,25 @@ public @interface net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic) ## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object + public (int, List, int, int, java.time.Instant, int) + public final int component1() + @org.jetbrains.annotations.NotNull public final List component2() + public final int component3() + public final int component4() + @org.jetbrains.annotations.NotNull public final java.time.Instant component5() + public final int component6() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, int, int, java.time.Instant, int) + public boolean equals(Object) + public final int getEpoch() + public final int getMaxMessageSize() + public final int getMaxTransactionSize() + public final int getMinimumPlatformVersion() + @org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime() + @org.jetbrains.annotations.NotNull public final List getNotaries() + public int hashCode() + public String toString() +## @net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object public (List, List, int, long) @org.jetbrains.annotations.NotNull public final List component1() @@ -1742,6 +1778,17 @@ public @interface net.corda.core.messaging.RPCReturnsObservables public final boolean isLegalIdentity(net.corda.core.identity.Party) public String toString() ## +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NotaryInfo extends java.lang.Object + public (net.corda.core.identity.Party, boolean) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() + public final boolean component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.node.NotaryInfo copy(net.corda.core.identity.Party, boolean) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getIdentity() + public final boolean getValidating() + public int hashCode() + public String toString() +## @net.corda.core.DoNotImplement public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) @@ -3148,6 +3195,7 @@ public final class net.corda.core.utilities.ByteArrays extends java.lang.Object @net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Object implements java.lang.Comparable public int compareTo(net.corda.core.utilities.ByteSequence) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence copy() + @org.jetbrains.annotations.NotNull public final byte[] copyBytes() public boolean equals(Object) @org.jetbrains.annotations.NotNull public abstract byte[] getBytes() public final int getOffset() @@ -3157,9 +3205,12 @@ public final class net.corda.core.utilities.ByteArrays extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.ByteSequence of(byte[], int) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.ByteSequence of(byte[], int, int) @org.jetbrains.annotations.NotNull public final java.io.ByteArrayInputStream open() + @org.jetbrains.annotations.NotNull public final java.nio.ByteBuffer putTo(java.nio.ByteBuffer) + @org.jetbrains.annotations.NotNull public final java.nio.ByteBuffer slice(int, int) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence subSequence(int, int) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence take(int) @org.jetbrains.annotations.NotNull public String toString() + public final void writeTo(java.io.OutputStream) public static final net.corda.core.utilities.ByteSequence$Companion Companion ## public static final class net.corda.core.utilities.ByteSequence$Companion extends java.lang.Object @@ -3796,20 +3847,25 @@ public static final class net.corda.testing.node.ClusterSpec$Raft extends net.co public static final class net.corda.testing.node.InMemoryMessagingNetwork$Companion extends java.lang.Object ## public static final class net.corda.testing.node.InMemoryMessagingNetwork$InMemoryMessage extends java.lang.Object implements net.corda.node.services.messaging.Message - public (net.corda.node.services.messaging.TopicSession, byte[], UUID, java.time.Instant) - @org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.TopicSession component1() - @org.jetbrains.annotations.NotNull public final byte[] component2() - @org.jetbrains.annotations.NotNull public final UUID component3() + public (String, net.corda.core.utilities.ByteSequence, String, java.time.Instant) + @org.jetbrains.annotations.NotNull public final String component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence component2() + @org.jetbrains.annotations.NotNull public final String component3() @org.jetbrains.annotations.NotNull public final java.time.Instant component4() - @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$InMemoryMessage copy(net.corda.node.services.messaging.TopicSession, byte[], UUID, java.time.Instant) + @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$InMemoryMessage copy(String, net.corda.core.utilities.ByteSequence, String, java.time.Instant) public boolean equals(Object) - @org.jetbrains.annotations.NotNull public byte[] getData() + @org.jetbrains.annotations.NotNull public net.corda.core.utilities.ByteSequence getData() @org.jetbrains.annotations.NotNull public java.time.Instant getDebugTimestamp() - @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.TopicSession getTopicSession() - @org.jetbrains.annotations.NotNull public UUID getUniqueMessageId() + @org.jetbrains.annotations.NotNull public String getTopic() + @org.jetbrains.annotations.NotNull public String getUniqueMessageId() public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## +public final class net.corda.testing.node.InMemoryMessagingNetwork$InMemoryMessaging$Handler extends java.lang.Object implements net.corda.node.services.messaging.MessageHandlerRegistration + public (net.corda.testing.node.InMemoryMessagingNetwork$InMemoryMessaging, String, kotlin.jvm.functions.Function2) + @org.jetbrains.annotations.NotNull public final kotlin.jvm.functions.Function2 getCallback() + @org.jetbrains.annotations.NotNull public final String getTopicSession() +## public static interface net.corda.testing.node.InMemoryMessagingNetwork$LatencyCalculator @org.jetbrains.annotations.NotNull public abstract java.time.Duration between(net.corda.core.messaging.SingleMessageRecipient, net.corda.core.messaging.SingleMessageRecipient) ## @@ -3869,16 +3925,15 @@ public static final class net.corda.testing.node.InMemoryMessagingNetwork$pumpSe ## public class net.corda.testing.node.MessagingServiceSpy extends java.lang.Object implements net.corda.node.services.messaging.MessagingService public (net.corda.node.services.messaging.MessagingService) - @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.MessageHandlerRegistration addMessageHandler(String, long, kotlin.jvm.functions.Function2) - @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.MessageHandlerRegistration addMessageHandler(net.corda.node.services.messaging.TopicSession, kotlin.jvm.functions.Function2) + @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.MessageHandlerRegistration addMessageHandler(String, kotlin.jvm.functions.Function2) public void cancelRedelivery(long) - @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.Message createMessage(net.corda.node.services.messaging.TopicSession, byte[], UUID) + @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.Message createMessage(String, byte[], String) @org.jetbrains.annotations.NotNull public net.corda.core.messaging.MessageRecipients getAddressOfParty(net.corda.core.node.services.PartyInfo) @org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.MessagingService getMessagingService() @org.jetbrains.annotations.NotNull public net.corda.core.messaging.SingleMessageRecipient getMyAddress() public void removeMessageHandler(net.corda.node.services.messaging.MessageHandlerRegistration) - public void send(List, kotlin.jvm.functions.Function0) - public void send(net.corda.node.services.messaging.Message, net.corda.core.messaging.MessageRecipients, Long, Object, kotlin.jvm.functions.Function0) + @co.paralleluniverse.fibers.Suspendable public void send(List) + @co.paralleluniverse.fibers.Suspendable public void send(net.corda.node.services.messaging.Message, net.corda.core.messaging.MessageRecipients, Long, Object) ## public final class net.corda.testing.node.MockKeyManagementService extends net.corda.core.serialization.SingletonSerializeAsToken implements net.corda.core.node.services.KeyManagementService @org.jetbrains.annotations.NotNull public Iterable filterMyKeys(Iterable) @@ -4019,6 +4074,7 @@ public final class net.corda.testing.node.MockNodeKt extends java.lang.Object public long getAttachmentContentCacheSizeBytes() @org.jetbrains.annotations.NotNull public java.nio.file.Path getCertificatesDirectory() public boolean getDetectPublicIp() + public boolean getNoLocalShell() @org.jetbrains.annotations.NotNull public java.nio.file.Path getNodeKeystore() @org.jetbrains.annotations.NotNull public java.nio.file.Path getSslKeystore() public long getTransactionCacheSizeBytes() From 86fb1ed852c69121f989c9eeea92cfb4c27f9d13 Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Fri, 9 Feb 2018 15:33:48 +0000 Subject: [PATCH 035/114] CORDA-967 Multi-tx signature verification (#2488) --- .../kotlin/net/corda/core/crypto/Crypto.kt | 32 +++++-- .../corda/core/crypto/PartialMerkleTree.kt | 11 +-- .../net/corda/core/crypto/SignableData.kt | 4 +- .../corda/core/crypto/TransactionSignature.kt | 15 +++- .../corda/core/node/services/NotaryService.kt | 4 + .../core/crypto/TransactionSignatureTest.kt | 87 ++++++++++++++++++- 6 files changed, 136 insertions(+), 17 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index 98c150b5b8..cac653fbc6 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -55,7 +55,7 @@ import javax.crypto.spec.SecretKeySpec * However, only the schemes returned by {@link #listSupportedSignatureSchemes()} are supported. * Note that Corda currently supports the following signature schemes by their code names: *

Trade Id
{{deal.ref}} {{renderX500Name(deal.fixedLeg.fixedRatePayer)}} {{deal.fixedLeg.notional}}