mirror of
https://github.com/corda/corda.git
synced 2025-06-15 21:58:17 +00:00
[CORDA-2390] - Add whitelists and custom serializers from cordapps to serialization … (#4551)
* Add whitelists and custom serializers from cordapps to serialization context * Remove changes in TransactionBuilder, add caching * Add whitelists and custom serializers from cordapps to serialization context * Remove changes in TransactionBuilder, add caching * Address comments * Increase node memory for SIMM integration test * Cache only serialization context * Increase integ test timeout * Fix API breakage * Increase max heap size for web server integ test * Move classloading utils from separate module to core.internal * Adjust heap size for more integ tests * Increase time window for IRS demo transactions * Fix determinator * Add parameter in core-deterministic * Stub out class-loading method for DJVM
This commit is contained in:
committed by
Gavin Thomas
parent
36cd9b9791
commit
5b34020e59
@ -3,12 +3,15 @@ package net.corda.client.rpc
|
|||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import net.corda.client.rpc.internal.RPCClient
|
import net.corda.client.rpc.internal.RPCClient
|
||||||
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
|
import net.corda.core.internal.loadClassesImplementing
|
||||||
import net.corda.core.context.Actor
|
import net.corda.core.context.Actor
|
||||||
import net.corda.core.context.Trace
|
import net.corda.core.context.Trace
|
||||||
import net.corda.core.identity.CordaX500Name
|
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.SerializationCustomSerializer
|
||||||
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
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
|
||||||
@ -19,6 +22,7 @@ import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
|||||||
import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey
|
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
|
||||||
|
import java.util.ServiceLoader
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is essentially just a wrapper for an RPCConnection<CordaRPCOps> and can be treated identically.
|
* This class is essentially just a wrapper for an RPCConnection<CordaRPCOps> and can be treated identically.
|
||||||
@ -240,6 +244,7 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
|
|||||||
* @param configuration An optional configuration used to tweak client behaviour.
|
* @param configuration An optional configuration used to tweak client behaviour.
|
||||||
* @param sslConfiguration An optional [ClientRpcSslOptions] used to enable secure communication with the server.
|
* @param sslConfiguration An optional [ClientRpcSslOptions] used to enable secure communication with the server.
|
||||||
* @param haAddressPool A list of [NetworkHostAndPort] representing the addresses of servers in HA mode.
|
* @param haAddressPool A list of [NetworkHostAndPort] representing the addresses of servers in HA mode.
|
||||||
|
* @param classLoader a classloader, which will be used (if provided) to discover available [SerializationCustomSerializer]s and [SerializationWhitelist]s
|
||||||
* The client will attempt to connect to a live server by trying each address in the list. If the servers are not in
|
* The client will attempt to connect to a live server by trying each address in the list. If the servers are not in
|
||||||
* HA mode, the client will round-robin from the beginning of the list and try all servers.
|
* HA mode, the client will round-robin from the beginning of the list and try all servers.
|
||||||
*/
|
*/
|
||||||
@ -252,8 +257,9 @@ class CordaRPCClient private constructor(
|
|||||||
) {
|
) {
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(hostAndPort: NetworkHostAndPort,
|
constructor(hostAndPort: NetworkHostAndPort,
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT)
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
|
||||||
: this(hostAndPort, configuration, null)
|
classLoader: ClassLoader? = null)
|
||||||
|
: this(hostAndPort, configuration, null, classLoader = classLoader)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param haAddressPool A list of [NetworkHostAndPort] representing the addresses of servers in HA mode.
|
* @param haAddressPool A list of [NetworkHostAndPort] representing the addresses of servers in HA mode.
|
||||||
@ -287,7 +293,7 @@ class CordaRPCClient private constructor(
|
|||||||
sslConfiguration: ClientRpcSslOptions? = null,
|
sslConfiguration: ClientRpcSslOptions? = null,
|
||||||
classLoader: ClassLoader? = null
|
classLoader: ClassLoader? = null
|
||||||
): CordaRPCClient {
|
): CordaRPCClient {
|
||||||
return CordaRPCClient(hostAndPort, configuration, sslConfiguration, classLoader)
|
return CordaRPCClient(hostAndPort, configuration, sslConfiguration, classLoader = classLoader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,7 +302,14 @@ class CordaRPCClient private constructor(
|
|||||||
effectiveSerializationEnv
|
effectiveSerializationEnv
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
try {
|
try {
|
||||||
AMQPClientSerializationScheme.initialiseSerialization(classLoader, Caffeine.newBuilder().maximumSize(128).build<SerializationFactoryCacheKey, SerializerFactory>().asMap())
|
// If the client has provided a classloader, the associated classpath is checked for available custom serializers and serialization whitelists.
|
||||||
|
if (classLoader != null) {
|
||||||
|
val customSerializers = loadClassesImplementing(classLoader, SerializationCustomSerializer::class.java)
|
||||||
|
val serializationWhitelists = ServiceLoader.load(SerializationWhitelist::class.java, classLoader).toSet()
|
||||||
|
AMQPClientSerializationScheme.initialiseSerialization(classLoader, customSerializers, serializationWhitelists, Caffeine.newBuilder().maximumSize(128).build<SerializationFactoryCacheKey, SerializerFactory>().asMap())
|
||||||
|
} else {
|
||||||
|
AMQPClientSerializationScheme.initialiseSerialization(classLoader, serializerFactoriesForContexts = 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.
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import net.corda.core.internal.toSynchronised
|
|||||||
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
|
||||||
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
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.serialization.internal.*
|
import net.corda.serialization.internal.*
|
||||||
@ -17,24 +18,25 @@ import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
|
|||||||
*/
|
*/
|
||||||
class AMQPClientSerializationScheme(
|
class AMQPClientSerializationScheme(
|
||||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
|
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
|
||||||
|
cordappSerializationWhitelists: Set<SerializationWhitelist>,
|
||||||
serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>
|
serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>
|
||||||
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, cordappSerializationWhitelists, serializerFactoriesForContexts) {
|
||||||
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
|
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, cordapps.serializationWhitelists, AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
|
||||||
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts)
|
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>) : this(cordapps.customSerializers, cordapps.serializationWhitelists, serializerFactoriesForContexts)
|
||||||
|
|
||||||
@Suppress("UNUSED")
|
@Suppress("UNUSED")
|
||||||
constructor() : this(emptySet(), AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
|
constructor() : this(emptySet(), 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<SerializationFactoryCacheKey, SerializerFactory> = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised()) {
|
fun initialiseSerialization(classLoader: ClassLoader? = null, customSerializers: Set<SerializationCustomSerializer<*, *>> = emptySet(), serializationWhitelists: Set<SerializationWhitelist> = emptySet(), serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised()) {
|
||||||
nodeSerializationEnv = createSerializationEnv(classLoader, serializerFactoriesForContexts)
|
nodeSerializationEnv = createSerializationEnv(classLoader, customSerializers, serializationWhitelists, serializerFactoriesForContexts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSerializationEnv(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised()): SerializationEnvironment {
|
fun createSerializationEnv(classLoader: ClassLoader? = null, customSerializers: Set<SerializationCustomSerializer<*, *>> = emptySet(), serializationWhitelists: Set<SerializationWhitelist> = emptySet(), 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(customSerializers, serializationWhitelists, serializerFactoriesForContexts))
|
||||||
},
|
},
|
||||||
storageContext = AMQP_STORAGE_CONTEXT,
|
storageContext = AMQP_STORAGE_CONTEXT,
|
||||||
p2pContext = if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT,
|
p2pContext = if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT,
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stubbing out non-deterministic method.
|
||||||
|
*/
|
||||||
|
fun <T: Any> loadClassesImplementing(classloader: ClassLoader, clazz: Class<T>): Set<T> {
|
||||||
|
return emptySet()
|
||||||
|
}
|
@ -4,6 +4,7 @@ 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.P2P
|
import net.corda.core.serialization.SerializationContext.UseCase.P2P
|
||||||
import net.corda.core.serialization.SerializationCustomSerializer
|
import net.corda.core.serialization.SerializationCustomSerializer
|
||||||
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
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.*
|
||||||
@ -57,15 +58,16 @@ class LocalSerializationRule(private val label: String) : TestRule {
|
|||||||
|
|
||||||
private fun createTestSerializationEnv(): SerializationEnvironment {
|
private fun createTestSerializationEnv(): SerializationEnvironment {
|
||||||
val factory = SerializationFactoryImpl(mutableMapOf()).apply {
|
val factory = SerializationFactoryImpl(mutableMapOf()).apply {
|
||||||
registerScheme(AMQPSerializationScheme(emptySet(), AccessOrderLinkedHashMap(128)))
|
registerScheme(AMQPSerializationScheme(emptySet(), emptySet(), AccessOrderLinkedHashMap(128)))
|
||||||
}
|
}
|
||||||
return SerializationEnvironment.with(factory, AMQP_P2P_CONTEXT)
|
return SerializationEnvironment.with(factory, AMQP_P2P_CONTEXT)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AMQPSerializationScheme(
|
private class AMQPSerializationScheme(
|
||||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||||
|
cordappSerializationWhitelists: Set<SerializationWhitelist>,
|
||||||
serializerFactoriesForContexts: AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>
|
serializerFactoriesForContexts: AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>
|
||||||
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, cordappSerializationWhitelists, serializerFactoriesForContexts) {
|
||||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
throw UnsupportedOperationException()
|
throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,8 @@ dependencies {
|
|||||||
|
|
||||||
// required to use @Type annotation
|
// required to use @Type annotation
|
||||||
compile "org.hibernate:hibernate-core:$hibernate_version"
|
compile "org.hibernate:hibernate-core:$hibernate_version"
|
||||||
|
|
||||||
|
compile group: "io.github.classgraph", name: "classgraph", version: class_graph_version
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Consider moving it to quasar-utils in the future (introduced with PR-1388)
|
// TODO Consider moving it to quasar-utils in the future (introduced with PR-1388)
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import io.github.classgraph.ClassGraph
|
||||||
|
import net.corda.core.CordaInternal
|
||||||
|
import net.corda.core.DeleteForDJVM
|
||||||
|
import net.corda.core.StubOutForDJVM
|
||||||
|
import kotlin.reflect.full.createInstance
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates instances of all the classes in the classpath of the provided classloader, which implement the interface of the provided class.
|
||||||
|
* @param classloader the classloader, which will be searched for the classes.
|
||||||
|
* @param clazz the class of the interface, which the classes - to be returned - must implement.
|
||||||
|
*
|
||||||
|
* @return instances of the identified classes.
|
||||||
|
* @throws IllegalArgumentException if the classes found do not have proper constructors.
|
||||||
|
*
|
||||||
|
* Note: In order to be instantiated, the associated classes must:
|
||||||
|
* - be non-abstract
|
||||||
|
* - either be a Kotlin object or have a constructor with no parameters (or only optional ones)
|
||||||
|
*/
|
||||||
|
@StubOutForDJVM
|
||||||
|
fun <T: Any> loadClassesImplementing(classloader: ClassLoader, clazz: Class<T>): Set<T> {
|
||||||
|
return ClassGraph().addClassLoader(classloader)
|
||||||
|
.enableAllInfo()
|
||||||
|
.scan()
|
||||||
|
.getClassesImplementing(clazz.name)
|
||||||
|
.filterNot { it.isAbstract }
|
||||||
|
.mapNotNull { classloader.loadClass(it.name).asSubclass(clazz) }
|
||||||
|
.map { it.kotlin.objectInstance ?: it.kotlin.createInstance() }
|
||||||
|
.toSet()
|
||||||
|
}
|
@ -19,12 +19,16 @@ import net.corda.core.node.services.vault.Builder
|
|||||||
import net.corda.core.node.services.vault.Sort
|
import net.corda.core.node.services.vault.Sort
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.slf4j.MDC
|
import org.slf4j.MDC
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
import java.util.jar.JarEntry
|
||||||
import java.util.jar.JarInputStream
|
import java.util.jar.JarInputStream
|
||||||
|
|
||||||
// *Internal* Corda-specific utilities.
|
// *Internal* Corda-specific utilities.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
@file:KeepForDJVM
|
@file:KeepForDJVM
|
||||||
package net.corda.core.serialization
|
package net.corda.core.serialization
|
||||||
|
|
||||||
|
import co.paralleluniverse.io.serialization.Serialization
|
||||||
import net.corda.core.CordaInternal
|
import net.corda.core.CordaInternal
|
||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
@ -160,6 +161,10 @@ interface SerializationContext {
|
|||||||
* The use case we are serializing or deserializing for. See [UseCase].
|
* The use case we are serializing or deserializing for. See [UseCase].
|
||||||
*/
|
*/
|
||||||
val useCase: UseCase
|
val useCase: UseCase
|
||||||
|
/**
|
||||||
|
* Additional custom serializers that will be made available during (de)serialization.
|
||||||
|
*/
|
||||||
|
val customSerializers: Set<SerializationCustomSerializer<*, *>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to return a new context based on this context with the property added.
|
* Helper method to return a new context based on this context with the property added.
|
||||||
@ -200,6 +205,11 @@ interface SerializationContext {
|
|||||||
*/
|
*/
|
||||||
fun withWhitelisted(clazz: Class<*>): SerializationContext
|
fun withWhitelisted(clazz: Class<*>): SerializationContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to return a new context based on this context with the given serializers added.
|
||||||
|
*/
|
||||||
|
fun withCustomSerializers(serializers: Set<SerializationCustomSerializer<*, *>>): SerializationContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to return a new context based on this context but with serialization using the format this header sequence represents.
|
* Helper method to return a new context based on this context but with serialization using the format this header sequence represents.
|
||||||
*/
|
*/
|
||||||
@ -335,3 +345,15 @@ interface ClassWhitelist {
|
|||||||
interface EncodingWhitelist {
|
interface EncodingWhitelist {
|
||||||
fun acceptEncoding(encoding: SerializationEncoding): Boolean
|
fun acceptEncoding(encoding: SerializationEncoding): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to return a new context based on this context with the given list of classes specifically whitelisted.
|
||||||
|
*/
|
||||||
|
fun SerializationContext.withWhitelist(classes: List<Class<*>>): SerializationContext {
|
||||||
|
var currentContext = this
|
||||||
|
classes.forEach {
|
||||||
|
clazz -> currentContext = currentContext.withWhitelisted(clazz)
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentContext
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.core.serialization.internal
|
|||||||
|
|
||||||
import net.corda.core.CordaException
|
import net.corda.core.CordaException
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
|
import net.corda.core.internal.loadClassesImplementing
|
||||||
import net.corda.core.contracts.Attachment
|
import net.corda.core.contracts.Attachment
|
||||||
import net.corda.core.contracts.ContractAttachment
|
import net.corda.core.contracts.ContractAttachment
|
||||||
import net.corda.core.contracts.TransactionVerificationException.OverlappingAttachmentsException
|
import net.corda.core.contracts.TransactionVerificationException.OverlappingAttachmentsException
|
||||||
@ -10,7 +11,11 @@ import net.corda.core.crypto.sha256
|
|||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.internal.cordapp.targetPlatformVersion
|
import net.corda.core.internal.cordapp.targetPlatformVersion
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.MissingAttachmentsException
|
||||||
|
import net.corda.core.serialization.SerializationCustomSerializer
|
||||||
import net.corda.core.serialization.SerializationFactory
|
import net.corda.core.serialization.SerializationFactory
|
||||||
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.toUrl
|
import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.toUrl
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
@ -18,6 +23,7 @@ import java.io.ByteArrayOutputStream
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.*
|
import java.net.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only
|
* A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only
|
||||||
@ -174,34 +180,38 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is just a factory that provides a cache to avoid constructing expensive [AttachmentsClassLoader]s.
|
* This is just a factory that provides caches to optimise expensive construction/loading of classloaders, serializers, whitelisted classes.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
internal object AttachmentsClassLoaderBuilder {
|
internal object AttachmentsClassLoaderBuilder {
|
||||||
|
|
||||||
private const val ATTACHMENT_CLASSLOADER_CACHE_SIZE = 1000
|
private const val CACHE_SIZE = 1000
|
||||||
|
|
||||||
// This runs in the DJVM so it can't use caffeine.
|
// This runs in the DJVM so it can't use caffeine.
|
||||||
private val cache: MutableMap<List<SecureHash>, AttachmentsClassLoader> = createSimpleCache<List<SecureHash>, AttachmentsClassLoader>(ATTACHMENT_CLASSLOADER_CACHE_SIZE)
|
private val cache: MutableMap<Set<SecureHash>, SerializationContext> = createSimpleCache(CACHE_SIZE)
|
||||||
.toSynchronised()
|
|
||||||
|
|
||||||
fun build(attachments: List<Attachment>): AttachmentsClassLoader {
|
|
||||||
return cache.computeIfAbsent(attachments.map { it.id }.sorted()) {
|
|
||||||
AttachmentsClassLoader(attachments)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> withAttachmentsClassloaderContext(attachments: List<Attachment>, block: (ClassLoader) -> T): T {
|
fun <T> withAttachmentsClassloaderContext(attachments: List<Attachment>, block: (ClassLoader) -> T): T {
|
||||||
|
val attachmentIds = attachments.map { it.id }.toSet()
|
||||||
|
|
||||||
// Create classloader from the attachments.
|
val serializationContext = cache.computeIfAbsent(attachmentIds) {
|
||||||
val transactionClassLoader = AttachmentsClassLoaderBuilder.build(attachments)
|
// Create classloader and load serializers, whitelisted classes
|
||||||
|
val transactionClassLoader = AttachmentsClassLoader(attachments)
|
||||||
|
val serializers = loadClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
|
||||||
|
val whitelistedClasses = ServiceLoader.load(SerializationWhitelist::class.java, transactionClassLoader)
|
||||||
|
.flatMap { it.whitelist }
|
||||||
|
.toList()
|
||||||
|
|
||||||
// Create a new serializationContext for the current Transaction.
|
// Create a new serializationContext for the current Transaction.
|
||||||
val transactionSerializationContext = SerializationFactory.defaultFactory.defaultContext.withPreventDataLoss().withClassLoader(transactionClassLoader)
|
SerializationFactory.defaultFactory.defaultContext
|
||||||
|
.withPreventDataLoss()
|
||||||
|
.withClassLoader(transactionClassLoader)
|
||||||
|
.withWhitelist(whitelistedClasses)
|
||||||
|
.withCustomSerializers(serializers)
|
||||||
|
}
|
||||||
|
|
||||||
// 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(serializationContext) {
|
||||||
block(transactionClassLoader)
|
block(serializationContext.deserializationClassLoader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
|
||||||
|
class ClassLoadingUtilsTest {
|
||||||
|
|
||||||
|
interface BaseInterface {}
|
||||||
|
|
||||||
|
interface BaseInterface2 {}
|
||||||
|
|
||||||
|
class ConcreteClassWithEmptyConstructor: BaseInterface {}
|
||||||
|
|
||||||
|
abstract class AbstractClass: BaseInterface
|
||||||
|
|
||||||
|
class ConcreteClassWithNonEmptyConstructor(private val someData: Int): BaseInterface2 {}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun predicateClassAreLoadedSuccessfully() {
|
||||||
|
val classes = loadClassesImplementing(BaseInterface::class.java.classLoader, BaseInterface::class.java)
|
||||||
|
|
||||||
|
val classNames = classes.map { it.javaClass.name }
|
||||||
|
|
||||||
|
assertThat(classNames).contains(ConcreteClassWithEmptyConstructor::class.java.name)
|
||||||
|
assertThat(classNames).doesNotContain(AbstractClass::class.java.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun throwsExceptionWhenClassDoesNotContainProperConstructors() {
|
||||||
|
val classes = loadClassesImplementing(BaseInterface::class.java.classLoader, BaseInterface2::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -190,7 +190,7 @@ object TwoPartyDealFlow {
|
|||||||
|
|
||||||
// We set the transaction's time-window: it may be that none of the contracts need this!
|
// We set the transaction's time-window: it may be that none of the contracts need this!
|
||||||
// But it can't hurt to have one.
|
// But it can't hurt to have one.
|
||||||
ptx.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
ptx.setTimeWindow(serviceHub.clock.instant(), 60.seconds)
|
||||||
return Triple(ptx, arrayListOf(deal.participants.single { it is Party && serviceHub.myInfo.isLegalIdentity(it) }.owningKey), emptyList())
|
return Triple(ptx, arrayListOf(deal.participants.single { it is Party && serviceHub.myInfo.isLegalIdentity(it) }.owningKey), emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ class FlowCheckpointVersionNodeStartupCheckTest {
|
|||||||
val result = if (page.snapshot.states.isNotEmpty()) {
|
val result = if (page.snapshot.states.isNotEmpty()) {
|
||||||
page.snapshot.states.first()
|
page.snapshot.states.first()
|
||||||
} else {
|
} else {
|
||||||
val r = page.updates.timeout(5, TimeUnit.SECONDS).take(1).toBlocking().single()
|
val r = page.updates.timeout(10, TimeUnit.SECONDS).take(1).toBlocking().single()
|
||||||
if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first()
|
if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first()
|
||||||
}
|
}
|
||||||
assertNotNull(result)
|
assertNotNull(result)
|
||||||
|
@ -4,6 +4,7 @@ import net.corda.core.cordapp.Cordapp
|
|||||||
import net.corda.core.internal.toSynchronised
|
import net.corda.core.internal.toSynchronised
|
||||||
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.core.serialization.SerializationWhitelist
|
||||||
import net.corda.serialization.internal.CordaSerializationMagic
|
import net.corda.serialization.internal.CordaSerializationMagic
|
||||||
import net.corda.serialization.internal.amqp.*
|
import net.corda.serialization.internal.amqp.*
|
||||||
import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
|
import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
|
||||||
@ -14,12 +15,13 @@ import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
|
|||||||
*/
|
*/
|
||||||
class AMQPServerSerializationScheme(
|
class AMQPServerSerializationScheme(
|
||||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||||
|
cordappSerializationWhitelists: Set<SerializationWhitelist>,
|
||||||
serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>
|
serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>
|
||||||
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, cordappSerializationWhitelists, serializerFactoriesForContexts) {
|
||||||
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
|
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, cordapps.serializationWhitelists, AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
|
||||||
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts)
|
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>) : this(cordapps.customSerializers, cordapps.serializationWhitelists, serializerFactoriesForContexts)
|
||||||
|
|
||||||
constructor() : this(emptySet(), AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised() )
|
constructor() : this(emptySet(), emptySet(), AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised() )
|
||||||
|
|
||||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
throw UnsupportedOperationException()
|
throw UnsupportedOperationException()
|
||||||
|
@ -59,7 +59,7 @@ class RoundTripObservableSerializerTests {
|
|||||||
@Test
|
@Test
|
||||||
fun roundTripTest1() {
|
fun roundTripTest1() {
|
||||||
val serializationScheme = AMQPRoundTripRPCSerializationScheme(
|
val serializationScheme = AMQPRoundTripRPCSerializationScheme(
|
||||||
serializationContext, emptySet(), AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
|
serializationContext, emptySet(), 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
|
||||||
|
@ -4,6 +4,7 @@ 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.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
import net.corda.core.serialization.SerializationCustomSerializer
|
import net.corda.core.serialization.SerializationCustomSerializer
|
||||||
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
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
|
||||||
@ -20,9 +21,10 @@ 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<*, *>>,
|
||||||
|
cordappSerializationWhitelists: Set<SerializationWhitelist>,
|
||||||
serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>)
|
serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>)
|
||||||
: AbstractAMQPSerializationScheme(
|
: AbstractAMQPSerializationScheme(
|
||||||
cordappCustomSerializers, serializerFactoriesForContexts
|
cordappCustomSerializers, cordappSerializationWhitelists, serializerFactoriesForContexts
|
||||||
) {
|
) {
|
||||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
return SerializerFactoryBuilder.build(AllWhitelist, javaClass.classLoader).apply {
|
return SerializerFactoryBuilder.build(AllWhitelist, javaClass.classLoader).apply {
|
||||||
|
@ -23,7 +23,7 @@ class AttachmentDemoTest {
|
|||||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = demoUser, maximumHeapSize = "1g"),
|
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = demoUser, maximumHeapSize = "1g"),
|
||||||
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = demoUser, maximumHeapSize = "1g")
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = demoUser, maximumHeapSize = "1g")
|
||||||
).map { it.getOrThrow() }
|
).map { it.getOrThrow() }
|
||||||
val webserverHandle = startWebserver(nodeB).getOrThrow()
|
val webserverHandle = startWebserver(nodeB, "1g").getOrThrow()
|
||||||
|
|
||||||
val senderThread = supplyAsync {
|
val senderThread = supplyAsync {
|
||||||
CordaRPCClient(nodeA.rpcAddress).start(demoUser[0].username, demoUser[0].password).use {
|
CordaRPCClient(nodeA.rpcAddress).start(demoUser[0].username, demoUser[0].password).use {
|
||||||
|
@ -63,7 +63,7 @@ object FixingFlow {
|
|||||||
|
|
||||||
// We set the transaction's time-window: it may be that none of the contracts need this!
|
// We set the transaction's time-window: it may be that none of the contracts need this!
|
||||||
// But it can't hurt to have one.
|
// But it can't hurt to have one.
|
||||||
ptx.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
ptx.setTimeWindow(serviceHub.clock.instant(), 60.seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
|
@ -33,6 +33,7 @@ dependencies {
|
|||||||
cordapp project(':finance:workflows')
|
cordapp project(':finance:workflows')
|
||||||
cordapp project(path: ':samples:simm-valuation-demo:contracts-states', configuration: 'shrinkArtifacts')
|
cordapp project(path: ':samples:simm-valuation-demo:contracts-states', configuration: 'shrinkArtifacts')
|
||||||
cordapp project(':samples:simm-valuation-demo:flows')
|
cordapp project(':samples:simm-valuation-demo:flows')
|
||||||
|
cordapp project(':confidential-identities')
|
||||||
|
|
||||||
// Corda integration dependencies
|
// Corda integration dependencies
|
||||||
cordaRuntime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
cordaRuntime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
@ -71,6 +72,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
|
|||||||
cordapp project(':finance:workflows')
|
cordapp project(':finance:workflows')
|
||||||
cordapp project(':samples:simm-valuation-demo:contracts-states')
|
cordapp project(':samples:simm-valuation-demo:contracts-states')
|
||||||
cordapp project(':samples:simm-valuation-demo:flows')
|
cordapp project(':samples:simm-valuation-demo:flows')
|
||||||
|
cordapp project(':confidential-identities')
|
||||||
rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
|
rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package net.corda.vega.plugin.customserializers
|
||||||
|
|
||||||
|
import com.opengamma.strata.basics.currency.Currency
|
||||||
|
import net.corda.core.serialization.SerializationCustomSerializer
|
||||||
|
|
||||||
|
@Suppress("UNUSED")
|
||||||
|
class CurrencySerializer : SerializationCustomSerializer<Currency, CurrencySerializer.Proxy> {
|
||||||
|
data class Proxy(val currency: String)
|
||||||
|
|
||||||
|
override fun fromProxy(proxy: Proxy): Currency {
|
||||||
|
return withCurrentClassLoader { Currency.parse(proxy.currency) }
|
||||||
|
}
|
||||||
|
override fun toProxy(obj: Currency) = Proxy(obj.toString())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initialization of [Currency] uses the classpath to identify needed resources.
|
||||||
|
* However, it gives priority to the classloader in the thread context over the one this class was loaded with.
|
||||||
|
* See: [com.opengamma.strata.collect.io.ResourceLocator.classLoader]
|
||||||
|
*
|
||||||
|
* This is the reason we temporarily override the class loader in the thread context here, with the classloader of this
|
||||||
|
* class, which is guaranteed to contain everything in the 3rd party library's classpath.
|
||||||
|
*/
|
||||||
|
private fun withCurrentClassLoader(serializationFunction: () -> Currency): Currency {
|
||||||
|
val threadClassLoader = Thread.currentThread().contextClassLoader
|
||||||
|
Thread.currentThread().contextClassLoader = this.javaClass.classLoader
|
||||||
|
val result =serializationFunction()
|
||||||
|
Thread.currentThread().contextClassLoader = threadClassLoader
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -1,40 +0,0 @@
|
|||||||
package net.corda.vega.plugin
|
|
||||||
|
|
||||||
import com.google.common.collect.Ordering
|
|
||||||
import com.opengamma.strata.basics.currency.Currency
|
|
||||||
import com.opengamma.strata.basics.currency.CurrencyAmount
|
|
||||||
import com.opengamma.strata.basics.currency.MultiCurrencyAmount
|
|
||||||
import com.opengamma.strata.basics.date.Tenor
|
|
||||||
import com.opengamma.strata.collect.array.DoubleArray
|
|
||||||
import com.opengamma.strata.market.curve.CurveName
|
|
||||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivities
|
|
||||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivity
|
|
||||||
import com.opengamma.strata.market.param.TenorDateParameterMetadata
|
|
||||||
import com.opengamma.strata.market.param.ParameterMetadata
|
|
||||||
import net.corda.core.serialization.SerializationWhitelist
|
|
||||||
import net.corda.vega.analytics.CordaMarketData
|
|
||||||
import net.corda.vega.analytics.InitialMarginTriple
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [SimmService] is the object that makes available the flows and services for the Simm agreement / evaluation flow.
|
|
||||||
* It is loaded via discovery - see [SerializationWhitelist].
|
|
||||||
* It is also the object that enables a human usable web service for demo purpose
|
|
||||||
* It is loaded via discovery see [WebServerPluginRegistry].
|
|
||||||
*/
|
|
||||||
class SimmFlowsPluginRegistry : SerializationWhitelist {
|
|
||||||
override val whitelist = listOf(
|
|
||||||
// MultiCurrencyAmount::class.java,
|
|
||||||
// Ordering.natural<Comparable<Any>>().javaClass,
|
|
||||||
// CurrencyAmount::class.java,
|
|
||||||
// Currency::class.java,
|
|
||||||
// InitialMarginTriple::class.java,
|
|
||||||
// CordaMarketData::class.java,
|
|
||||||
// CurrencyParameterSensitivities::class.java,
|
|
||||||
// CurrencyParameterSensitivity::class.java,
|
|
||||||
DoubleArray::class.java
|
|
||||||
// CurveName::class.java,
|
|
||||||
// TenorDateParameterMetadata::class.java,
|
|
||||||
// Tenor::class.java,
|
|
||||||
// ParameterMetadata::class.java
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package net.corda.vega.plugin.customserializers
|
|
||||||
|
|
||||||
import com.opengamma.strata.basics.currency.Currency
|
|
||||||
import net.corda.core.serialization.SerializationCustomSerializer
|
|
||||||
|
|
||||||
@Suppress("UNUSED")
|
|
||||||
class CurrencySerializer : SerializationCustomSerializer<Currency, CurrencySerializer.Proxy> {
|
|
||||||
data class Proxy(val currency: String)
|
|
||||||
|
|
||||||
override fun fromProxy(proxy: Proxy): Currency = Currency.parse(proxy.currency)
|
|
||||||
override fun toProxy(obj: Currency) = Proxy(obj.toString())
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
net.corda.vega.plugin.SimmFlowsPluginRegistry
|
|
@ -9,9 +9,11 @@ import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
|||||||
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||||
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||||
|
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||||
import net.corda.testing.driver.DriverParameters
|
import net.corda.testing.driver.DriverParameters
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import net.corda.testing.http.HttpApi
|
import net.corda.testing.http.HttpApi
|
||||||
|
import net.corda.testing.node.NotarySpec
|
||||||
import net.corda.testing.node.internal.FINANCE_CORDAPPS
|
import net.corda.testing.node.internal.FINANCE_CORDAPPS
|
||||||
import net.corda.testing.node.internal.findCordapp
|
import net.corda.testing.node.internal.findCordapp
|
||||||
import net.corda.vega.api.PortfolioApi
|
import net.corda.vega.api.PortfolioApi
|
||||||
@ -51,14 +53,15 @@ class SimmValuationTest {
|
|||||||
val logConfigFile = projectRootDir / "samples" / "simm-valuation-demo" / "src" / "main" / "resources" / "log4j2.xml"
|
val logConfigFile = projectRootDir / "samples" / "simm-valuation-demo" / "src" / "main" / "resources" / "log4j2.xml"
|
||||||
assertThat(logConfigFile).isRegularFile()
|
assertThat(logConfigFile).isRegularFile()
|
||||||
driver(DriverParameters(isDebug = true,
|
driver(DriverParameters(isDebug = true,
|
||||||
cordappsForAllNodes = listOf(findCordapp("net.corda.vega.flows"), findCordapp("net.corda.vega.contracts")) + FINANCE_CORDAPPS,
|
cordappsForAllNodes = listOf(findCordapp("net.corda.vega.flows"), findCordapp("net.corda.vega.contracts"), findCordapp("net.corda.confidential")) + FINANCE_CORDAPPS,
|
||||||
systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))
|
systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()),
|
||||||
|
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, maximumHeapSize = "1g")))
|
||||||
) {
|
) {
|
||||||
val nodeAFuture = startNode(providedName = nodeALegalName)
|
val nodeAFuture = startNode(providedName = nodeALegalName, maximumHeapSize = "1g")
|
||||||
val nodeBFuture = startNode(providedName = nodeBLegalName)
|
val nodeBFuture = startNode(providedName = nodeBLegalName, maximumHeapSize = "1g")
|
||||||
val (nodeA, nodeB) = listOf(nodeAFuture, nodeBFuture).map { it.getOrThrow() }
|
val (nodeA, nodeB) = listOf(nodeAFuture, nodeBFuture).map { it.getOrThrow() }
|
||||||
val nodeAWebServerFuture = startWebserver(nodeA)
|
val nodeAWebServerFuture = startWebserver(nodeA, "1g")
|
||||||
val nodeBWebServerFuture = startWebserver(nodeB)
|
val nodeBWebServerFuture = startWebserver(nodeB, "1g")
|
||||||
val nodeAApi = HttpApi.fromHostAndPort(nodeAWebServerFuture.getOrThrow().listenAddress, "api/simmvaluationdemo")
|
val nodeAApi = HttpApi.fromHostAndPort(nodeAWebServerFuture.getOrThrow().listenAddress, "api/simmvaluationdemo")
|
||||||
val nodeBApi = HttpApi.fromHostAndPort(nodeBWebServerFuture.getOrThrow().listenAddress, "api/simmvaluationdemo")
|
val nodeBApi = HttpApi.fromHostAndPort(nodeBWebServerFuture.getOrThrow().listenAddress, "api/simmvaluationdemo")
|
||||||
val nodeBParty = getPartyWithName(nodeAApi, nodeBLegalName)
|
val nodeBParty = getPartyWithName(nodeAApi, nodeBLegalName)
|
||||||
@ -68,10 +71,9 @@ class SimmValuationTest {
|
|||||||
assertTradeExists(nodeBApi, nodeAParty, testTradeId)
|
assertTradeExists(nodeBApi, nodeAParty, testTradeId)
|
||||||
assertTradeExists(nodeAApi, nodeBParty, testTradeId)
|
assertTradeExists(nodeAApi, nodeBParty, testTradeId)
|
||||||
|
|
||||||
// TODO Dimos - uncomment this on the CORDA-2390 branch to prove that the fix works.
|
runValuationsBetween(nodeAApi, nodeBParty)
|
||||||
// runValuationsBetween(nodeAApi, nodeBParty)
|
assertValuationExists(nodeBApi, nodeAParty)
|
||||||
// assertValuationExists(nodeBApi, nodeAParty)
|
assertValuationExists(nodeAApi, nodeBParty)
|
||||||
// assertValuationExists(nodeAApi, nodeBParty)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,14 +9,12 @@ import net.corda.finance.flows.CashIssueFlow
|
|||||||
import net.corda.finance.flows.CashPaymentFlow
|
import net.corda.finance.flows.CashPaymentFlow
|
||||||
import net.corda.node.services.Permissions.Companion.all
|
import net.corda.node.services.Permissions.Companion.all
|
||||||
import net.corda.node.services.Permissions.Companion.startFlow
|
import net.corda.node.services.Permissions.Companion.startFlow
|
||||||
import net.corda.testing.core.BOC_NAME
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
|
||||||
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
|
||||||
import net.corda.testing.core.singleIdentity
|
|
||||||
import net.corda.testing.driver.DriverParameters
|
import net.corda.testing.driver.DriverParameters
|
||||||
import net.corda.testing.driver.InProcess
|
import net.corda.testing.driver.InProcess
|
||||||
import net.corda.testing.driver.OutOfProcess
|
import net.corda.testing.driver.OutOfProcess
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
|
import net.corda.testing.node.NotarySpec
|
||||||
import net.corda.testing.node.TestCordapp
|
import net.corda.testing.node.TestCordapp
|
||||||
import net.corda.testing.node.User
|
import net.corda.testing.node.User
|
||||||
import net.corda.testing.node.internal.FINANCE_CORDAPPS
|
import net.corda.testing.node.internal.FINANCE_CORDAPPS
|
||||||
@ -39,12 +37,13 @@ class TraderDemoTest {
|
|||||||
driver(DriverParameters(
|
driver(DriverParameters(
|
||||||
startNodesInProcess = true,
|
startNodesInProcess = true,
|
||||||
inMemoryDB = false,
|
inMemoryDB = false,
|
||||||
cordappsForAllNodes = FINANCE_CORDAPPS + TestCordapp.findCordapp("net.corda.traderdemo")
|
cordappsForAllNodes = FINANCE_CORDAPPS + TestCordapp.findCordapp("net.corda.traderdemo"),
|
||||||
|
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, maximumHeapSize = "1g"))
|
||||||
)) {
|
)) {
|
||||||
val (nodeA, nodeB, bankNode) = listOf(
|
val (nodeA, nodeB, bankNode) = listOf(
|
||||||
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser)),
|
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(demoUser), maximumHeapSize = "1g"),
|
||||||
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser)),
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(demoUser), maximumHeapSize = "1g"),
|
||||||
startNode(providedName = BOC_NAME, rpcUsers = listOf(bankUser))
|
startNode(providedName = BOC_NAME, rpcUsers = listOf(bankUser), maximumHeapSize = "1g")
|
||||||
).map { (it.getOrThrow() as InProcess) }
|
).map { (it.getOrThrow() as InProcess) }
|
||||||
|
|
||||||
val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map {
|
val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map {
|
||||||
|
@ -26,7 +26,8 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
|
|||||||
override val encoding: SerializationEncoding?,
|
override val encoding: SerializationEncoding?,
|
||||||
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist,
|
override val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist,
|
||||||
override val lenientCarpenterEnabled: Boolean = false,
|
override val lenientCarpenterEnabled: Boolean = false,
|
||||||
override val preventDataLoss: Boolean = false) : SerializationContext {
|
override val preventDataLoss: Boolean = false,
|
||||||
|
override val customSerializers: Set<SerializationCustomSerializer<*, *>> = emptySet()) : SerializationContext {
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@ -56,6 +57,10 @@ data class SerializationContextImpl @JvmOverloads constructor(override val prefe
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun withCustomSerializers(serializers: Set<SerializationCustomSerializer<*, *>>): SerializationContextImpl {
|
||||||
|
return copy(customSerializers = customSerializers.union(serializers))
|
||||||
|
}
|
||||||
|
|
||||||
override fun withPreferredSerializationVersion(magic: SerializationMagic) = copy(preferredSerializationVersion = magic)
|
override fun withPreferredSerializationVersion(magic: SerializationMagic) = copy(preferredSerializationVersion = magic)
|
||||||
override fun withEncoding(encoding: SerializationEncoding?) = copy(encoding = encoding)
|
override fun withEncoding(encoding: SerializationEncoding?) = copy(encoding = encoding)
|
||||||
override fun withEncodingWhitelist(encodingWhitelist: EncodingWhitelist) = copy(encodingWhitelist = encodingWhitelist)
|
override fun withEncodingWhitelist(encodingWhitelist: EncodingWhitelist) = copy(encodingWhitelist = encodingWhitelist)
|
||||||
|
@ -22,7 +22,10 @@ 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)
|
data class SerializationFactoryCacheKey(val classWhitelist: ClassWhitelist,
|
||||||
|
val deserializationClassLoader: ClassLoader,
|
||||||
|
val preventDataLoss: Boolean,
|
||||||
|
val customSerializers: Set<SerializationCustomSerializer<*, *>>)
|
||||||
|
|
||||||
fun SerializerFactory.addToWhitelist(vararg types: Class<*>) {
|
fun SerializerFactory.addToWhitelist(vararg types: Class<*>) {
|
||||||
require(types.toSet().size == types.size) {
|
require(types.toSet().size == types.size) {
|
||||||
@ -43,11 +46,12 @@ interface SerializerFactoryFactory {
|
|||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
abstract class AbstractAMQPSerializationScheme(
|
abstract class AbstractAMQPSerializationScheme(
|
||||||
private val cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
private val cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||||
|
private val cordappSerializationWhitelists: Set<SerializationWhitelist>,
|
||||||
maybeNotConcurrentSerializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, 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<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
|
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, cordapps.serializationWhitelists, 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<SerializationFactoryCacheKey, SerializerFactory> = if (maybeNotConcurrentSerializerFactoriesForContexts is AccessOrderLinkedHashMap<*, *>) {
|
private val serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = if (maybeNotConcurrentSerializerFactoriesForContexts is AccessOrderLinkedHashMap<*, *>) {
|
||||||
@ -98,9 +102,12 @@ abstract class AbstractAMQPSerializationScheme(
|
|||||||
@DeleteForDJVM
|
@DeleteForDJVM
|
||||||
val List<Cordapp>.customSerializers
|
val List<Cordapp>.customSerializers
|
||||||
get() = flatMap { it.serializationCustomSerializers }.toSet()
|
get() = flatMap { it.serializationCustomSerializers }.toSet()
|
||||||
|
|
||||||
|
@DeleteForDJVM
|
||||||
|
val List<Cordapp>.serializationWhitelists
|
||||||
|
get() = flatMap { it.serializationWhitelists }.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parameter "context" is unused directly but passed in by reflection. Removing it will cause failures.
|
|
||||||
private fun registerCustomSerializers(context: SerializationContext, factory: SerializerFactory) {
|
private fun registerCustomSerializers(context: SerializationContext, factory: SerializerFactory) {
|
||||||
with(factory) {
|
with(factory) {
|
||||||
register(publicKeySerializer)
|
register(publicKeySerializer)
|
||||||
@ -135,9 +142,6 @@ abstract class AbstractAMQPSerializationScheme(
|
|||||||
register(net.corda.serialization.internal.amqp.custom.ContractAttachmentSerializer(this))
|
register(net.corda.serialization.internal.amqp.custom.ContractAttachmentSerializer(this))
|
||||||
registerNonDeterministicSerializers(factory)
|
registerNonDeterministicSerializers(factory)
|
||||||
}
|
}
|
||||||
for (whitelistProvider in serializationWhitelists) {
|
|
||||||
factory.addToWhitelist(*whitelistProvider.whitelist.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're passed in an external list we trust that, otherwise revert to looking at the scan of the
|
// If we're passed in an external list we trust that, otherwise revert to looking at the scan of the
|
||||||
// classpath to find custom serializers.
|
// classpath to find custom serializers.
|
||||||
@ -146,6 +150,15 @@ abstract class AbstractAMQPSerializationScheme(
|
|||||||
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
|
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// This step is registering custom serializers, which have been added after node initialisation (i.e. via attachments during transaction verification).
|
||||||
|
// Note: the order between the registration of customSerializers and cordappCustomSerializers must be preserved as-is. The reason is the following:
|
||||||
|
// Currently, the serialization infrastructure does not support multiple versions of a class (the first one that is registered dominates).
|
||||||
|
// As a result, when inside a context with attachments class loader, we prioritize serializers loaded on-demand from attachments to serializers that had been
|
||||||
|
// loaded during node initialisation, by scanning the cordapps folder.
|
||||||
|
context.customSerializers.forEach { customSerializer ->
|
||||||
|
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug("Custom Serializer list loaded - not scanning classpath")
|
logger.debug("Custom Serializer list loaded - not scanning classpath")
|
||||||
cordappCustomSerializers.forEach { customSerializer ->
|
cordappCustomSerializers.forEach { customSerializer ->
|
||||||
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
|
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
|
||||||
@ -159,6 +172,17 @@ abstract class AbstractAMQPSerializationScheme(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun registerCustomWhitelists(factory: SerializerFactory) {
|
||||||
|
serializationWhitelists.forEach {
|
||||||
|
factory.addToWhitelist(*it.whitelist.toTypedArray())
|
||||||
|
}
|
||||||
|
cordappSerializationWhitelists.forEach {
|
||||||
|
it.whitelist.forEach {
|
||||||
|
clazz -> factory.addToWhitelist(clazz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Register the serializers which will be excluded from the DJVM.
|
* Register the serializers which will be excluded from the DJVM.
|
||||||
*/
|
*/
|
||||||
@ -176,7 +200,7 @@ 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 = SerializationFactoryCacheKey(context.whitelist, context.deserializationClassLoader, context.preventDataLoss)
|
val key = SerializationFactoryCacheKey(context.whitelist, context.deserializationClassLoader, context.preventDataLoss, context.customSerializers)
|
||||||
// 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) {
|
||||||
@ -187,6 +211,7 @@ abstract class AbstractAMQPSerializationScheme(
|
|||||||
else -> sff.make(context)
|
else -> sff.make(context)
|
||||||
}.also {
|
}.also {
|
||||||
registerCustomSerializers(context, it)
|
registerCustomSerializers(context, it)
|
||||||
|
registerCustomWhitelists(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,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<SerializationFactoryCacheKey, SerializerFactory>({ maxFactories }).toSynchronised()
|
val backingMap = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>({ maxFactories }).toSynchronised()
|
||||||
val scheme = object : AbstractAMQPSerializationScheme(emptySet(), backingMap, createSerializerFactoryFactory()) {
|
val scheme = object : AbstractAMQPSerializationScheme(emptySet(), emptySet(), backingMap, createSerializerFactoryFactory()) {
|
||||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
return factory
|
return factory
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,19 @@ data class NotarySpec(
|
|||||||
val rpcUsers: List<User> = emptyList(),
|
val rpcUsers: List<User> = emptyList(),
|
||||||
val verifierType: VerifierType = VerifierType.InMemory,
|
val verifierType: VerifierType = VerifierType.InMemory,
|
||||||
val cluster: ClusterSpec? = null
|
val cluster: ClusterSpec? = null
|
||||||
)
|
) {
|
||||||
|
// These extra fields are handled this way to preserve Kotlin wire compatibility wrt additional parameters with default values.
|
||||||
|
constructor(name: CordaX500Name,
|
||||||
|
validating: Boolean = true,
|
||||||
|
rpcUsers: List<User> = emptyList(),
|
||||||
|
verifierType: VerifierType = VerifierType.InMemory,
|
||||||
|
cluster: ClusterSpec? = null,
|
||||||
|
maximumHeapSize: String = "512m"): this(name, validating, rpcUsers, verifierType, cluster) {
|
||||||
|
this.maximumHeapSize = maximumHeapSize
|
||||||
|
}
|
||||||
|
|
||||||
|
var maximumHeapSize: String = "512m"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class specifying information about the consensus algorithm used for a cluster of nodes.
|
* Abstract class specifying information about the consensus algorithm used for a cluster of nodes.
|
||||||
|
@ -488,7 +488,7 @@ class DriverDSLImpl(
|
|||||||
return startRegisteredNode(
|
return startRegisteredNode(
|
||||||
spec.name,
|
spec.name,
|
||||||
localNetworkMap,
|
localNetworkMap,
|
||||||
NodeParameters(rpcUsers = spec.rpcUsers, verifierType = spec.verifierType, customOverrides = notaryConfig + customOverrides)
|
NodeParameters(rpcUsers = spec.rpcUsers, verifierType = spec.verifierType, customOverrides = notaryConfig + customOverrides, maximumHeapSize = spec.maximumHeapSize)
|
||||||
).map { listOf(it) }
|
).map { listOf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class WebserverDriverTests {
|
|||||||
fun `starting a node and independent web server works`() {
|
fun `starting a node and independent web server works`() {
|
||||||
val addr = driver(DriverParameters(notarySpecs = emptyList())) {
|
val addr = driver(DriverParameters(notarySpecs = emptyList())) {
|
||||||
val node = startNode(providedName = DUMMY_BANK_A_NAME).getOrThrow()
|
val node = startNode(providedName = DUMMY_BANK_A_NAME).getOrThrow()
|
||||||
val webserverHandle = startWebserver(node).getOrThrow()
|
val webserverHandle = startWebserver(node, "512m").getOrThrow()
|
||||||
webserverMustBeUp(webserverHandle)
|
webserverMustBeUp(webserverHandle)
|
||||||
webserverHandle.listenAddress
|
webserverHandle.listenAddress
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,7 @@ class NodeWebServer(val config: WebServerConfig) {
|
|||||||
|
|
||||||
private fun connectLocalRpcAsNodeUser(): CordaRPCOps {
|
private fun connectLocalRpcAsNodeUser(): CordaRPCOps {
|
||||||
log.info("Connecting to node at ${config.rpcAddress} as ${config.runAs}")
|
log.info("Connecting to node at ${config.rpcAddress} as ${config.runAs}")
|
||||||
val client = CordaRPCClient(config.rpcAddress)
|
val client = CordaRPCClient(config.rpcAddress, classLoader = javaClass.classLoader)
|
||||||
val connection = client.start(config.runAs.username, config.runAs.password)
|
val connection = client.start(config.runAs.username, config.runAs.password)
|
||||||
return connection.proxy
|
return connection.proxy
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user