CORDA-2147 Use serialization strict mode during transaction verification. (#4312)

* CORDA-2147 Use serialization strict mode during transaction verification.

* CORDA-2147 Address code review comments.

* CORDA-2147 Fix compilation error.
This commit is contained in:
Tudor Malene 2018-11-30 09:44:41 +00:00 committed by GitHub
parent 559932a581
commit 66e097b58d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 59 additions and 66 deletions

View File

@ -9,7 +9,6 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.PLATFORM_VERSION import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.messaging.ClientRpcSslOptions import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.days import net.corda.core.utilities.days
@ -17,6 +16,7 @@ import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport import net.corda.nodeapi.internal.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import java.time.Duration import java.time.Duration
@ -296,7 +296,7 @@ class CordaRPCClient private constructor(
effectiveSerializationEnv effectiveSerializationEnv
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
try { try {
AMQPClientSerializationScheme.initialiseSerialization(classLoader, Caffeine.newBuilder().maximumSize(128).build<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>().asMap()) AMQPClientSerializationScheme.initialiseSerialization(classLoader, Caffeine.newBuilder().maximumSize(128).build<SerializationFactoryCacheKey, SerializerFactory>().asMap())
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
// Race e.g. two of these constructed in parallel, ignore. // Race e.g. two of these constructed in parallel, ignore.
} }

View File

@ -2,7 +2,6 @@ package net.corda.client.rpc.internal.serialization.amqp
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.toSynchronised import net.corda.core.internal.toSynchronised
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase import net.corda.core.serialization.SerializationContext.UseCase
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
@ -18,21 +17,21 @@ import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
*/ */
class AMQPClientSerializationScheme( class AMQPClientSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>, cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>(128).toSynchronised()) constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts) constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts)
@Suppress("UNUSED") @Suppress("UNUSED")
constructor() : this(emptySet(), AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>(128).toSynchronised()) constructor() : this(emptySet(), AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
companion object { companion object {
/** Call from main only. */ /** Call from main only. */
fun initialiseSerialization(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> = AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>(128).toSynchronised()) { fun initialiseSerialization(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised()) {
nodeSerializationEnv = createSerializationEnv(classLoader, serializerFactoriesForContexts) nodeSerializationEnv = createSerializationEnv(classLoader, serializerFactoriesForContexts)
} }
fun createSerializationEnv(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> = AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>(128).toSynchronised()): SerializationEnvironment { fun createSerializationEnv(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised()): SerializationEnvironment {
return SerializationEnvironment.with( return SerializationEnvironment.with(
SerializationFactoryImpl().apply { SerializationFactoryImpl().apply {
registerScheme(AMQPClientSerializationScheme(emptyList(), serializerFactoriesForContexts)) registerScheme(AMQPClientSerializationScheme(emptyList(), serializerFactoriesForContexts))

View File

@ -7,10 +7,7 @@ import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.serialization.internal.* import net.corda.serialization.internal.*
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.*
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.amqpMagic
import org.junit.rules.TestRule import org.junit.rules.TestRule
import org.junit.runner.Description import org.junit.runner.Description
import org.junit.runners.model.Statement import org.junit.runners.model.Statement
@ -67,7 +64,7 @@ class LocalSerializationRule(private val label: String) : TestRule {
private class AMQPSerializationScheme( private class AMQPSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>, cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> serializerFactoriesForContexts: AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory { override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException() throw UnsupportedOperationException()

View File

@ -150,6 +150,12 @@ interface SerializationContext {
* The default is false. * The default is false.
*/ */
val lenientCarpenterEnabled: Boolean val lenientCarpenterEnabled: Boolean
/**
* If true the carpenter will fail if the binary to be deserialized contains more fields then the current object from the classpath.
*
* The default is false.
*/
val preventDataLoss: Boolean
/** /**
* The use case we are serializing or deserializing for. See [UseCase]. * The use case we are serializing or deserializing for. See [UseCase].
*/ */
@ -171,6 +177,12 @@ interface SerializationContext {
*/ */
fun withLenientCarpenter(): SerializationContext fun withLenientCarpenter(): SerializationContext
/**
* Return a new context based on this one but with a strict evolution.
* @see preventDataLoss
*/
fun withPreventDataLoss(): SerializationContext
/** /**
* Helper method to return a new context based on this context with the deserialization class loader changed. * Helper method to return a new context based on this context with the deserialization class loader changed.
*/ */

View File

@ -123,7 +123,7 @@ internal object AttachmentsClassLoaderBuilder {
val transactionClassLoader = AttachmentsClassLoaderBuilder.build(attachments) val transactionClassLoader = AttachmentsClassLoaderBuilder.build(attachments)
// Create a new serializationContext for the current Transaction. // Create a new serializationContext for the current Transaction.
val transactionSerializationContext = SerializationFactory.defaultFactory.defaultContext.withClassLoader(transactionClassLoader) val transactionSerializationContext = SerializationFactory.defaultFactory.defaultContext.withPreventDataLoss().withClassLoader(transactionClassLoader)
// Deserialize all relevant classes in the transaction classloader. // Deserialize all relevant classes in the transaction classloader.
return SerializationFactory.defaultFactory.withCurrentContext(transactionSerializationContext) { return SerializationFactory.defaultFactory.withCurrentContext(transactionSerializationContext) {

View File

@ -23,7 +23,6 @@ import net.corda.core.messaging.RPCOps
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
@ -56,6 +55,7 @@ import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.serialization.internal.* import net.corda.serialization.internal.*
import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import org.apache.commons.lang.SystemUtils import org.apache.commons.lang.SystemUtils
import org.h2.jdbc.JdbcSQLException import org.h2.jdbc.JdbcSQLException
@ -474,8 +474,8 @@ open class Node(configuration: NodeConfiguration,
val classloader = cordappLoader.appClassLoader val classloader = cordappLoader.appClassLoader
nodeSerializationEnv = SerializationEnvironment.with( nodeSerializationEnv = SerializationEnvironment.with(
SerializationFactoryImpl().apply { SerializationFactoryImpl().apply {
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps, Caffeine.newBuilder().maximumSize(128).build<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>().asMap())) registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps, Caffeine.newBuilder().maximumSize(128).build<SerializationFactoryCacheKey, SerializerFactory>().asMap()))
registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps, Caffeine.newBuilder().maximumSize(128).build<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>().asMap())) registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps, Caffeine.newBuilder().maximumSize(128).build<SerializationFactoryCacheKey, SerializerFactory>().asMap()))
}, },
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader), p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
rpcServerContext = AMQP_RPC_SERVER_CONTEXT.withClassLoader(classloader), rpcServerContext = AMQP_RPC_SERVER_CONTEXT.withClassLoader(classloader),

View File

@ -2,14 +2,10 @@ package net.corda.node.serialization.amqp
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.toSynchronised import net.corda.core.internal.toSynchronised
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.serialization.internal.CordaSerializationMagic import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.*
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.SerializerFactoryBuilder
import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
/** /**
@ -18,12 +14,12 @@ import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
*/ */
class AMQPServerSerializationScheme( class AMQPServerSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>, cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>(128).toSynchronised()) constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts) constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts)
constructor() : this(emptySet(), AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>(128).toSynchronised() ) constructor() : this(emptySet(), AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised() )
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException() throw UnsupportedOperationException()

View File

@ -9,16 +9,12 @@ import net.corda.client.rpc.internal.serialization.amqp.RpcClientObservableDeSer
import net.corda.core.context.Trace import net.corda.core.context.Trace
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.internal.toSynchronised import net.corda.core.internal.toSynchronised
import net.corda.core.serialization.ClassWhitelist
import net.corda.node.internal.serialization.testutils.AMQPRoundTripRPCSerializationScheme import net.corda.node.internal.serialization.testutils.AMQPRoundTripRPCSerializationScheme
import net.corda.node.internal.serialization.testutils.serializationContext import net.corda.node.internal.serialization.testutils.serializationContext
import net.corda.node.serialization.amqp.RpcServerObservableSerializer import net.corda.node.serialization.amqp.RpcServerObservableSerializer
import net.corda.node.services.messaging.ObservableSubscription import net.corda.node.services.messaging.ObservableSubscription
import net.corda.nodeapi.RPCApi import net.corda.nodeapi.RPCApi
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap import net.corda.serialization.internal.amqp.*
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.SerializationOutput
import net.corda.serialization.internal.amqp.SerializerFactory
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import org.junit.Test import org.junit.Test
import rx.Notification import rx.Notification
@ -63,7 +59,7 @@ class RoundTripObservableSerializerTests {
@Test @Test
fun roundTripTest1() { fun roundTripTest1() {
val serializationScheme = AMQPRoundTripRPCSerializationScheme( val serializationScheme = AMQPRoundTripRPCSerializationScheme(
serializationContext, emptySet(), AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>(128).toSynchronised()) serializationContext, emptySet(), AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
// Fake up a message ID, needs to be used on both "sides". The server setting it in the subscriptionMap, // Fake up a message ID, needs to be used on both "sides". The server setting it in the subscriptionMap,
// the client as a property of the deserializer which, in the actual RPC client, is pulled off of // the client as a property of the deserializer which, in the actual RPC client, is pulled off of

View File

@ -2,17 +2,13 @@ package net.corda.node.internal.serialization.testutils
import net.corda.client.rpc.internal.serialization.amqp.RpcClientObservableDeSerializer import net.corda.client.rpc.internal.serialization.amqp.RpcClientObservableDeSerializer
import net.corda.core.context.Trace import net.corda.core.context.Trace
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.node.serialization.amqp.RpcServerObservableSerializer import net.corda.node.serialization.amqp.RpcServerObservableSerializer
import net.corda.nodeapi.RPCApi import net.corda.nodeapi.RPCApi
import net.corda.serialization.internal.CordaSerializationMagic import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.AllWhitelist import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap import net.corda.serialization.internal.amqp.*
import net.corda.serialization.internal.amqp.SerializerFactoryBuilder
import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext
/** /**
@ -24,7 +20,7 @@ import net.corda.client.rpc.internal.ObservableContext as ClientObservableContex
class AMQPRoundTripRPCSerializationScheme( class AMQPRoundTripRPCSerializationScheme(
private val serializationContext: SerializationContext, private val serializationContext: SerializationContext,
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>, cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>) serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>)
: AbstractAMQPSerializationScheme( : AbstractAMQPSerializationScheme(
cordappCustomSerializers, serializerFactoriesForContexts cordappCustomSerializers, serializerFactoriesForContexts
) { ) {

View File

@ -1,23 +1,16 @@
package net.corda.serialization.internal package net.corda.serialization.internal
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.core.DeleteForDJVM import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.copyBytes import net.corda.core.internal.copyBytes
import net.corda.core.serialization.* import net.corda.core.serialization.*
import net.corda.core.serialization.internal.AttachmentsClassLoader
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.serialization.internal.amqp.amqpMagic import net.corda.serialization.internal.amqp.amqpMagic
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.NotSerializableException import java.io.NotSerializableException
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutionException
const val attachmentsClassLoaderEnabledPropertyName = "attachments.class.loader.enabled"
internal object NullEncodingWhitelist : EncodingWhitelist { internal object NullEncodingWhitelist : EncodingWhitelist {
override fun acceptEncoding(encoding: SerializationEncoding) = false override fun acceptEncoding(encoding: SerializationEncoding) = false
@ -32,7 +25,8 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
override val useCase: SerializationContext.UseCase, override val useCase: SerializationContext.UseCase,
override val encoding: SerializationEncoding?, override val encoding: SerializationEncoding?,
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist, override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist,
override val lenientCarpenterEnabled: Boolean = false) : SerializationContext { override val lenientCarpenterEnabled: Boolean = false,
override val preventDataLoss: Boolean = false) : SerializationContext {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -50,6 +44,8 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
override fun withLenientCarpenter(): SerializationContext = copy(lenientCarpenterEnabled = true) override fun withLenientCarpenter(): SerializationContext = copy(lenientCarpenterEnabled = true)
override fun withPreventDataLoss(): SerializationContext = copy(preventDataLoss = true)
override fun withClassLoader(classLoader: ClassLoader): SerializationContext { override fun withClassLoader(classLoader: ClassLoader): SerializationContext {
return copy(deserializationClassLoader = classLoader) return copy(deserializationClassLoader = classLoader)
} }
@ -67,8 +63,8 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
@KeepForDJVM @KeepForDJVM
open class SerializationFactoryImpl( open class SerializationFactoryImpl(
// TODO: This is read-mostly. Probably a faster implementation to be found. // TODO: This is read-mostly. Probably a faster implementation to be found.
private val schemes: MutableMap<Pair<CordaSerializationMagic, SerializationContext.UseCase>, SerializationScheme> private val schemes: MutableMap<Pair<CordaSerializationMagic, SerializationContext.UseCase>, SerializationScheme>
) : SerializationFactory() { ) : SerializationFactory() {
@DeleteForDJVM @DeleteForDJVM
constructor() : this(ConcurrentHashMap()) constructor() : this(ConcurrentHashMap())
@ -132,7 +128,6 @@ open class SerializationFactoryImpl(
override fun hashCode(): Int = registeredSchemes.hashCode() override fun hashCode(): Int = registeredSchemes.hashCode()
} }
@KeepForDJVM @KeepForDJVM
interface SerializationScheme { interface SerializationScheme {
fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean

View File

@ -22,6 +22,8 @@ import java.util.*
val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == amqpMagic val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == amqpMagic
data class SerializationFactoryCacheKey(val classWhitelist: ClassWhitelist, val deserializationClassLoader: ClassLoader, val preventDataLoss: Boolean)
fun SerializerFactory.addToWhitelist(vararg types: Class<*>) { fun SerializerFactory.addToWhitelist(vararg types: Class<*>) {
require(types.toSet().size == types.size) { require(types.toSet().size == types.size) {
val duplicates = types.toMutableList() val duplicates = types.toMutableList()
@ -41,14 +43,14 @@ interface SerializerFactoryFactory {
@KeepForDJVM @KeepForDJVM
abstract class AbstractAMQPSerializationScheme( abstract class AbstractAMQPSerializationScheme(
private val cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>, private val cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
maybeNotConcurrentSerializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>, maybeNotConcurrentSerializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>,
val sff: SerializerFactoryFactory = createSerializerFactoryFactory() val sff: SerializerFactoryFactory = createSerializerFactoryFactory()
) : SerializationScheme { ) : SerializationScheme {
@DeleteForDJVM @DeleteForDJVM
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>(128).toSynchronised()) constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
// This is a bit gross but a broader check for ConcurrentMap is not allowed inside DJVM. // This is a bit gross but a broader check for ConcurrentMap is not allowed inside DJVM.
private val serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> = if (maybeNotConcurrentSerializerFactoriesForContexts is AccessOrderLinkedHashMap<*, *>) { private val serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = if (maybeNotConcurrentSerializerFactoriesForContexts is AccessOrderLinkedHashMap<*, *>) {
Collections.synchronizedMap(maybeNotConcurrentSerializerFactoriesForContexts) Collections.synchronizedMap(maybeNotConcurrentSerializerFactoriesForContexts)
} else { } else {
maybeNotConcurrentSerializerFactoriesForContexts maybeNotConcurrentSerializerFactoriesForContexts
@ -174,19 +176,19 @@ abstract class AbstractAMQPSerializationScheme(
open val publicKeySerializer: CustomSerializer<*> = net.corda.serialization.internal.amqp.custom.PublicKeySerializer open val publicKeySerializer: CustomSerializer<*> = net.corda.serialization.internal.amqp.custom.PublicKeySerializer
fun getSerializerFactory(context: SerializationContext): SerializerFactory { fun getSerializerFactory(context: SerializationContext): SerializerFactory {
val key = Pair(context.whitelist, context.deserializationClassLoader) val key = SerializationFactoryCacheKey(context.whitelist, context.deserializationClassLoader, context.preventDataLoss)
// ConcurrentHashMap.get() is lock free, but computeIfAbsent is not, even if the key is in the map already. // ConcurrentHashMap.get() is lock free, but computeIfAbsent is not, even if the key is in the map already.
return serializerFactoriesForContexts[key] ?: serializerFactoriesForContexts.computeIfAbsent(key) { return serializerFactoriesForContexts[key] ?: serializerFactoriesForContexts.computeIfAbsent(key) {
when (context.useCase) { when (context.useCase) {
SerializationContext.UseCase.RPCClient -> SerializationContext.UseCase.RPCClient ->
rpcClientSerializerFactory(context) rpcClientSerializerFactory(context)
SerializationContext.UseCase.RPCServer -> SerializationContext.UseCase.RPCServer ->
rpcServerSerializerFactory(context) rpcServerSerializerFactory(context)
else -> sff.make(context) else -> sff.make(context)
}.also { }.also {
registerCustomSerializers(context, it) registerCustomSerializers(context, it)
}
} }
}
} }
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T { override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {

View File

@ -10,7 +10,8 @@ fun createSerializerFactoryFactory(): SerializerFactoryFactory = SerializerFacto
open class SerializerFactoryFactoryImpl : SerializerFactoryFactory { open class SerializerFactoryFactoryImpl : SerializerFactoryFactory {
override fun make(context: SerializationContext): SerializerFactory { override fun make(context: SerializationContext): SerializerFactory {
return SerializerFactoryBuilder.build(context.whitelist, return SerializerFactoryBuilder.build(context.whitelist,
ClassCarpenterImpl(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled) ClassCarpenterImpl(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled),
mustPreserveDataWhenEvolving = context.preventDataLoss
) )
} }
} }

View File

@ -1,7 +1,6 @@
package net.corda.serialization.internal.amqp package net.corda.serialization.internal.amqp
import net.corda.core.internal.toSynchronised import net.corda.core.internal.toSynchronised
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
@ -40,7 +39,7 @@ class AbstractAMQPSerializationSchemeTest {
val factory = SerializerFactoryBuilder.build(TESTING_CONTEXT.whitelist, TESTING_CONTEXT.deserializationClassLoader) val factory = SerializerFactoryBuilder.build(TESTING_CONTEXT.whitelist, TESTING_CONTEXT.deserializationClassLoader)
val maxFactories = 512 val maxFactories = 512
val backingMap = AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>({ maxFactories }).toSynchronised() val backingMap = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>({ maxFactories }).toSynchronised()
val scheme = object : AbstractAMQPSerializationScheme(emptySet(), backingMap, createSerializerFactoryFactory()) { val scheme = object : AbstractAMQPSerializationScheme(emptySet(), backingMap, createSerializerFactoryFactory()) {
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
return factory return factory