mirror of
https://github.com/corda/corda.git
synced 2025-02-14 22:52:22 +00:00
Merge remote-tracking branch 'open/master' into kat-merge-20180517
This commit is contained in:
commit
74c5cffd38
7952
.ci/api-current.txt
7952
.ci/api-current.txt
File diff suppressed because it is too large
Load Diff
@ -120,9 +120,7 @@ buildscript {
|
|||||||
classpath "net.corda.plugins:quasar-utils:$gradle_plugins_version"
|
classpath "net.corda.plugins:quasar-utils:$gradle_plugins_version"
|
||||||
classpath "net.corda.plugins:cordformation:$gradle_plugins_version"
|
classpath "net.corda.plugins:cordformation:$gradle_plugins_version"
|
||||||
classpath "net.corda.plugins:cordapp:$gradle_plugins_version"
|
classpath "net.corda.plugins:cordapp:$gradle_plugins_version"
|
||||||
//TODO Anthony- this should be changed back to $gradle_plugins_version when the api-scaner is fixed
|
classpath "net.corda.plugins:api-scanner:$gradle_plugins_version"
|
||||||
// classpath "net.corda.plugins:api-scanner:$gradle_plugins_version"
|
|
||||||
classpath "net.corda.plugins:api-scanner:4.0.15"
|
|
||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
|
||||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}"
|
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}"
|
||||||
|
@ -51,9 +51,9 @@ class BlacklistKotlinClosureTest : IntegrationTest() {
|
|||||||
driver(DriverParameters(startNodesInProcess = true)) {
|
driver(DriverParameters(startNodesInProcess = true)) {
|
||||||
val rpc = startNode(providedName = ALICE_NAME).getOrThrow().rpc
|
val rpc = startNode(providedName = ALICE_NAME).getOrThrow().rpc
|
||||||
val packet = Packet { EVIL }
|
val packet = Packet { EVIL }
|
||||||
assertThatExceptionOfType(KryoException::class.java)
|
assertThatExceptionOfType(RPCException::class.java)
|
||||||
.isThrownBy { rpc.startFlow(::FlowC, packet) }
|
.isThrownBy { rpc.startFlow(::FlowC, packet) }
|
||||||
.withMessageContaining("is not annotated or on the whitelist, so cannot be used in serialization")
|
.withMessageContaining("is not on the whitelist or annotated with @CordaSerializable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,9 +10,10 @@
|
|||||||
|
|
||||||
package net.corda.client.rpc
|
package net.corda.client.rpc
|
||||||
|
|
||||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
|
||||||
|
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
import net.corda.client.rpc.internal.RPCClient
|
import net.corda.client.rpc.internal.RPCClient
|
||||||
import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme
|
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
||||||
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.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
@ -21,7 +22,7 @@ import net.corda.core.utilities.NetworkHostAndPort
|
|||||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.nodeapi.ConnectionDirection
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT
|
import net.corda.nodeapi.internal.serialization.AMQP_RPC_CLIENT_CONTEXT
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,7 +122,9 @@ class CordaRPCClient private constructor(
|
|||||||
private val haAddressPool: List<NetworkHostAndPort> = emptyList()
|
private val haAddressPool: List<NetworkHostAndPort> = emptyList()
|
||||||
) {
|
) {
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(hostAndPort: NetworkHostAndPort, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default()) : this(hostAndPort, configuration, null)
|
constructor(hostAndPort: NetworkHostAndPort,
|
||||||
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default())
|
||||||
|
: this(hostAndPort, configuration, null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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.
|
||||||
@ -173,7 +176,7 @@ class CordaRPCClient private constructor(
|
|||||||
effectiveSerializationEnv
|
effectiveSerializationEnv
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
try {
|
try {
|
||||||
KryoClientSerializationScheme.initialiseSerialization(classLoader)
|
AMQPClientSerializationScheme.initialiseSerialization()
|
||||||
} 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.
|
||||||
}
|
}
|
||||||
@ -185,12 +188,12 @@ class CordaRPCClient private constructor(
|
|||||||
RPCClient(
|
RPCClient(
|
||||||
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, config = sslConfiguration),
|
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, config = sslConfiguration),
|
||||||
configuration,
|
configuration,
|
||||||
if (classLoader != null) KRYO_RPC_CLIENT_CONTEXT.withClassLoader(classLoader) else KRYO_RPC_CLIENT_CONTEXT)
|
if (classLoader != null) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classLoader) else AMQP_RPC_CLIENT_CONTEXT)
|
||||||
} else {
|
} else {
|
||||||
RPCClient(haAddressPool,
|
RPCClient(haAddressPool,
|
||||||
sslConfiguration,
|
sslConfiguration,
|
||||||
configuration,
|
configuration,
|
||||||
if (classLoader != null) KRYO_RPC_CLIENT_CONTEXT.withClassLoader(classLoader) else KRYO_RPC_CLIENT_CONTEXT)
|
if (classLoader != null) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classLoader) else AMQP_RPC_CLIENT_CONTEXT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder
|
|||||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
import net.corda.client.rpc.RPCException
|
import net.corda.client.rpc.RPCException
|
||||||
import net.corda.client.rpc.RPCSinceVersion
|
import net.corda.client.rpc.RPCSinceVersion
|
||||||
import net.corda.client.rpc.internal.serialization.kryo.RpcClientObservableSerializer
|
import net.corda.client.rpc.internal.serialization.amqp.RpcClientObservableSerializer
|
||||||
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.context.Trace.InvocationId
|
import net.corda.core.context.Trace.InvocationId
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
package net.corda.client.rpc.internal.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.cordapp.Cordapp
|
||||||
|
import net.corda.core.serialization.ClassWhitelist
|
||||||
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.serialization.SerializationCustomSerializer
|
||||||
|
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||||
|
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||||
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
|
import net.corda.nodeapi.internal.serialization.*
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.custom.RxNotificationSerializer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set as the serialization scheme for a process, sets it to be the Corda AMQP implementation.
|
||||||
|
* This scheme is for use by the RPC Client calls.
|
||||||
|
*/
|
||||||
|
class AMQPClientSerializationScheme(
|
||||||
|
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
|
||||||
|
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||||
|
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
||||||
|
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, ConcurrentHashMap())
|
||||||
|
|
||||||
|
@Suppress("UNUSED")
|
||||||
|
constructor() : this(emptySet(), ConcurrentHashMap())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** Call from main only. */
|
||||||
|
fun initialiseSerialization() {
|
||||||
|
nodeSerializationEnv = createSerializationEnv()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createSerializationEnv(): SerializationEnvironment {
|
||||||
|
return SerializationEnvironmentImpl(
|
||||||
|
SerializationFactoryImpl().apply {
|
||||||
|
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
||||||
|
},
|
||||||
|
storageContext = AMQP_STORAGE_CONTEXT,
|
||||||
|
p2pContext = AMQP_P2P_CONTEXT,
|
||||||
|
rpcClientContext = AMQP_RPC_CLIENT_CONTEXT,
|
||||||
|
rpcServerContext = AMQP_RPC_SERVER_CONTEXT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase) =
|
||||||
|
magic == amqpMagic && (
|
||||||
|
target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P)
|
||||||
|
|
||||||
|
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
|
return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader()).apply {
|
||||||
|
register(RpcClientObservableSerializer)
|
||||||
|
register(RpcClientCordaFutureSerializer(this))
|
||||||
|
register(RxNotificationSerializer(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package net.corda.client.rpc.internal.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
import net.corda.core.toFuture
|
||||||
|
import net.corda.core.toObservable
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||||
|
import rx.Observable
|
||||||
|
import java.io.NotSerializableException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializer for [CordaFuture] instances that can only deserialize such objects (just as the server
|
||||||
|
* side can only serialize them). Futures will have been converted to an Rx [Observable] for serialization.
|
||||||
|
*/
|
||||||
|
class RpcClientCordaFutureSerializer (factory: SerializerFactory)
|
||||||
|
: CustomSerializer.Proxy<CordaFuture<*>, RpcClientCordaFutureSerializer.FutureProxy>(
|
||||||
|
CordaFuture::class.java,
|
||||||
|
RpcClientCordaFutureSerializer.FutureProxy::class.java, factory
|
||||||
|
) {
|
||||||
|
override fun fromProxy(proxy: FutureProxy): CordaFuture<*> {
|
||||||
|
try {
|
||||||
|
return proxy.observable.toFuture()
|
||||||
|
} catch (e: NotSerializableException) {
|
||||||
|
throw NotSerializableException("Failed to deserialize Future from proxy Observable - ${e.message}\n").apply {
|
||||||
|
initCause(e.cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toProxy(obj: CordaFuture<*>): FutureProxy {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FutureProxy(val observable: Observable<*>)
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
package net.corda.client.rpc.internal.serialization.amqp
|
||||||
|
|
||||||
|
|
||||||
|
import net.corda.client.rpc.internal.ObservableContext
|
||||||
|
import net.corda.core.context.Trace
|
||||||
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.nodeapi.RPCApi
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.*
|
||||||
|
import org.apache.qpid.proton.codec.Data
|
||||||
|
import rx.Notification
|
||||||
|
import rx.Observable
|
||||||
|
import rx.subjects.UnicastSubject
|
||||||
|
import java.io.NotSerializableException
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import javax.transaction.NotSupportedException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializer for Rx[Observable] instances for the RPC Client library. Can only be used to deserialize such objects,
|
||||||
|
* just as the corresponding RPC server side code can only serialize them. Observables are only notionally serialized,
|
||||||
|
* what is actually sent is a reference to the observable that can then be subscribed to.
|
||||||
|
*/
|
||||||
|
object RpcClientObservableSerializer : CustomSerializer.Implements<Observable<*>>(Observable::class.java) {
|
||||||
|
private object RpcObservableContextKey
|
||||||
|
|
||||||
|
fun createContext(
|
||||||
|
serializationContext: SerializationContext,
|
||||||
|
observableContext: ObservableContext
|
||||||
|
) = serializationContext.withProperty(RpcObservableContextKey, observableContext)
|
||||||
|
|
||||||
|
private fun <T> pinInSubscriptions(observable: Observable<T>, hardReferenceStore: MutableSet<Observable<*>>): Observable<T> {
|
||||||
|
val refCount = AtomicInteger(0)
|
||||||
|
return observable.doOnSubscribe {
|
||||||
|
if (refCount.getAndIncrement() == 0) {
|
||||||
|
require(hardReferenceStore.add(observable)) {
|
||||||
|
"Reference store already contained reference $this on add"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.doOnUnsubscribe {
|
||||||
|
if (refCount.decrementAndGet() == 0) {
|
||||||
|
require(hardReferenceStore.remove(observable)) {
|
||||||
|
"Reference store did not contain reference $this on remove"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val schemaForDocumentation = Schema(
|
||||||
|
listOf(
|
||||||
|
CompositeType(
|
||||||
|
name = type.toString(),
|
||||||
|
label = "",
|
||||||
|
provides = emptyList(),
|
||||||
|
descriptor = descriptor,
|
||||||
|
fields = listOf(
|
||||||
|
Field(
|
||||||
|
name = "observableId",
|
||||||
|
type = "string",
|
||||||
|
requires = emptyList(),
|
||||||
|
default = null,
|
||||||
|
label = null,
|
||||||
|
mandatory = true,
|
||||||
|
multiple = false),
|
||||||
|
Field(
|
||||||
|
name = "observableInstant",
|
||||||
|
type = "long",
|
||||||
|
requires = emptyList(),
|
||||||
|
default = null,
|
||||||
|
label = null,
|
||||||
|
mandatory = true,
|
||||||
|
multiple = false)
|
||||||
|
))))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the serialized form, a blob, back into an Observable
|
||||||
|
*/
|
||||||
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput,
|
||||||
|
context: SerializationContext
|
||||||
|
): Observable<*> {
|
||||||
|
if (RpcObservableContextKey !in context.properties) {
|
||||||
|
throw NotSerializableException("Missing Observable Context Key on Client Context")
|
||||||
|
}
|
||||||
|
|
||||||
|
val observableContext =
|
||||||
|
context.properties[RpcClientObservableSerializer.RpcObservableContextKey] as ObservableContext
|
||||||
|
|
||||||
|
if (obj !is List<*>) throw NotSerializableException("Input must be a serialised list")
|
||||||
|
if (obj.size != 2) throw NotSerializableException("Expecting two elements, have ${obj.size}")
|
||||||
|
|
||||||
|
val observableId: Trace.InvocationId = Trace.InvocationId((obj[0] as String), Instant.ofEpochMilli((obj[1] as Long)))
|
||||||
|
val observable = UnicastSubject.create<Notification<*>>()
|
||||||
|
|
||||||
|
require(observableContext.observableMap.getIfPresent(observableId) == null) {
|
||||||
|
"Multiple Observables arrived with the same ID $observableId"
|
||||||
|
}
|
||||||
|
|
||||||
|
val rpcCallSite = getRpcCallSite(context, observableContext)
|
||||||
|
|
||||||
|
observableContext.observableMap.put(observableId, observable)
|
||||||
|
observableContext.callSiteMap?.put(observableId, rpcCallSite)
|
||||||
|
|
||||||
|
// We pin all Observables into a hard reference store (rooted in the RPC proxy) on subscription so that users
|
||||||
|
// don't need to store a reference to the Observables themselves.
|
||||||
|
return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe {
|
||||||
|
// This causes Future completions to give warnings because the corresponding OnComplete sent from the server
|
||||||
|
// will arrive after the client unsubscribes from the observable and consequently invalidates the mapping.
|
||||||
|
// The unsubscribe is due to [ObservableToFuture]'s use of first().
|
||||||
|
observableContext.observableMap.invalidate(observableId)
|
||||||
|
}.dematerialize<Any>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRpcCallSite(context: SerializationContext, observableContext: ObservableContext): Throwable? {
|
||||||
|
val rpcRequestOrObservableId = context.properties[RPCApi.RpcRequestOrObservableIdKey] as Trace.InvocationId
|
||||||
|
return observableContext.callSiteMap?.get(rpcRequestOrObservableId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeDescribedObject(
|
||||||
|
obj: Observable<*>,
|
||||||
|
data: Data,
|
||||||
|
type: Type,
|
||||||
|
output: SerializationOutput,
|
||||||
|
context: SerializationContext
|
||||||
|
) {
|
||||||
|
throw NotSupportedException()
|
||||||
|
}
|
||||||
|
}
|
@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* R3 Proprietary and Confidential
|
|
||||||
*
|
|
||||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
|
||||||
*
|
|
||||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
|
||||||
*
|
|
||||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.corda.client.rpc.internal.serialization.kryo
|
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.pool.KryoPool
|
|
||||||
import net.corda.core.serialization.SerializationContext
|
|
||||||
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
|
||||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
|
||||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
|
||||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
|
||||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
|
||||||
import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT
|
|
||||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
|
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer
|
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.RPCKryo
|
|
||||||
|
|
||||||
class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
|
|
||||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
|
||||||
return magic == kryoMagic && (target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun rpcClientKryoPool(context: SerializationContext): KryoPool {
|
|
||||||
return KryoPool.Builder {
|
|
||||||
DefaultKryoCustomizer.customize(RPCKryo(RpcClientObservableSerializer, context), publicKeySerializer).apply {
|
|
||||||
classLoader = context.deserializationClassLoader
|
|
||||||
}
|
|
||||||
}.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're on the client and don't have access to server classes.
|
|
||||||
override fun rpcServerKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/** Call from main only. */
|
|
||||||
fun initialiseSerialization(classLoader: ClassLoader? = null) {
|
|
||||||
nodeSerializationEnv = createSerializationEnv(classLoader)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createSerializationEnv(classLoader: ClassLoader? = null): SerializationEnvironment {
|
|
||||||
return SerializationEnvironmentImpl(
|
|
||||||
SerializationFactoryImpl().apply {
|
|
||||||
registerScheme(KryoClientSerializationScheme())
|
|
||||||
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
|
||||||
},
|
|
||||||
if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT,
|
|
||||||
rpcClientContext = if (classLoader != null) KRYO_RPC_CLIENT_CONTEXT.withClassLoader(classLoader) else KRYO_RPC_CLIENT_CONTEXT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
package net.corda.client.rpc.internal.serialization.kryo
|
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.Kryo
|
|
||||||
import com.esotericsoftware.kryo.Serializer
|
|
||||||
import com.esotericsoftware.kryo.io.Input
|
|
||||||
import com.esotericsoftware.kryo.io.Output
|
|
||||||
import net.corda.client.rpc.internal.ObservableContext
|
|
||||||
import net.corda.core.context.Trace
|
|
||||||
import net.corda.core.serialization.SerializationContext
|
|
||||||
import net.corda.nodeapi.RPCApi
|
|
||||||
import rx.Notification
|
|
||||||
import rx.Observable
|
|
||||||
import rx.subjects.UnicastSubject
|
|
||||||
import java.time.Instant
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Serializer] to deserialise Observables once the corresponding Kryo instance has been provided with an [ObservableContext].
|
|
||||||
*/
|
|
||||||
object RpcClientObservableSerializer : Serializer<Observable<*>>() {
|
|
||||||
private object RpcObservableContextKey
|
|
||||||
|
|
||||||
fun createContext(serializationContext: SerializationContext, observableContext: ObservableContext): SerializationContext {
|
|
||||||
return serializationContext.withProperty(RpcObservableContextKey, observableContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> pinInSubscriptions(observable: Observable<T>, hardReferenceStore: MutableSet<Observable<*>>): Observable<T> {
|
|
||||||
val refCount = AtomicInteger(0)
|
|
||||||
return observable.doOnSubscribe {
|
|
||||||
if (refCount.getAndIncrement() == 0) {
|
|
||||||
require(hardReferenceStore.add(observable)) { "Reference store already contained reference $this on add" }
|
|
||||||
}
|
|
||||||
}.doOnUnsubscribe {
|
|
||||||
if (refCount.decrementAndGet() == 0) {
|
|
||||||
require(hardReferenceStore.remove(observable)) { "Reference store did not contain reference $this on remove" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(kryo: Kryo, input: Input, type: Class<Observable<*>>): Observable<Any> {
|
|
||||||
val observableContext = kryo.context[RpcObservableContextKey] as ObservableContext
|
|
||||||
val observableId = input.readInvocationId() ?: throw IllegalStateException("Unable to read invocationId from Input.")
|
|
||||||
val observable = UnicastSubject.create<Notification<*>>()
|
|
||||||
require(observableContext.observableMap.getIfPresent(observableId) == null) {
|
|
||||||
"Multiple Observables arrived with the same ID $observableId"
|
|
||||||
}
|
|
||||||
val rpcCallSite = getRpcCallSite(kryo, observableContext)
|
|
||||||
observableContext.observableMap.put(observableId, observable)
|
|
||||||
observableContext.callSiteMap?.put(observableId, rpcCallSite)
|
|
||||||
// We pin all Observables into a hard reference store (rooted in the RPC proxy) on subscription so that users
|
|
||||||
// don't need to store a reference to the Observables themselves.
|
|
||||||
return pinInSubscriptions(observable, observableContext.hardReferenceStore).doOnUnsubscribe {
|
|
||||||
// This causes Future completions to give warnings because the corresponding OnComplete sent from the server
|
|
||||||
// will arrive after the client unsubscribes from the observable and consequently invalidates the mapping.
|
|
||||||
// The unsubscribe is due to [ObservableToFuture]'s use of first().
|
|
||||||
observableContext.observableMap.invalidate(observableId)
|
|
||||||
}.dematerialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Input.readInvocationId(): Trace.InvocationId? {
|
|
||||||
|
|
||||||
val value = readString() ?: return null
|
|
||||||
val timestamp = readLong()
|
|
||||||
return Trace.InvocationId(value, Instant.ofEpochMilli(timestamp))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(kryo: Kryo, output: Output, observable: Observable<*>) {
|
|
||||||
throw UnsupportedOperationException("Cannot serialise Observables on the client side")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getRpcCallSite(kryo: Kryo, observableContext: ObservableContext): Throwable? {
|
|
||||||
val rpcRequestOrObservableId = kryo.context[RPCApi.RpcRequestOrObservableIdKey] as Trace.InvocationId
|
|
||||||
return observableContext.callSiteMap?.get(rpcRequestOrObservableId)
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
package net.corda.client.rpc
|
package net.corda.client.rpc
|
||||||
|
|
||||||
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.internal.concurrent.doneFuture
|
import net.corda.core.internal.concurrent.doneFuture
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
@ -21,6 +22,7 @@ import net.corda.testing.node.internal.RPCDriverDSL
|
|||||||
import net.corda.testing.node.internal.rpcDriver
|
import net.corda.testing.node.internal.rpcDriver
|
||||||
import net.corda.testing.node.internal.rpcTestUser
|
import net.corda.testing.node.internal.rpcTestUser
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.Parameterized
|
import org.junit.runners.Parameterized
|
||||||
@ -87,9 +89,10 @@ class ClientRPCInfrastructureTests : AbstractRPCTest() {
|
|||||||
// Does nothing, doesn't throw.
|
// Does nothing, doesn't throw.
|
||||||
proxy.void()
|
proxy.void()
|
||||||
|
|
||||||
assertEquals("Barf!", assertFailsWith<IllegalArgumentException> {
|
assertThatThrownBy { proxy.barf() }
|
||||||
proxy.barf()
|
.isInstanceOf(CordaRuntimeException::class.java)
|
||||||
}.message)
|
.hasMessage("java.lang.IllegalArgumentException: Barf!")
|
||||||
|
|
||||||
|
|
||||||
assertEquals("hi 5", proxy.someCalculation("hi", 5))
|
assertEquals("hi 5", proxy.someCalculation("hi", 5))
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
package net.corda.client.rpc
|
package net.corda.client.rpc
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.KryoException
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.messaging.*
|
import net.corda.core.messaging.*
|
||||||
@ -58,23 +58,29 @@ class RPCFailureTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `kotlin NPE`() = rpc {
|
fun `kotlin NPE`() = rpc {
|
||||||
assertThatThrownBy { it.kotlinNPE() }.isInstanceOf(KotlinNullPointerException::class.java)
|
assertThatThrownBy { it.kotlinNPE() }.isInstanceOf(CordaRuntimeException::class.java)
|
||||||
|
.hasMessageContaining("kotlin.KotlinNullPointerException")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `kotlin NPE async`() = rpc {
|
fun `kotlin NPE async`() = rpc {
|
||||||
val future = it.kotlinNPEAsync()
|
val future = it.kotlinNPEAsync()
|
||||||
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(KotlinNullPointerException::class.java)
|
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(CordaRuntimeException::class.java)
|
||||||
|
.hasMessageContaining("kotlin.KotlinNullPointerException")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun unserializable() = rpc {
|
fun `unserializable`() = rpc {
|
||||||
assertThatThrownBy { it.getUnserializable() }.isInstanceOf(KryoException::class.java)
|
assertThatThrownBy { it.getUnserializable() }.isInstanceOf(CordaRuntimeException::class.java)
|
||||||
|
.hasMessageContaining("java.io.NotSerializableException:")
|
||||||
|
.hasMessageContaining("Unserializable is not on the whitelist or annotated with @CordaSerializable.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `unserializable async`() = rpc {
|
fun `unserializable async`() = rpc {
|
||||||
val future = it.getUnserializableAsync()
|
val future = it.getUnserializableAsync()
|
||||||
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(KryoException::class.java)
|
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(CordaRuntimeException::class.java)
|
||||||
|
.hasMessageContaining("java.io.NotSerializableException:")
|
||||||
|
.hasMessageContaining("Unserializable is not on the whitelist or annotated with @CordaSerializable.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
package net.corda.core.concurrent
|
package net.corda.core.concurrent
|
||||||
|
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ import java.util.concurrent.Future
|
|||||||
* Same as [Future] with additional methods to provide some of the features of [java.util.concurrent.CompletableFuture] while minimising the API surface area.
|
* Same as [Future] with additional methods to provide some of the features of [java.util.concurrent.CompletableFuture] while minimising the API surface area.
|
||||||
* In Kotlin, to avoid compile errors, whenever CordaFuture is used in a parameter or extension method receiver type, its type parameter should be specified with out variance.
|
* In Kotlin, to avoid compile errors, whenever CordaFuture is used in a parameter or extension method receiver type, its type parameter should be specified with out variance.
|
||||||
*/
|
*/
|
||||||
|
@CordaSerializable
|
||||||
interface CordaFuture<V> : Future<V> {
|
interface CordaFuture<V> : Future<V> {
|
||||||
/**
|
/**
|
||||||
* Run the given callback when this future is done, on the completion thread.
|
* Run the given callback when this future is done, on the completion thread.
|
||||||
|
@ -20,6 +20,7 @@ import rx.Observable
|
|||||||
* [FlowHandle] is a serialisable handle for the started flow, parameterised by the type of the flow's return value.
|
* [FlowHandle] is a serialisable handle for the started flow, parameterised by the type of the flow's return value.
|
||||||
*/
|
*/
|
||||||
@DoNotImplement
|
@DoNotImplement
|
||||||
|
@CordaSerializable
|
||||||
interface FlowHandle<A> : AutoCloseable {
|
interface FlowHandle<A> : AutoCloseable {
|
||||||
/**
|
/**
|
||||||
* The started state machine's ID.
|
* The started state machine's ID.
|
||||||
|
@ -124,11 +124,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FungibleStateQueryCriteria: provides query by attributes defined in [VaultSchema.VaultFungibleState]
|
* FungibleStateQueryCriteria: provides query by attributes defined in [VaultSchema.VaultFungibleStates]
|
||||||
*
|
|
||||||
* Valid TokenType implementations defined by Amount<T> are
|
|
||||||
* [Currency] as used in [Cash] contract state
|
|
||||||
* [Commodity] as used in [CommodityContract] state
|
|
||||||
*/
|
*/
|
||||||
data class FungibleAssetQueryCriteria @JvmOverloads constructor(val participants: List<AbstractParty>? = null,
|
data class FungibleAssetQueryCriteria @JvmOverloads constructor(val participants: List<AbstractParty>? = null,
|
||||||
val owner: List<AbstractParty>? = null,
|
val owner: List<AbstractParty>? = null,
|
||||||
@ -150,8 +146,6 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
|||||||
*
|
*
|
||||||
* Params
|
* Params
|
||||||
* [expression] refers to a (composable) type safe [CriteriaExpression]
|
* [expression] refers to a (composable) type safe [CriteriaExpression]
|
||||||
*
|
|
||||||
* Refer to [CommercialPaper.State] for a concrete example.
|
|
||||||
*/
|
*/
|
||||||
data class VaultCustomQueryCriteria<L : PersistentState> @JvmOverloads constructor
|
data class VaultCustomQueryCriteria<L : PersistentState> @JvmOverloads constructor
|
||||||
(val expression: CriteriaExpression<L, Boolean>,
|
(val expression: CriteriaExpression<L, Boolean>,
|
||||||
|
@ -195,6 +195,15 @@ interface SerializationContext {
|
|||||||
enum class UseCase { P2P, RPCServer, RPCClient, Storage, Checkpoint, Testing }
|
enum class UseCase { P2P, RPCServer, RPCClient, Storage, Checkpoint, Testing }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of well known properties that may be set on a serialization context. This doesn't preclude
|
||||||
|
* others being set that aren't keyed on this enumeration, but for general use properties adding a
|
||||||
|
* well known key here is preferred.
|
||||||
|
*/
|
||||||
|
enum class ContextPropertyKeys {
|
||||||
|
SERIALIZERS
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global singletons to be used as defaults that are injected elsewhere (generally, in the node or in RPC client).
|
* Global singletons to be used as defaults that are injected elsewhere (generally, in the node or in RPC client).
|
||||||
*/
|
*/
|
||||||
|
@ -16,7 +16,7 @@ package net.corda.core.serialization
|
|||||||
* a proxy serializer can be written that extends this type whose purpose is to move between those an
|
* a proxy serializer can be written that extends this type whose purpose is to move between those an
|
||||||
* unserializable types and an intermediate representation.
|
* unserializable types and an intermediate representation.
|
||||||
*
|
*
|
||||||
* NOTE: The proxy object should be specified as a seperate class. However, this can be defined within the
|
* NOTE: The proxy object should be specified as a separate class. However, this can be defined within the
|
||||||
* scope of the custom serializer.
|
* scope of the custom serializer.
|
||||||
*/
|
*/
|
||||||
interface SerializationCustomSerializer<OBJ, PROXY> {
|
interface SerializationCustomSerializer<OBJ, PROXY> {
|
||||||
|
@ -12,16 +12,20 @@ package net.corda.core.utilities
|
|||||||
|
|
||||||
import com.esotericsoftware.kryo.KryoException
|
import com.esotericsoftware.kryo.KryoException
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.serialization.deserialize
|
|
||||||
import net.corda.core.serialization.serialize
|
|
||||||
import net.corda.nodeapi.internal.serialization.KRYO_CHECKPOINT_CONTEXT
|
import net.corda.nodeapi.internal.serialization.KRYO_CHECKPOINT_CONTEXT
|
||||||
|
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
|
||||||
|
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.ExpectedException
|
import org.junit.rules.ExpectedException
|
||||||
|
|
||||||
|
object EmptyWhitelist : ClassWhitelist {
|
||||||
|
override fun hasListed(type: Class<*>): Boolean = false
|
||||||
|
}
|
||||||
|
|
||||||
class KotlinUtilsTest {
|
class KotlinUtilsTest {
|
||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
@ -30,6 +34,14 @@ class KotlinUtilsTest {
|
|||||||
@Rule
|
@Rule
|
||||||
val expectedEx: ExpectedException = ExpectedException.none()
|
val expectedEx: ExpectedException = ExpectedException.none()
|
||||||
|
|
||||||
|
val KRYO_CHECKPOINT_NOWHITELIST_CONTEXT = SerializationContextImpl(kryoMagic,
|
||||||
|
SerializationDefaults.javaClass.classLoader,
|
||||||
|
EmptyWhitelist,
|
||||||
|
emptyMap(),
|
||||||
|
true,
|
||||||
|
SerializationContext.UseCase.Checkpoint,
|
||||||
|
null)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `transient property which is null`() {
|
fun `transient property which is null`() {
|
||||||
val test = NullTransientProperty()
|
val test = NullTransientProperty()
|
||||||
@ -53,7 +65,8 @@ class KotlinUtilsTest {
|
|||||||
expectedEx.expect(KryoException::class.java)
|
expectedEx.expect(KryoException::class.java)
|
||||||
expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization")
|
expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization")
|
||||||
val original = NonCapturingTransientProperty()
|
val original = NonCapturingTransientProperty()
|
||||||
original.serialize(context = KRYO_CHECKPOINT_CONTEXT.withEncoding(null)).deserialize()
|
original.serialize(context = KRYO_CHECKPOINT_CONTEXT.withEncoding(null))
|
||||||
|
.deserialize(context = KRYO_CHECKPOINT_NOWHITELIST_CONTEXT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -71,8 +84,10 @@ class KotlinUtilsTest {
|
|||||||
fun `deserialise transient property with capturing lambda`() {
|
fun `deserialise transient property with capturing lambda`() {
|
||||||
expectedEx.expect(KryoException::class.java)
|
expectedEx.expect(KryoException::class.java)
|
||||||
expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization")
|
expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization")
|
||||||
|
|
||||||
val original = CapturingTransientProperty("Hello")
|
val original = CapturingTransientProperty("Hello")
|
||||||
original.serialize(context = KRYO_CHECKPOINT_CONTEXT.withEncoding(null)).deserialize()
|
original.serialize(context = KRYO_CHECKPOINT_CONTEXT.withEncoding(null))
|
||||||
|
.deserialize(context = KRYO_CHECKPOINT_NOWHITELIST_CONTEXT)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NullTransientProperty {
|
private class NullTransientProperty {
|
||||||
|
@ -6,6 +6,10 @@ release, see :doc:`upgrade-notes`.
|
|||||||
|
|
||||||
Unreleased
|
Unreleased
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
* RPC Framework moved from Kryo to the Corda AMQP implementation [Corda-847]. This completes the removal
|
||||||
|
of ``Kryo`` from general use within Corda, remaining only for use in flow checkpointing.
|
||||||
|
|
||||||
* Set co.paralleluniverse.fibers.verifyInstrumentation=true in devMode.
|
* Set co.paralleluniverse.fibers.verifyInstrumentation=true in devMode.
|
||||||
|
|
||||||
* Node will now gracefully fail to start if one of the required ports is already in use.
|
* Node will now gracefully fail to start if one of the required ports is already in use.
|
||||||
|
@ -238,6 +238,13 @@ absolute path to the node's base directory.
|
|||||||
|
|
||||||
.. note:: This is temporary feature for onboarding network participants that limits their visibility for privacy reasons.
|
.. note:: This is temporary feature for onboarding network participants that limits their visibility for privacy reasons.
|
||||||
|
|
||||||
|
:tlsCertCrlDistPoint: CRL distribution point (i.e. URL) for the TLS certificate. Default value is NULL, which indicates no CRL availability for the TLS certificate.
|
||||||
|
Note: If crlCheckSoftFail is FALSE (meaning that there is the strict CRL checking mode) this value needs to be set.
|
||||||
|
|
||||||
|
:tlsCertCrlIssuer: CRL issuer (given in the X500 name format) for the TLS certificate. Default value is NULL,
|
||||||
|
which indicates that the issuer of the TLS certificate is also the issuer of the CRL.
|
||||||
|
Note: If this parameter is set then the tlsCertCrlDistPoint needs to be set as well.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -52,9 +52,6 @@ There are two main steps to implementing scheduled events:
|
|||||||
.. note:: The scheduler's clock always operates in the UTC time zone for uniformity, so any time zone logic must be
|
.. note:: The scheduler's clock always operates in the UTC time zone for uniformity, so any time zone logic must be
|
||||||
performed by the contract, using ``ZonedDateTime``.
|
performed by the contract, using ``ZonedDateTime``.
|
||||||
|
|
||||||
In the short term, until we have automatic flow session set up, you will also likely need to install a network
|
|
||||||
handler to help with obtaining a unique and secure random session. An example is described below.
|
|
||||||
|
|
||||||
The production and consumption of ``ContractStates`` is observed by the scheduler and the activities associated with
|
The production and consumption of ``ContractStates`` is observed by the scheduler and the activities associated with
|
||||||
any consumed states are unscheduled. Any newly produced states are then queried via the ``nextScheduledActivity``
|
any consumed states are unscheduled. Any newly produced states are then queried via the ``nextScheduledActivity``
|
||||||
method and if they do not return ``null`` then that activity is scheduled based on the content of the
|
method and if they do not return ``null`` then that activity is scheduled based on the content of the
|
||||||
@ -82,11 +79,3 @@ should become available and schedules an activity at that time to work out what
|
|||||||
business process and to take on those roles. That ``FlowLogic`` will be handed the ``StateRef`` for the interest
|
business process and to take on those roles. That ``FlowLogic`` will be handed the ``StateRef`` for the interest
|
||||||
rate swap ``State`` in question, as well as a tolerance ``Duration`` of how long to wait after the activity is triggered
|
rate swap ``State`` in question, as well as a tolerance ``Duration`` of how long to wait after the activity is triggered
|
||||||
for the interest rate before indicating an error.
|
for the interest rate before indicating an error.
|
||||||
|
|
||||||
.. note:: This is a way to create a reference to the FlowLogic class and its constructor parameters to instantiate.
|
|
||||||
|
|
||||||
As previously mentioned, we currently need a small network handler to assist with session setup until the work to
|
|
||||||
automate that is complete. See the interest rate swap specific implementation ``FixingSessionInitiationHandler`` which
|
|
||||||
is responsible for starting a ``FlowLogic`` to perform one role in the fixing flow with the ``sessionID`` sent
|
|
||||||
by the ``FixingRoleDecider`` on the other node which then launches the other role in the fixing flow. Currently
|
|
||||||
the handler needs to be manually installed in the node.
|
|
||||||
|
@ -105,11 +105,9 @@ The current set of network parameters:
|
|||||||
:notaries: List of identity and validation type (either validating or non-validating) of the notaries which are permitted
|
:notaries: List of identity and validation type (either validating or non-validating) of the notaries which are permitted
|
||||||
in the compatibility zone.
|
in the compatibility zone.
|
||||||
|
|
||||||
:maxMessageSize: (This is currently ignored. However, it will be wired up in a future release.)
|
:maxMessageSize: Maximum allowed size in bytes of an individual message sent over the wire. Note that attachments are
|
||||||
|
a special case and may be fragmented for streaming transfer, however, an individual transaction or flow message
|
||||||
.. TODO Replace the above with this once wired: Maximum allowed size in bytes of an individual message sent over the wire. Note that attachments are
|
may not be larger than this value.
|
||||||
a special case and may be fragmented for streaming transfer, however, an individual transaction or flow message
|
|
||||||
may not be larger than this value.
|
|
||||||
|
|
||||||
:maxTransactionSize: Maximum allowed size in bytes of a transaction. This is the size of the transaction object and its attachments.
|
:maxTransactionSize: Maximum allowed size in bytes of a transaction. This is the size of the transaction object and its attachments.
|
||||||
|
|
||||||
|
@ -76,6 +76,11 @@ dependencies {
|
|||||||
testCompile "junit:junit:$junit_version"
|
testCompile "junit:junit:$junit_version"
|
||||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||||
testCompile project(':node-driver')
|
testCompile project(':node-driver')
|
||||||
|
|
||||||
|
compile ("org.apache.activemq:artemis-amqp-protocol:${artemis_version}") {
|
||||||
|
// Gains our proton-j version from core module.
|
||||||
|
exclude group: 'org.apache.qpid', module: 'proton-j'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
|
@ -28,7 +28,9 @@ class InternalNodeException(message: String) : CordaRuntimeException(message) {
|
|||||||
(wrapped as? CordaRuntimeException)?.setCause(null)
|
(wrapped as? CordaRuntimeException)?.setCause(null)
|
||||||
return when {
|
return when {
|
||||||
whitelisted.any { it.isInstance(wrapped) } -> wrapped
|
whitelisted.any { it.isInstance(wrapped) } -> wrapped
|
||||||
else -> InternalNodeException(DEFAULT_MESSAGE)
|
else -> InternalNodeException(DEFAULT_MESSAGE).apply {
|
||||||
|
stackTrace = emptyArray()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ class ArtemisMessagingClient(
|
|||||||
minLargeMessageSize = maxMessageSize
|
minLargeMessageSize = maxMessageSize
|
||||||
isUseGlobalPools = nodeSerializationEnv != null
|
isUseGlobalPools = nodeSerializationEnv != null
|
||||||
confirmationWindowSize = this@ArtemisMessagingClient.confirmationWindowSize
|
confirmationWindowSize = this@ArtemisMessagingClient.confirmationWindowSize
|
||||||
|
addIncomingInterceptor(ArtemisMessageSizeChecksInterceptor(maxMessageSize))
|
||||||
}
|
}
|
||||||
val sessionFactory = locator.createSessionFactory()
|
val sessionFactory = locator.createSessionFactory()
|
||||||
// Login using the node username. The broker will authenticate us as its node (as opposed to another peer)
|
// Login using the node username. The broker will authenticate us as its node (as opposed to another peer)
|
||||||
|
@ -40,7 +40,9 @@ class ArtemisMessagingComponent {
|
|||||||
const val BRIDGE_CONTROL = "${INTERNAL_PREFIX}bridge.control"
|
const val BRIDGE_CONTROL = "${INTERNAL_PREFIX}bridge.control"
|
||||||
const val BRIDGE_NOTIFY = "${INTERNAL_PREFIX}bridge.notify"
|
const val BRIDGE_NOTIFY = "${INTERNAL_PREFIX}bridge.notify"
|
||||||
const val NOTIFICATIONS_ADDRESS = "${INTERNAL_PREFIX}activemq.notifications"
|
const val NOTIFICATIONS_ADDRESS = "${INTERNAL_PREFIX}activemq.notifications"
|
||||||
|
// This is a rough guess on the extra space needed on top of maxMessageSize to store the journal.
|
||||||
|
// TODO: we might want to make this value configurable.
|
||||||
|
const val JOURNAL_HEADER_SIZE = 1024
|
||||||
object P2PMessagingHeaders {
|
object P2PMessagingHeaders {
|
||||||
// This is a "property" attached to an Artemis MQ message object, which contains our own notion of "topic".
|
// This is a "property" attached to an Artemis MQ message object, which contains our own notion of "topic".
|
||||||
// We should probably try to unify our notion of "topic" (really, just a string that identifies an endpoint
|
// We should probably try to unify our notion of "topic" (really, just a string that identifies an endpoint
|
||||||
|
@ -22,3 +22,7 @@ import java.nio.file.Path
|
|||||||
fun Path.requireOnDefaultFileSystem() {
|
fun Path.requireOnDefaultFileSystem() {
|
||||||
require(fileSystem == FileSystems.getDefault()) { "Artemis only uses the default file system" }
|
require(fileSystem == FileSystems.getDefault()) { "Artemis only uses the default file system" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun requireMessageSize(messageSize: Int, limit: Int) {
|
||||||
|
require(messageSize <= limit) { "Message exceeds maxMessageSize network parameter, maxMessageSize: [$limit], message size: [$messageSize]" }
|
||||||
|
}
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
package net.corda.nodeapi.internal
|
||||||
|
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import org.apache.activemq.artemis.api.core.BaseInterceptor
|
||||||
|
import org.apache.activemq.artemis.api.core.Interceptor
|
||||||
|
import org.apache.activemq.artemis.core.protocol.core.Packet
|
||||||
|
import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.MessagePacket
|
||||||
|
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage
|
||||||
|
import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor
|
||||||
|
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection
|
||||||
|
|
||||||
|
class ArtemisMessageSizeChecksInterceptor(maxMessageSize: Int) : MessageSizeChecksInterceptor<Packet>(maxMessageSize), Interceptor {
|
||||||
|
override fun getMessageSize(packet: Packet?): Int? {
|
||||||
|
return when (packet) {
|
||||||
|
// This is an estimate of how much memory a Message body takes up.
|
||||||
|
// Note, it is only an estimate
|
||||||
|
is MessagePacket -> (packet.message.persistentSize - packet.message.headersAndPropertiesEncodeSize - 4).toInt()
|
||||||
|
// Skip all artemis control messages.
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AmqpMessageSizeChecksInterceptor(maxMessageSize: Int) : MessageSizeChecksInterceptor<AMQPMessage>(maxMessageSize), AmqpInterceptor {
|
||||||
|
override fun getMessageSize(packet: AMQPMessage?): Int? = packet?.length
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Artemis message interceptor to enforce maxMessageSize on incoming messages.
|
||||||
|
*/
|
||||||
|
sealed class MessageSizeChecksInterceptor<T : Any>(private val maxMessageSize: Int) : BaseInterceptor<T> {
|
||||||
|
companion object {
|
||||||
|
private val logger = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun intercept(packet: T, connection: RemotingConnection?): Boolean {
|
||||||
|
val messageSize = getMessageSize(packet) ?: return true
|
||||||
|
return if (messageSize > maxMessageSize) {
|
||||||
|
logger.warn("Message size exceeds maxMessageSize network parameter, maxMessageSize: [$maxMessageSize], message size: [$messageSize], " +
|
||||||
|
"dropping message, client id :${connection?.clientID}")
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get size of the message in byte, returns null if the message is null or size don't need to be checked.
|
||||||
|
abstract fun getMessageSize(packet: T?): Int?
|
||||||
|
}
|
||||||
|
|
@ -48,7 +48,8 @@ import kotlin.concurrent.withLock
|
|||||||
* The Netty thread pool used by the AMQPBridges is also shared and managed by the AMQPBridgeManager.
|
* The Netty thread pool used by the AMQPBridges is also shared and managed by the AMQPBridgeManager.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConfig: SocksProxyConfig? = null, private val artemisMessageClientFactory: () -> ArtemisSessionProvider) : BridgeManager {
|
class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConfig: SocksProxyConfig? = null,
|
||||||
|
private val maxMessageSize: Int, private val artemisMessageClientFactory: () -> ArtemisSessionProvider) : BridgeManager {
|
||||||
|
|
||||||
private val lock = ReentrantLock()
|
private val lock = ReentrantLock()
|
||||||
private val bridgeNameToBridgeMap = mutableMapOf<String, AMQPBridge>()
|
private val bridgeNameToBridgeMap = mutableMapOf<String, AMQPBridge>()
|
||||||
@ -59,7 +60,8 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConf
|
|||||||
private var artemis: ArtemisSessionProvider? = null
|
private var artemis: ArtemisSessionProvider? = null
|
||||||
private val crlCheckSoftFail: Boolean = config.crlCheckSoftFail
|
private val crlCheckSoftFail: Boolean = config.crlCheckSoftFail
|
||||||
|
|
||||||
constructor(config: NodeSSLConfiguration, p2pAddress: NetworkHostAndPort, maxMessageSize: Int, socksProxyConfig: SocksProxyConfig? = null) : this(config, socksProxyConfig, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) })
|
constructor(config: NodeSSLConfiguration, p2pAddress: NetworkHostAndPort, maxMessageSize: Int, socksProxyConfig: SocksProxyConfig? = null) : this(config, socksProxyConfig, maxMessageSize, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) })
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val NUM_BRIDGE_THREADS = 0 // Default sized pool
|
private const val NUM_BRIDGE_THREADS = 0 // Default sized pool
|
||||||
@ -82,14 +84,16 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConf
|
|||||||
crlCheckSoftFail: Boolean,
|
crlCheckSoftFail: Boolean,
|
||||||
sharedEventGroup: EventLoopGroup,
|
sharedEventGroup: EventLoopGroup,
|
||||||
socksProxyConfig: SocksProxyConfig?,
|
socksProxyConfig: SocksProxyConfig?,
|
||||||
private val artemis: ArtemisSessionProvider) {
|
private val artemis: ArtemisSessionProvider,
|
||||||
|
private val maxMessageSize: Int) {
|
||||||
companion object {
|
companion object {
|
||||||
fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort"
|
fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val log = LoggerFactory.getLogger("$bridgeName:${legalNames.first()}")
|
private val log = LoggerFactory.getLogger("$bridgeName:${legalNames.first()}")
|
||||||
|
|
||||||
val amqpClient = AMQPClient(listOf(target), legalNames, PEER_USER, PEER_USER, keyStore, keyStorePrivateKeyPassword, trustStore, crlCheckSoftFail, sharedThreadPool = sharedEventGroup, socksProxyConfig = socksProxyConfig)
|
val amqpClient = AMQPClient(listOf(target), legalNames, PEER_USER, PEER_USER, keyStore, keyStorePrivateKeyPassword, trustStore, crlCheckSoftFail,
|
||||||
|
sharedThreadPool = sharedEventGroup, socksProxyConfig = socksProxyConfig, maxMessageSize = maxMessageSize)
|
||||||
val bridgeName: String get() = getBridgeName(queueName, target)
|
val bridgeName: String get() = getBridgeName(queueName, target)
|
||||||
private val lock = ReentrantLock() // lock to serialise session level access
|
private val lock = ReentrantLock() // lock to serialise session level access
|
||||||
private var session: ClientSession? = null
|
private var session: ClientSession? = null
|
||||||
@ -141,6 +145,13 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConf
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun clientArtemisMessageHandler(artemisMessage: ClientMessage) {
|
private fun clientArtemisMessageHandler(artemisMessage: ClientMessage) {
|
||||||
|
if (artemisMessage.bodySize > maxMessageSize) {
|
||||||
|
log.warn("Message exceeds maxMessageSize network parameter, maxMessageSize: [$maxMessageSize], message size: [${artemisMessage.bodySize}], " +
|
||||||
|
"dropping message, uuid: ${artemisMessage.getObjectProperty("_AMQ_DUPL_ID")}")
|
||||||
|
// Ack the message to prevent same message being sent to us again.
|
||||||
|
artemisMessage.acknowledge()
|
||||||
|
return
|
||||||
|
}
|
||||||
val data = ByteArray(artemisMessage.bodySize).apply { artemisMessage.bodyBuffer.readBytes(this) }
|
val data = ByteArray(artemisMessage.bodySize).apply { artemisMessage.bodyBuffer.readBytes(this) }
|
||||||
val properties = HashMap<String, Any?>()
|
val properties = HashMap<String, Any?>()
|
||||||
for (key in P2PMessagingHeaders.whitelistedHeaders) {
|
for (key in P2PMessagingHeaders.whitelistedHeaders) {
|
||||||
@ -183,7 +194,9 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val socksProxyConf
|
|||||||
if (bridgeExists(getBridgeName(queueName, target))) {
|
if (bridgeExists(getBridgeName(queueName, target))) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val newBridge = AMQPBridge(queueName, target, legalNames, keyStore, keyStorePrivateKeyPassword, trustStore, crlCheckSoftFail, sharedEventLoopGroup!!, socksProxyConfig, artemis!!)
|
|
||||||
|
val newBridge = AMQPBridge(queueName, target, legalNames, keyStore, keyStorePrivateKeyPassword, trustStore, crlCheckSoftFail, sharedEventLoopGroup!!, socksProxyConfig, artemis!!, maxMessageSize)
|
||||||
|
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
bridgeNameToBridgeMap[newBridge.bridgeName] = newBridge
|
bridgeNameToBridgeMap[newBridge.bridgeName] = newBridge
|
||||||
}
|
}
|
||||||
|
@ -33,19 +33,24 @@ import rx.subjects.PublishSubject
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class BridgeControlListener(val config: NodeSSLConfiguration,
|
class BridgeControlListener(val config: NodeSSLConfiguration,
|
||||||
|
|
||||||
socksProxyConfig: SocksProxyConfig? = null,
|
socksProxyConfig: SocksProxyConfig? = null,
|
||||||
|
maxMessageSize: Int,
|
||||||
val artemisMessageClientFactory: () -> ArtemisSessionProvider) : AutoCloseable {
|
val artemisMessageClientFactory: () -> ArtemisSessionProvider) : AutoCloseable {
|
||||||
private val bridgeId: String = UUID.randomUUID().toString()
|
private val bridgeId: String = UUID.randomUUID().toString()
|
||||||
private val bridgeControlQueue = "$BRIDGE_CONTROL.$bridgeId"
|
private val bridgeControlQueue = "$BRIDGE_CONTROL.$bridgeId"
|
||||||
private val bridgeManager: BridgeManager = AMQPBridgeManager(config, socksProxyConfig, artemisMessageClientFactory)
|
private val bridgeManager: BridgeManager = AMQPBridgeManager(config, socksProxyConfig, maxMessageSize,
|
||||||
|
artemisMessageClientFactory)
|
||||||
private val validInboundQueues = mutableSetOf<String>()
|
private val validInboundQueues = mutableSetOf<String>()
|
||||||
private var artemis: ArtemisSessionProvider? = null
|
private var artemis: ArtemisSessionProvider? = null
|
||||||
private var controlConsumer: ClientConsumer? = null
|
private var controlConsumer: ClientConsumer? = null
|
||||||
|
|
||||||
constructor(config: NodeSSLConfiguration,
|
constructor(config: NodeSSLConfiguration,
|
||||||
p2pAddress: NetworkHostAndPort,
|
p2pAddress: NetworkHostAndPort,
|
||||||
|
|
||||||
maxMessageSize: Int,
|
maxMessageSize: Int,
|
||||||
socksProxy: SocksProxyConfig? = null) : this(config, socksProxy, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) })
|
socksProxy: SocksProxyConfig? = null) : this(config, socksProxy, maxMessageSize, { ArtemisMessagingClient(config, p2pAddress, maxMessageSize) })
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
|
@ -33,10 +33,11 @@ import net.corda.nodeapi.internal.ContractsJarFile
|
|||||||
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
|
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
|
||||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
|
||||||
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
||||||
|
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -288,7 +289,7 @@ class NetworkBootstrapper {
|
|||||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||||
SerializationFactoryImpl().apply {
|
SerializationFactoryImpl().apply {
|
||||||
registerScheme(KryoParametersSerializationScheme)
|
registerScheme(KryoParametersSerializationScheme)
|
||||||
registerScheme(AMQPServerSerializationScheme())
|
registerScheme(AMQPParametersSerializationScheme)
|
||||||
},
|
},
|
||||||
AMQP_P2P_CONTEXT)
|
AMQP_P2P_CONTEXT)
|
||||||
)
|
)
|
||||||
@ -302,4 +303,13 @@ class NetworkBootstrapper {
|
|||||||
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object AMQPParametersSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
|
||||||
|
override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
|
override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
||||||
|
return magic == amqpMagic && target == SerializationContext.UseCase.P2P
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,7 +362,7 @@ internal class ConnectionStateMachine(serverMode: Boolean,
|
|||||||
val connection = event.connection
|
val connection = event.connection
|
||||||
val channel = connection?.context as? Channel
|
val channel = connection?.context as? Channel
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
val appProperties = HashMap(amqpMessage.applicationProperties.value as Map<String, Any?>)
|
val appProperties = HashMap(amqpMessage.applicationProperties.value)
|
||||||
appProperties["_AMQ_VALIDATED_USER"] = remoteLegalName
|
appProperties["_AMQ_VALIDATED_USER"] = remoteLegalName
|
||||||
val localAddress = channel.localAddress() as InetSocketAddress
|
val localAddress = channel.localAddress() as InetSocketAddress
|
||||||
val remoteAddress = channel.remoteAddress() as InetSocketAddress
|
val remoteAddress = channel.remoteAddress() as InetSocketAddress
|
||||||
|
@ -27,6 +27,7 @@ import net.corda.core.utilities.contextLogger
|
|||||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||||
import net.corda.nodeapi.internal.protonwrapper.messages.SendableMessage
|
import net.corda.nodeapi.internal.protonwrapper.messages.SendableMessage
|
||||||
import net.corda.nodeapi.internal.protonwrapper.messages.impl.SendableMessageImpl
|
import net.corda.nodeapi.internal.protonwrapper.messages.impl.SendableMessageImpl
|
||||||
|
import net.corda.nodeapi.internal.requireMessageSize
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
@ -68,7 +69,8 @@ class AMQPClient(val targets: List<NetworkHostAndPort>,
|
|||||||
private val crlCheckSoftFail: Boolean,
|
private val crlCheckSoftFail: Boolean,
|
||||||
private val trace: Boolean = false,
|
private val trace: Boolean = false,
|
||||||
private val sharedThreadPool: EventLoopGroup? = null,
|
private val sharedThreadPool: EventLoopGroup? = null,
|
||||||
private val socksProxyConfig: SocksProxyConfig? = null) : AutoCloseable {
|
private val socksProxyConfig: SocksProxyConfig? = null,
|
||||||
|
private val maxMessageSize: Int) : AutoCloseable {
|
||||||
companion object {
|
companion object {
|
||||||
init {
|
init {
|
||||||
InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE)
|
InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE)
|
||||||
@ -118,17 +120,15 @@ class AMQPClient(val targets: List<NetworkHostAndPort>,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val closeListener = object : ChannelFutureListener {
|
private val closeListener = ChannelFutureListener { future ->
|
||||||
override fun operationComplete(future: ChannelFuture) {
|
log.info("Disconnected from $currentTarget")
|
||||||
log.info("Disconnected from $currentTarget")
|
future.channel()?.disconnect()
|
||||||
future.channel()?.disconnect()
|
clientChannel = null
|
||||||
clientChannel = null
|
if (!stopping) {
|
||||||
if (!stopping) {
|
workerGroup?.schedule({
|
||||||
workerGroup?.schedule({
|
nextTarget()
|
||||||
nextTarget()
|
restart()
|
||||||
restart()
|
}, retryInterval, TimeUnit.MILLISECONDS)
|
||||||
}, retryInterval, TimeUnit.MILLISECONDS)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,6 +228,7 @@ class AMQPClient(val targets: List<NetworkHostAndPort>,
|
|||||||
topic: String,
|
topic: String,
|
||||||
destinationLegalName: String,
|
destinationLegalName: String,
|
||||||
properties: Map<String, Any?>): SendableMessage {
|
properties: Map<String, Any?>): SendableMessage {
|
||||||
|
requireMessageSize(payload.size, maxMessageSize)
|
||||||
return SendableMessageImpl(payload, topic, destinationLegalName, currentTarget, properties)
|
return SendableMessageImpl(payload, topic, destinationLegalName, currentTarget, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import net.corda.core.utilities.contextLogger
|
|||||||
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
import net.corda.nodeapi.internal.protonwrapper.messages.ReceivedMessage
|
||||||
import net.corda.nodeapi.internal.protonwrapper.messages.SendableMessage
|
import net.corda.nodeapi.internal.protonwrapper.messages.SendableMessage
|
||||||
import net.corda.nodeapi.internal.protonwrapper.messages.impl.SendableMessageImpl
|
import net.corda.nodeapi.internal.protonwrapper.messages.impl.SendableMessageImpl
|
||||||
|
import net.corda.nodeapi.internal.requireMessageSize
|
||||||
import org.apache.qpid.proton.engine.Delivery
|
import org.apache.qpid.proton.engine.Delivery
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
@ -51,7 +52,8 @@ class AMQPServer(val hostName: String,
|
|||||||
private val keyStorePrivateKeyPassword: CharArray,
|
private val keyStorePrivateKeyPassword: CharArray,
|
||||||
private val trustStore: KeyStore,
|
private val trustStore: KeyStore,
|
||||||
private val crlCheckSoftFail: Boolean,
|
private val crlCheckSoftFail: Boolean,
|
||||||
private val trace: Boolean = false) : AutoCloseable {
|
private val trace: Boolean = false,
|
||||||
|
private val maxMessageSize: Int) : AutoCloseable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
init {
|
init {
|
||||||
@ -78,7 +80,8 @@ class AMQPServer(val hostName: String,
|
|||||||
keyStorePrivateKeyPassword: String,
|
keyStorePrivateKeyPassword: String,
|
||||||
trustStore: KeyStore,
|
trustStore: KeyStore,
|
||||||
crlCheckSoftFail: Boolean,
|
crlCheckSoftFail: Boolean,
|
||||||
trace: Boolean = false) : this(hostName, port, userName, password, keyStore, keyStorePrivateKeyPassword.toCharArray(), trustStore, crlCheckSoftFail, trace)
|
trace: Boolean = false,
|
||||||
|
maxMessageSize: Int) : this(hostName, port, userName, password, keyStore, keyStorePrivateKeyPassword.toCharArray(), trustStore, crlCheckSoftFail, trace, maxMessageSize)
|
||||||
|
|
||||||
private class ServerChannelInitializer(val parent: AMQPServer) : ChannelInitializer<SocketChannel>() {
|
private class ServerChannelInitializer(val parent: AMQPServer) : ChannelInitializer<SocketChannel>() {
|
||||||
private val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
|
private val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
|
||||||
@ -166,6 +169,7 @@ class AMQPServer(val hostName: String,
|
|||||||
destinationLegalName: String,
|
destinationLegalName: String,
|
||||||
destinationLink: NetworkHostAndPort,
|
destinationLink: NetworkHostAndPort,
|
||||||
properties: Map<String, Any?>): SendableMessage {
|
properties: Map<String, Any?>): SendableMessage {
|
||||||
|
requireMessageSize(payload.size, maxMessageSize)
|
||||||
val dest = InetSocketAddress(destinationLink.host, destinationLink.port)
|
val dest = InetSocketAddress(destinationLink.host, destinationLink.port)
|
||||||
require(dest in clientChannels.keys) {
|
require(dest in clientChannels.keys) {
|
||||||
"Destination not available"
|
"Destination not available"
|
||||||
|
@ -23,13 +23,7 @@ import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
|||||||
* servers from trying to instantiate any of them.
|
* servers from trying to instantiate any of them.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(kryoMagic,
|
|
||||||
SerializationDefaults.javaClass.classLoader,
|
|
||||||
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
|
|
||||||
emptyMap(),
|
|
||||||
true,
|
|
||||||
SerializationContext.UseCase.RPCClient,
|
|
||||||
null)
|
|
||||||
val AMQP_RPC_CLIENT_CONTEXT = SerializationContextImpl(amqpMagic,
|
val AMQP_RPC_CLIENT_CONTEXT = SerializationContextImpl(amqpMagic,
|
||||||
SerializationDefaults.javaClass.classLoader,
|
SerializationDefaults.javaClass.classLoader,
|
||||||
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
|
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
|
||||||
|
@ -124,8 +124,9 @@ open class SerializationFactoryImpl(
|
|||||||
val lookupKey = magic to target
|
val lookupKey = magic to target
|
||||||
return schemes.computeIfAbsent(lookupKey) {
|
return schemes.computeIfAbsent(lookupKey) {
|
||||||
registeredSchemes.filter { it.canDeserializeVersion(magic, target) }.forEach { return@computeIfAbsent it } // XXX: Not single?
|
registeredSchemes.filter { it.canDeserializeVersion(magic, target) }.forEach { return@computeIfAbsent it } // XXX: Not single?
|
||||||
logger.warn("Cannot find serialization scheme for: $lookupKey, registeredSchemes are: $registeredSchemes")
|
logger.warn("Cannot find serialization scheme for: [$lookupKey, " +
|
||||||
throw UnsupportedOperationException("Serialization scheme not supported.")
|
"${if (magic == amqpMagic) "AMQP" else if (magic == kryoMagic) "Kryo" else "UNKNOWN MAGIC"}] registeredSchemes are: $registeredSchemes")
|
||||||
|
throw UnsupportedOperationException("Serialization scheme $lookupKey not supported.")
|
||||||
} to magic
|
} to magic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,13 +27,6 @@ import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
|||||||
* MUST be kept separate!
|
* MUST be kept separate!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
val KRYO_RPC_SERVER_CONTEXT = SerializationContextImpl(kryoMagic,
|
|
||||||
SerializationDefaults.javaClass.classLoader,
|
|
||||||
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
|
|
||||||
emptyMap(),
|
|
||||||
true,
|
|
||||||
SerializationContext.UseCase.RPCServer,
|
|
||||||
null)
|
|
||||||
|
|
||||||
val AMQP_STORAGE_CONTEXT = SerializationContextImpl(amqpMagic,
|
val AMQP_STORAGE_CONTEXT = SerializationContextImpl(amqpMagic,
|
||||||
SerializationDefaults.javaClass.classLoader,
|
SerializationDefaults.javaClass.classLoader,
|
||||||
|
@ -15,6 +15,7 @@ package net.corda.nodeapi.internal.serialization.amqp
|
|||||||
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||||
import net.corda.core.cordapp.Cordapp
|
import net.corda.core.cordapp.Cordapp
|
||||||
import net.corda.core.internal.objectOrNewInstance
|
import net.corda.core.internal.objectOrNewInstance
|
||||||
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.utilities.ByteSequence
|
import net.corda.core.utilities.ByteSequence
|
||||||
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
||||||
@ -22,7 +23,6 @@ import net.corda.nodeapi.internal.serialization.DefaultWhitelist
|
|||||||
import net.corda.nodeapi.internal.serialization.MutableClassWhitelist
|
import net.corda.nodeapi.internal.serialization.MutableClassWhitelist
|
||||||
import net.corda.nodeapi.internal.serialization.SerializationScheme
|
import net.corda.nodeapi.internal.serialization.SerializationScheme
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.security.PublicKey
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
@ -128,6 +128,12 @@ abstract class AbstractAMQPSerializationScheme(
|
|||||||
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
|
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.properties[ContextPropertyKeys.SERIALIZERS]?.apply {
|
||||||
|
uncheckedCast<Any, List<CustomSerializer<out Any>>>(this).forEach {
|
||||||
|
factory.register(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -141,7 +147,9 @@ abstract class AbstractAMQPSerializationScheme(
|
|||||||
|
|
||||||
protected abstract fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory
|
protected abstract fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory
|
||||||
protected abstract fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory
|
protected abstract fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory
|
||||||
protected open val publicKeySerializer: CustomSerializer.Implements<PublicKey> = net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer
|
|
||||||
|
// Not used as a simple direct import to facilitate testing
|
||||||
|
open val publicKeySerializer : CustomSerializer<*> = net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer
|
||||||
|
|
||||||
private fun getSerializerFactory(context: SerializationContext): SerializerFactory {
|
private fun getSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
return serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) {
|
return serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) {
|
||||||
@ -172,52 +180,3 @@ abstract class AbstractAMQPSerializationScheme(
|
|||||||
|
|
||||||
protected fun canDeserializeVersion(magic: CordaSerializationMagic) = magic == amqpMagic
|
protected fun canDeserializeVersion(magic: CordaSerializationMagic) = magic == amqpMagic
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This will eventually cover server RPC as well and move to node module, but for now this is not implemented
|
|
||||||
class AMQPServerSerializationScheme(
|
|
||||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
|
||||||
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
|
||||||
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
|
||||||
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, ConcurrentHashMap())
|
|
||||||
|
|
||||||
constructor() : this(emptySet(), ConcurrentHashMap())
|
|
||||||
|
|
||||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
|
||||||
return canDeserializeVersion(magic) &&
|
|
||||||
(target == SerializationContext.UseCase.P2P || target == SerializationContext.UseCase.Storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This will eventually cover client RPC as well and move to client module, but for now this is not implemented
|
|
||||||
class AMQPClientSerializationScheme(
|
|
||||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
|
|
||||||
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
|
||||||
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
|
||||||
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, ConcurrentHashMap())
|
|
||||||
|
|
||||||
constructor() : this(emptySet(), ConcurrentHashMap())
|
|
||||||
|
|
||||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
|
||||||
return canDeserializeVersion(magic) &&
|
|
||||||
(target == SerializationContext.UseCase.P2P || target == SerializationContext.UseCase.Storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -295,7 +295,11 @@ open class SerializerFactory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun makeClassSerializer(clazz: Class<*>, type: Type, declaredType: Type): AMQPSerializer<Any> = serializersByType.computeIfAbsent(type) {
|
private fun makeClassSerializer(clazz: Class<*>, type: Type, declaredType: Type): AMQPSerializer<Any> = serializersByType.computeIfAbsent(type) {
|
||||||
if (isPrimitive(clazz)) {
|
if (clazz.isSynthetic) {
|
||||||
|
// Explicitly ban synthetic classes, we have no way of recreating them when deserializing. This also
|
||||||
|
// captures Lambda expressions and other anonymous functions
|
||||||
|
throw NotSerializableException(type.typeName)
|
||||||
|
} else if (isPrimitive(clazz)) {
|
||||||
AMQPPrimitiveSerializer(clazz)
|
AMQPPrimitiveSerializer(clazz)
|
||||||
} else {
|
} else {
|
||||||
findCustomSerializer(clazz, declaredType) ?: run {
|
findCustomSerializer(clazz, declaredType) ?: run {
|
||||||
|
@ -12,7 +12,6 @@ package net.corda.nodeapi.internal.serialization.amqp.custom
|
|||||||
|
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
|
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.custom.ClassSerializer.ClassProxy
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A serializer for [Class] that uses [ClassProxy] proxy object to write out
|
* A serializer for [Class] that uses [ClassProxy] proxy object to write out
|
||||||
|
@ -49,7 +49,7 @@ object InputStreamSerializer : CustomSerializer.Implements<InputStream>(InputStr
|
|||||||
|
|
||||||
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput,
|
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput,
|
||||||
context: SerializationContext
|
context: SerializationContext
|
||||||
): InputStream {
|
) : InputStream {
|
||||||
val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray
|
val bits = input.readObject(obj, schemas, ByteArray::class.java, context) as ByteArray
|
||||||
return bits.inputStream()
|
return bits.inputStream()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp.custom
|
||||||
|
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||||
|
import rx.Notification
|
||||||
|
|
||||||
|
class RxNotificationSerializer(
|
||||||
|
factory: SerializerFactory
|
||||||
|
) : CustomSerializer.Proxy<rx.Notification<*>, RxNotificationSerializer.Proxy>(
|
||||||
|
Notification::class.java,
|
||||||
|
Proxy::class.java,
|
||||||
|
factory
|
||||||
|
) {
|
||||||
|
data class Proxy(
|
||||||
|
val kind: Notification.Kind,
|
||||||
|
val t: Throwable?,
|
||||||
|
val value: Any?)
|
||||||
|
|
||||||
|
override fun toProxy(obj: Notification<*>) = Proxy(obj.kind, obj.throwable, obj.value)
|
||||||
|
|
||||||
|
override fun fromProxy(proxy: Proxy): Notification<*> {
|
||||||
|
return when (proxy.kind) {
|
||||||
|
Notification.Kind.OnCompleted -> Notification.createOnCompleted<Any>()
|
||||||
|
Notification.Kind.OnError -> Notification.createOnError<Any>(proxy.t)
|
||||||
|
Notification.Kind.OnNext -> Notification.createOnNext(proxy.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,6 @@ import com.esotericsoftware.kryo.io.Output
|
|||||||
import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
|
import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
|
||||||
import com.esotericsoftware.kryo.serializers.FieldSerializer
|
import com.esotericsoftware.kryo.serializers.FieldSerializer
|
||||||
import com.esotericsoftware.kryo.util.MapReferenceResolver
|
import com.esotericsoftware.kryo.util.MapReferenceResolver
|
||||||
import net.corda.core.concurrent.CordaFuture
|
|
||||||
import net.corda.core.contracts.PrivacySalt
|
import net.corda.core.contracts.PrivacySalt
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
@ -28,8 +27,6 @@ import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint
|
|||||||
import net.corda.core.serialization.SerializationContext.UseCase.Storage
|
import net.corda.core.serialization.SerializationContext.UseCase.Storage
|
||||||
import net.corda.core.serialization.SerializeAsTokenContext
|
import net.corda.core.serialization.SerializeAsTokenContext
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.core.toFuture
|
|
||||||
import net.corda.core.toObservable
|
|
||||||
import net.corda.core.transactions.*
|
import net.corda.core.transactions.*
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||||
@ -38,7 +35,6 @@ import net.corda.nodeapi.internal.serialization.CordaClassResolver
|
|||||||
import net.corda.nodeapi.internal.serialization.serializationContextKey
|
import net.corda.nodeapi.internal.serialization.serializationContextKey
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import rx.Observable
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
@ -57,39 +53,16 @@ import kotlin.reflect.jvm.isAccessible
|
|||||||
import kotlin.reflect.jvm.javaType
|
import kotlin.reflect.jvm.javaType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialization utilities, using the Kryo framework with a custom serialiser for immutable data classes and a dead
|
* Serialization utilities, using the Kryo framework with a custom serializer for immutable data classes and a dead
|
||||||
* simple, totally non-extensible binary (sub)format.
|
* simple, totally non-extensible binary (sub)format. Used exclusively within Corda for checkpointing flows as
|
||||||
*
|
* it will happily deserialise literally anything, including malicious streams that would reconstruct classes
|
||||||
* This is NOT what should be used in any final platform product, rather, the final state should be a precisely
|
* in invalid states and thus violating system invariants. In the context of checkpointing a Java stack, this is
|
||||||
* specified and standardised binary format with attention paid to anti-malleability, versioning and performance.
|
* absolutely the functionality we desire, for a stable binary wire format and persistence technology, we have
|
||||||
* FIX SBE is a potential candidate: it prioritises performance over convenience and was designed for HFT. Google
|
* the AMQP implementation.
|
||||||
* Protocol Buffers with a minor tightening to make field reordering illegal is another possibility.
|
|
||||||
*
|
|
||||||
* FIX SBE:
|
|
||||||
* https://real-logic.github.io/simple-binary-encoding/
|
|
||||||
* http://mechanical-sympathy.blogspot.co.at/2014/05/simple-binary-encoding.html
|
|
||||||
* Protocol buffers:
|
|
||||||
* https://developers.google.com/protocol-buffers/
|
|
||||||
*
|
|
||||||
* But for now we use Kryo to maximise prototyping speed.
|
|
||||||
*
|
|
||||||
* Note that this code ignores *ALL* concerns beyond convenience, in particular it ignores:
|
|
||||||
*
|
|
||||||
* - Performance
|
|
||||||
* - Security
|
|
||||||
*
|
|
||||||
* This code will happily deserialise literally anything, including malicious streams that would reconstruct classes
|
|
||||||
* in invalid states, thus violating system invariants. It isn't designed to handle malicious streams and therefore,
|
|
||||||
* isn't usable beyond the prototyping stage. But that's fine: we can revisit serialisation technologies later after
|
|
||||||
* a formal evaluation process.
|
|
||||||
*
|
|
||||||
* We now distinguish between internal, storage related Kryo and external, network facing Kryo. We presently use
|
|
||||||
* some non-whitelisted classes as part of internal storage.
|
|
||||||
* TODO: eliminate internal, storage related whitelist issues, such as private keys in blob storage.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A serialiser that avoids writing the wrapper class to the byte stream, thus ensuring [SerializedBytes] is a pure
|
* A serializer that avoids writing the wrapper class to the byte stream, thus ensuring [SerializedBytes] is a pure
|
||||||
* type safety hack.
|
* type safety hack.
|
||||||
*/
|
*/
|
||||||
object SerializedBytesSerializer : Serializer<SerializedBytes<Any>>() {
|
object SerializedBytesSerializer : Serializer<SerializedBytes<Any>>() {
|
||||||
@ -405,44 +378,6 @@ open class CordaKryo(classResolver: ClassResolver) : Kryo(classResolver, MapRefe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The Kryo used for the RPC wire protocol.
|
|
||||||
*/
|
|
||||||
// Every type in the wire protocol is listed here explicitly.
|
|
||||||
// This is annoying to write out, but will make it easier to formalise the wire protocol when the time comes,
|
|
||||||
// because we can see everything we're using in one place.
|
|
||||||
class RPCKryo(observableSerializer: Serializer<Observable<*>>, serializationContext: SerializationContext) : CordaKryo(CordaClassResolver(serializationContext)) {
|
|
||||||
init {
|
|
||||||
DefaultKryoCustomizer.customize(this)
|
|
||||||
|
|
||||||
// RPC specific classes
|
|
||||||
register(InputStream::class.java, InputStreamSerializer)
|
|
||||||
register(Observable::class.java, observableSerializer)
|
|
||||||
register(CordaFuture::class,
|
|
||||||
read = { kryo, input -> observableSerializer.read(kryo, input, Observable::class.java).toFuture() },
|
|
||||||
write = { kryo, output, obj -> observableSerializer.write(kryo, output, obj.toObservable()) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getRegistration(type: Class<*>): Registration {
|
|
||||||
if (Observable::class.java != type && Observable::class.java.isAssignableFrom(type)) {
|
|
||||||
return super.getRegistration(Observable::class.java)
|
|
||||||
}
|
|
||||||
if (InputStream::class.java != type && InputStream::class.java.isAssignableFrom(type)) {
|
|
||||||
return super.getRegistration(InputStream::class.java)
|
|
||||||
}
|
|
||||||
if (CordaFuture::class.java != type && CordaFuture::class.java.isAssignableFrom(type)) {
|
|
||||||
return super.getRegistration(CordaFuture::class.java)
|
|
||||||
}
|
|
||||||
type.requireExternal("RPC not allowed to deserialise internal classes")
|
|
||||||
return super.getRegistration(type)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Class<*>.requireExternal(msg: String) {
|
|
||||||
require(!name.startsWith("net.corda.node.") && ".internal" !in name) { "$msg: $name" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T : Any> Kryo.register(
|
inline fun <T : Any> Kryo.register(
|
||||||
type: KClass<T>,
|
type: KClass<T>,
|
||||||
crossinline read: (Kryo, Input) -> T,
|
crossinline read: (Kryo, Input) -> T,
|
||||||
|
@ -14,13 +14,13 @@ import com.google.common.collect.Maps;
|
|||||||
import net.corda.core.serialization.SerializationContext;
|
import net.corda.core.serialization.SerializationContext;
|
||||||
import net.corda.core.serialization.SerializationFactory;
|
import net.corda.core.serialization.SerializationFactory;
|
||||||
import net.corda.core.serialization.SerializedBytes;
|
import net.corda.core.serialization.SerializedBytes;
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.CordaClosureBlacklistSerializer;
|
import net.corda.nodeapi.internal.serialization.amqp.SchemaKt;
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.KryoSerializationSchemeKt;
|
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule;
|
import net.corda.testing.core.SerializationEnvironmentRule;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.NotSerializableException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
@ -43,20 +43,17 @@ public final class ForbiddenLambdaSerializationTests {
|
|||||||
@Test
|
@Test
|
||||||
public final void serialization_fails_for_serializable_java_lambdas() {
|
public final void serialization_fails_for_serializable_java_lambdas() {
|
||||||
contexts.forEach(ctx -> {
|
contexts.forEach(ctx -> {
|
||||||
SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(),
|
SerializationContext context = new SerializationContextImpl(SchemaKt.getAmqpMagic(),
|
||||||
this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx, null);
|
this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx, null);
|
||||||
String value = "Hey";
|
String value = "Hey";
|
||||||
Callable<String> target = (Callable<String> & Serializable) () -> value;
|
Callable<String> target = (Callable<String> & Serializable) () -> value;
|
||||||
|
|
||||||
Throwable throwable = catchThrowable(() -> serialize(target, context));
|
Throwable throwable = catchThrowable(() -> serialize(target, context));
|
||||||
|
|
||||||
assertThat(throwable).isNotNull();
|
assertThat(throwable)
|
||||||
assertThat(throwable).isInstanceOf(IllegalArgumentException.class);
|
.isNotNull()
|
||||||
if (ctx != SerializationContext.UseCase.RPCServer && ctx != SerializationContext.UseCase.Storage) {
|
.isInstanceOf(NotSerializableException.class)
|
||||||
assertThat(throwable).hasMessage(CordaClosureBlacklistSerializer.ERROR_MESSAGE);
|
.hasMessageContaining(getClass().getName());
|
||||||
} else {
|
|
||||||
assertThat(throwable).hasMessageContaining("RPC not allowed to deserialise internal classes");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,21 +61,17 @@ public final class ForbiddenLambdaSerializationTests {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public final void serialization_fails_for_not_serializable_java_lambdas() {
|
public final void serialization_fails_for_not_serializable_java_lambdas() {
|
||||||
contexts.forEach(ctx -> {
|
contexts.forEach(ctx -> {
|
||||||
SerializationContext context = new SerializationContextImpl(KryoSerializationSchemeKt.getKryoMagic(),
|
SerializationContext context = new SerializationContextImpl(SchemaKt.getAmqpMagic(),
|
||||||
this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx, null);
|
this.getClass().getClassLoader(), AllWhitelist.INSTANCE, Maps.newHashMap(), true, ctx, null);
|
||||||
String value = "Hey";
|
String value = "Hey";
|
||||||
Callable<String> target = () -> value;
|
Callable<String> target = () -> value;
|
||||||
|
|
||||||
Throwable throwable = catchThrowable(() -> serialize(target, context));
|
Throwable throwable = catchThrowable(() -> serialize(target, context));
|
||||||
|
|
||||||
assertThat(throwable).isNotNull();
|
assertThat(throwable)
|
||||||
assertThat(throwable).isInstanceOf(IllegalArgumentException.class);
|
.isNotNull()
|
||||||
assertThat(throwable).isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(NotSerializableException.class)
|
||||||
if (ctx != SerializationContext.UseCase.RPCServer && ctx != SerializationContext.UseCase.Storage) {
|
.hasMessageContaining(getClass().getName());
|
||||||
assertThat(throwable).hasMessage(CordaClosureBlacklistSerializer.ERROR_MESSAGE);
|
|
||||||
} else {
|
|
||||||
assertThat(throwable).hasMessageContaining("RPC not allowed to deserialise internal classes");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,13 +18,13 @@ import net.corda.core.internal.div
|
|||||||
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.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.node.serialization.kryo.KryoServerSerializationScheme
|
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import net.corda.nodeapi.internal.createDevKeyStores
|
import net.corda.nodeapi.internal.createDevKeyStores
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
|
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
|
||||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
import net.corda.testing.core.TestIdentity
|
import net.corda.testing.core.TestIdentity
|
||||||
@ -345,8 +345,8 @@ class X509UtilitiesTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `serialize - deserialize X509Certififcate`() {
|
fun `serialize - deserialize X509Certififcate`() {
|
||||||
val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
|
val factory = SerializationFactoryImpl().apply { registerScheme(AMQPServerSerializationScheme()) }
|
||||||
val context = SerializationContextImpl(kryoMagic,
|
val context = SerializationContextImpl(amqpMagic,
|
||||||
javaClass.classLoader,
|
javaClass.classLoader,
|
||||||
AllWhitelist,
|
AllWhitelist,
|
||||||
emptyMap(),
|
emptyMap(),
|
||||||
@ -361,8 +361,8 @@ class X509UtilitiesTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `serialize - deserialize X509CertPath`() {
|
fun `serialize - deserialize X509CertPath`() {
|
||||||
val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
|
val factory = SerializationFactoryImpl().apply { registerScheme(AMQPServerSerializationScheme()) }
|
||||||
val context = SerializationContextImpl(kryoMagic,
|
val context = SerializationContextImpl(amqpMagic,
|
||||||
javaClass.classLoader,
|
javaClass.classLoader,
|
||||||
AllWhitelist,
|
AllWhitelist,
|
||||||
emptyMap(),
|
emptyMap(),
|
||||||
|
@ -40,6 +40,7 @@ import net.corda.core.serialization.SerializationContext
|
|||||||
import net.corda.core.serialization.SerializationFactory
|
import net.corda.core.serialization.SerializationFactory
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
|
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||||
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
|
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
|
||||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
|
@ -15,6 +15,7 @@ import com.esotericsoftware.kryo.KryoException
|
|||||||
import com.esotericsoftware.kryo.KryoSerializable
|
import com.esotericsoftware.kryo.KryoSerializable
|
||||||
import com.esotericsoftware.kryo.io.Input
|
import com.esotericsoftware.kryo.io.Input
|
||||||
import com.esotericsoftware.kryo.io.Output
|
import com.esotericsoftware.kryo.io.Output
|
||||||
|
import com.esotericsoftware.kryo.pool.KryoPool
|
||||||
import com.google.common.primitives.Ints
|
import com.google.common.primitives.Ints
|
||||||
import com.nhaarman.mockito_kotlin.doReturn
|
import com.nhaarman.mockito_kotlin.doReturn
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
@ -22,9 +23,9 @@ import net.corda.core.contracts.PrivacySalt
|
|||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.internal.FetchDataFlow
|
import net.corda.core.internal.FetchDataFlow
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.*
|
||||||
|
import net.corda.core.utilities.ByteSequence
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.sequence
|
import net.corda.core.utilities.sequence
|
||||||
import net.corda.node.serialization.kryo.KryoServerSerializationScheme
|
|
||||||
import net.corda.node.services.persistence.NodeAttachmentService
|
import net.corda.node.services.persistence.NodeAttachmentService
|
||||||
import net.corda.nodeapi.internal.serialization.*
|
import net.corda.nodeapi.internal.serialization.*
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
@ -44,6 +45,17 @@ import java.time.Instant
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class TestScheme : AbstractKryoSerializationScheme() {
|
||||||
|
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
||||||
|
return magic == kryoMagic && target != SerializationContext.UseCase.RPCClient
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rpcClientKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun rpcServerKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@RunWith(Parameterized::class)
|
@RunWith(Parameterized::class)
|
||||||
class KryoTests(private val compression: CordaSerializationEncoding?) {
|
class KryoTests(private val compression: CordaSerializationEncoding?) {
|
||||||
companion object {
|
companion object {
|
||||||
@ -58,7 +70,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
|
factory = SerializationFactoryImpl().apply { registerScheme(TestScheme()) }
|
||||||
context = SerializationContextImpl(kryoMagic,
|
context = SerializationContextImpl(kryoMagic,
|
||||||
javaClass.classLoader,
|
javaClass.classLoader,
|
||||||
AllWhitelist,
|
AllWhitelist,
|
||||||
@ -86,11 +98,12 @@ class KryoTests(private val compression: CordaSerializationEncoding?) {
|
|||||||
assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null))
|
assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `serialised form is stable when the same object instance is added to the deserialised object graph`() {
|
fun `serialised form is stable when the same object instance is added to the deserialised object graph`() {
|
||||||
val noReferencesContext = context.withoutReferences()
|
val noReferencesContext = context.withoutReferences()
|
||||||
val obj = Ints.toByteArray(0x01234567).sequence()
|
val obj : ByteSequence = Ints.toByteArray(0x01234567).sequence()
|
||||||
val originalList = arrayListOf(obj)
|
val originalList : ArrayList<ByteSequence> = arrayListOf(obj)
|
||||||
val deserialisedList = originalList.serialize(factory, noReferencesContext).deserialize(factory, noReferencesContext)
|
val deserialisedList = originalList.serialize(factory, noReferencesContext).deserialize(factory, noReferencesContext)
|
||||||
originalList += obj
|
originalList += obj
|
||||||
deserialisedList += obj
|
deserialisedList += obj
|
||||||
@ -278,7 +291,7 @@ class KryoTests(private val compression: CordaSerializationEncoding?) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Tmp()
|
Tmp()
|
||||||
val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
|
val factory = SerializationFactoryImpl().apply { registerScheme(TestScheme()) }
|
||||||
val context = SerializationContextImpl(kryoMagic,
|
val context = SerializationContextImpl(kryoMagic,
|
||||||
javaClass.classLoader,
|
javaClass.classLoader,
|
||||||
AllWhitelist,
|
AllWhitelist,
|
||||||
|
@ -12,6 +12,7 @@ package net.corda.node
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
@ -21,6 +22,7 @@ import net.corda.core.messaging.startFlow
|
|||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.internal.NodeStartup
|
import net.corda.node.internal.NodeStartup
|
||||||
import net.corda.node.services.Permissions.Companion.startFlow
|
import net.corda.node.services.Permissions.Companion.startFlow
|
||||||
|
import net.corda.nodeapi.exceptions.InternalNodeException
|
||||||
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
@ -51,8 +53,11 @@ class BootTests : IntegrationTest() {
|
|||||||
fun `java deserialization is disabled`() {
|
fun `java deserialization is disabled`() {
|
||||||
driver {
|
driver {
|
||||||
val user = User("u", "p", setOf(startFlow<ObjectInputStreamFlow>()))
|
val user = User("u", "p", setOf(startFlow<ObjectInputStreamFlow>()))
|
||||||
val future = CordaRPCClient(startNode(rpcUsers = listOf(user)).getOrThrow().rpcAddress).start(user.username, user.password).proxy.startFlow(::ObjectInputStreamFlow).returnValue
|
val future = CordaRPCClient(startNode(rpcUsers = listOf(user)).getOrThrow().rpcAddress).
|
||||||
assertThatThrownBy { future.getOrThrow() }.isInstanceOf(InvalidClassException::class.java).hasMessage("filter status: REJECTED")
|
start(user.username, user.password).proxy.startFlow(::ObjectInputStreamFlow).returnValue
|
||||||
|
assertThatThrownBy { future.getOrThrow() }
|
||||||
|
.isInstanceOf(CordaRuntimeException::class.java)
|
||||||
|
.hasMessageContaining(InternalNodeException.defaultMessage())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,6 +272,7 @@ class AMQPBridgeTest {
|
|||||||
return Triple(artemisServer, artemisClient, bridgeManager)
|
return Triple(artemisServer, artemisClient, bridgeManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun createArtemisReceiver(targetAdress: NetworkHostAndPort, workingDir: String): Pair<ArtemisMessagingServer, ArtemisMessagingClient> {
|
private fun createArtemisReceiver(targetAdress: NetworkHostAndPort, workingDir: String): Pair<ArtemisMessagingServer, ArtemisMessagingClient> {
|
||||||
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||||
doReturn(temporaryFolder.root.toPath() / workingDir).whenever(it).baseDirectory
|
doReturn(temporaryFolder.root.toPath() / workingDir).whenever(it).baseDirectory
|
||||||
@ -293,8 +294,7 @@ class AMQPBridgeTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createAMQPServer(maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPServer {
|
||||||
private fun createAMQPServer(): AMQPServer {
|
|
||||||
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||||
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
|
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
|
||||||
doReturn(BOB_NAME).whenever(it).myLegalName
|
doReturn(BOB_NAME).whenever(it).myLegalName
|
||||||
@ -311,7 +311,8 @@ class AMQPBridgeTest {
|
|||||||
serverConfig.keyStorePassword,
|
serverConfig.keyStorePassword,
|
||||||
serverConfig.loadTrustStore().internal,
|
serverConfig.loadTrustStore().internal,
|
||||||
crlCheckSoftFail = true,
|
crlCheckSoftFail = true,
|
||||||
trace = true
|
trace = true,
|
||||||
|
maxMessageSize = maxMessageSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,10 +19,7 @@ import net.corda.nodeapi.internal.crypto.*
|
|||||||
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
|
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
|
||||||
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient
|
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPClient
|
||||||
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPServer
|
import net.corda.nodeapi.internal.protonwrapper.netty.AMQPServer
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.core.BOB_NAME
|
|
||||||
import net.corda.testing.core.CHARLIE_NAME
|
|
||||||
import net.corda.testing.core.freePort
|
|
||||||
import net.corda.testing.internal.DEV_INTERMEDIATE_CA
|
import net.corda.testing.internal.DEV_INTERMEDIATE_CA
|
||||||
import net.corda.testing.internal.DEV_ROOT_CA
|
import net.corda.testing.internal.DEV_ROOT_CA
|
||||||
import net.corda.testing.internal.rigorousMock
|
import net.corda.testing.internal.rigorousMock
|
||||||
@ -272,7 +269,8 @@ class CertificateRevocationListNodeTests {
|
|||||||
private fun createClient(targetPort: Int,
|
private fun createClient(targetPort: Int,
|
||||||
crlCheckSoftFail: Boolean,
|
crlCheckSoftFail: Boolean,
|
||||||
nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl",
|
nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl",
|
||||||
tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl"): Pair<AMQPClient, X509Certificate> {
|
tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl",
|
||||||
|
maxMessageSize: Int = MAX_MESSAGE_SIZE): Pair<AMQPClient, X509Certificate> {
|
||||||
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||||
doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory
|
doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory
|
||||||
doReturn(BOB_NAME).whenever(it).myLegalName
|
doReturn(BOB_NAME).whenever(it).myLegalName
|
||||||
@ -292,13 +290,15 @@ class CertificateRevocationListNodeTests {
|
|||||||
clientKeystore,
|
clientKeystore,
|
||||||
clientConfig.keyStorePassword,
|
clientConfig.keyStorePassword,
|
||||||
clientTruststore,
|
clientTruststore,
|
||||||
crlCheckSoftFail), nodeCert)
|
crlCheckSoftFail,
|
||||||
|
maxMessageSize = maxMessageSize), nodeCert)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME,
|
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME,
|
||||||
crlCheckSoftFail: Boolean,
|
crlCheckSoftFail: Boolean,
|
||||||
nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl",
|
nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl",
|
||||||
tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl"): Pair<AMQPServer, X509Certificate> {
|
tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl",
|
||||||
|
maxMessageSize: Int = MAX_MESSAGE_SIZE): Pair<AMQPServer, X509Certificate> {
|
||||||
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||||
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
|
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
|
||||||
doReturn(name).whenever(it).myLegalName
|
doReturn(name).whenever(it).myLegalName
|
||||||
@ -318,7 +318,8 @@ class CertificateRevocationListNodeTests {
|
|||||||
serverKeystore,
|
serverKeystore,
|
||||||
serverConfig.keyStorePassword,
|
serverConfig.keyStorePassword,
|
||||||
serverTruststore,
|
serverTruststore,
|
||||||
crlCheckSoftFail), nodeCert)
|
crlCheckSoftFail,
|
||||||
|
maxMessageSize = maxMessageSize), nodeCert)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SSLConfiguration.recreateNodeCaAndTlsCertificates(nodeCaCrlDistPoint: String, tlsCrlDistPoint: String?): X509Certificate {
|
private fun SSLConfiguration.recreateNodeCaAndTlsCertificates(nodeCaCrlDistPoint: String, tlsCrlDistPoint: String?): X509Certificate {
|
||||||
|
@ -34,6 +34,7 @@ import net.corda.testing.core.*
|
|||||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||||
import net.corda.testing.internal.rigorousMock
|
import net.corda.testing.internal.rigorousMock
|
||||||
import org.apache.activemq.artemis.api.core.RoutingType
|
import org.apache.activemq.artemis.api.core.RoutingType
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -91,7 +92,8 @@ class ProtonWrapperTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `AMPQ Client fails to connect when crl soft fail check is disabled`() {
|
fun `AMPQ Client fails to connect when crl soft fail check is disabled`() {
|
||||||
val amqpServer = createServer(serverPort, CordaX500Name("Rogue 1", "London", "GB"), false)
|
val amqpServer = createServer(serverPort, CordaX500Name("Rogue 1", "London", "GB"),
|
||||||
|
maxMessageSize = MAX_MESSAGE_SIZE, crlCheckSoftFail = false)
|
||||||
amqpServer.use {
|
amqpServer.use {
|
||||||
amqpServer.start()
|
amqpServer.start()
|
||||||
val amqpClient = createClient()
|
val amqpClient = createClient()
|
||||||
@ -283,6 +285,52 @@ class ProtonWrapperTests {
|
|||||||
server.stop()
|
server.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Send a message larger then maxMessageSize from AMQP to Artemis inbox`() {
|
||||||
|
val maxMessageSize = 100_000
|
||||||
|
val (server, artemisClient) = createArtemisServerAndClient(maxMessageSize)
|
||||||
|
val amqpClient = createClient(maxMessageSize)
|
||||||
|
val clientConnected = amqpClient.onConnection.toFuture()
|
||||||
|
amqpClient.start()
|
||||||
|
assertEquals(true, clientConnected.get().connected)
|
||||||
|
assertEquals(CHARLIE_NAME, CordaX500Name.build(clientConnected.get().remoteCert!!.subjectX500Principal))
|
||||||
|
val artemis = artemisClient.started!!
|
||||||
|
val sendAddress = P2P_PREFIX + "Test"
|
||||||
|
artemis.session.createQueue(sendAddress, RoutingType.ANYCAST, "queue", true)
|
||||||
|
val consumer = artemis.session.createConsumer("queue")
|
||||||
|
|
||||||
|
val testProperty = mutableMapOf<String, Any?>()
|
||||||
|
testProperty["TestProp"] = "1"
|
||||||
|
|
||||||
|
// Send normal message.
|
||||||
|
val testData = ByteArray(maxMessageSize)
|
||||||
|
val message = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty)
|
||||||
|
amqpClient.write(message)
|
||||||
|
assertEquals(MessageStatus.Acknowledged, message.onComplete.get())
|
||||||
|
val received = consumer.receive()
|
||||||
|
assertEquals("1", received.getStringProperty("TestProp"))
|
||||||
|
assertArrayEquals(testData, ByteArray(received.bodySize).apply { received.bodyBuffer.readBytes(this) })
|
||||||
|
|
||||||
|
// Send message larger then max message size.
|
||||||
|
val largeData = ByteArray(maxMessageSize + 1)
|
||||||
|
// Create message will fail.
|
||||||
|
assertThatThrownBy {
|
||||||
|
amqpClient.createMessage(largeData, sendAddress, CHARLIE_NAME.toString(), testProperty)
|
||||||
|
}.hasMessageContaining("Message exceeds maxMessageSize network parameter")
|
||||||
|
|
||||||
|
// Send normal message again to confirm the large message didn't reach the server and client is not killed by the message.
|
||||||
|
val message2 = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty)
|
||||||
|
amqpClient.write(message2)
|
||||||
|
assertEquals(MessageStatus.Acknowledged, message2.onComplete.get())
|
||||||
|
val received2 = consumer.receive()
|
||||||
|
assertEquals("1", received2.getStringProperty("TestProp"))
|
||||||
|
assertArrayEquals(testData, ByteArray(received2.bodySize).apply { received2.bodyBuffer.readBytes(this) })
|
||||||
|
|
||||||
|
amqpClient.stop()
|
||||||
|
artemisClient.stop()
|
||||||
|
server.stop()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `shared AMQPClient threadpool tests`() {
|
fun `shared AMQPClient threadpool tests`() {
|
||||||
val amqpServer = createServer(serverPort)
|
val amqpServer = createServer(serverPort)
|
||||||
@ -333,7 +381,7 @@ class ProtonWrapperTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createArtemisServerAndClient(): Pair<ArtemisMessagingServer, ArtemisMessagingClient> {
|
private fun createArtemisServerAndClient(maxMessageSize: Int = MAX_MESSAGE_SIZE): Pair<ArtemisMessagingServer, ArtemisMessagingClient> {
|
||||||
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||||
doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory
|
doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory
|
||||||
doReturn(CHARLIE_NAME).whenever(it).myLegalName
|
doReturn(CHARLIE_NAME).whenever(it).myLegalName
|
||||||
@ -347,14 +395,14 @@ class ProtonWrapperTests {
|
|||||||
}
|
}
|
||||||
artemisConfig.configureWithDevSSLCertificate()
|
artemisConfig.configureWithDevSSLCertificate()
|
||||||
|
|
||||||
val server = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", artemisPort), MAX_MESSAGE_SIZE)
|
val server = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", artemisPort), maxMessageSize)
|
||||||
val client = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", artemisPort), MAX_MESSAGE_SIZE)
|
val client = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", artemisPort), maxMessageSize)
|
||||||
server.start()
|
server.start()
|
||||||
client.start()
|
client.start()
|
||||||
return Pair(server, client)
|
return Pair(server, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createClient(): AMQPClient {
|
private fun createClient(maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPClient {
|
||||||
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||||
doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory
|
doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory
|
||||||
doReturn(BOB_NAME).whenever(it).myLegalName
|
doReturn(BOB_NAME).whenever(it).myLegalName
|
||||||
@ -376,10 +424,11 @@ class ProtonWrapperTests {
|
|||||||
clientKeystore,
|
clientKeystore,
|
||||||
clientConfig.keyStorePassword,
|
clientConfig.keyStorePassword,
|
||||||
clientTruststore,
|
clientTruststore,
|
||||||
true)
|
true,
|
||||||
|
maxMessageSize = maxMessageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int): AMQPClient {
|
private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int, maxMessageSize: Int = MAX_MESSAGE_SIZE): AMQPClient {
|
||||||
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||||
doReturn(temporaryFolder.root.toPath() / "client_%$id").whenever(it).baseDirectory
|
doReturn(temporaryFolder.root.toPath() / "client_%$id").whenever(it).baseDirectory
|
||||||
doReturn(CordaX500Name(null, "client $id", "Corda", "London", null, "GB")).whenever(it).myLegalName
|
doReturn(CordaX500Name(null, "client $id", "Corda", "London", null, "GB")).whenever(it).myLegalName
|
||||||
@ -400,10 +449,14 @@ class ProtonWrapperTests {
|
|||||||
clientConfig.keyStorePassword,
|
clientConfig.keyStorePassword,
|
||||||
clientTruststore,
|
clientTruststore,
|
||||||
true,
|
true,
|
||||||
sharedThreadPool = sharedEventGroup)
|
sharedThreadPool = sharedEventGroup,
|
||||||
|
maxMessageSize = maxMessageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME, crlCheckSoftFail: Boolean = true): AMQPServer {
|
|
||||||
|
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME, maxMessageSize: Int = MAX_MESSAGE_SIZE,
|
||||||
|
crlCheckSoftFail: Boolean = true
|
||||||
|
): AMQPServer {
|
||||||
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||||
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
|
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
|
||||||
doReturn(name).whenever(it).myLegalName
|
doReturn(name).whenever(it).myLegalName
|
||||||
@ -423,6 +476,7 @@ class ProtonWrapperTests {
|
|||||||
serverKeystore,
|
serverKeystore,
|
||||||
serverConfig.keyStorePassword,
|
serverConfig.keyStorePassword,
|
||||||
serverTruststore,
|
serverTruststore,
|
||||||
crlCheckSoftFail = true)
|
crlCheckSoftFail = true,
|
||||||
|
maxMessageSize = maxMessageSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,12 +45,12 @@ class RpcExceptionHandlingTest : IntegrationTest() {
|
|||||||
|
|
||||||
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||||
|
|
||||||
assertThatCode { node.rpc.startFlow(::Flow).returnValue.getOrThrow() }
|
assertThatCode { node.rpc.startFlow(::Flow).returnValue.getOrThrow() }.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
||||||
.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
|
||||||
assertThat(exception).hasNoCause()
|
assertThat(exception).hasNoCause()
|
||||||
assertThat(exception.stackTrace).isEmpty()
|
assertThat(exception.stackTrace).isEmpty()
|
||||||
assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage())
|
assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,12 +61,12 @@ class RpcExceptionHandlingTest : IntegrationTest() {
|
|||||||
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
val node = startNode(NodeParameters(rpcUsers = users)).getOrThrow()
|
||||||
val clientRelevantMessage = "This is for the players!"
|
val clientRelevantMessage = "This is for the players!"
|
||||||
|
|
||||||
assertThatCode { node.rpc.startFlow(::ClientRelevantErrorFlow, clientRelevantMessage).returnValue.getOrThrow() }
|
assertThatCode { node.rpc.startFlow(::ClientRelevantErrorFlow, clientRelevantMessage).returnValue.getOrThrow() }.isInstanceOfSatisfying(ClientRelevantException::class.java) { exception ->
|
||||||
.isInstanceOfSatisfying(ClientRelevantException::class.java) { exception ->
|
|
||||||
assertThat(exception).hasNoCause()
|
assertThat(exception).hasNoCause()
|
||||||
assertThat(exception.stackTrace).isEmpty()
|
assertThat(exception.stackTrace).isEmpty()
|
||||||
assertThat(exception.message).isEqualTo(clientRelevantMessage)
|
assertThat(exception.message).isEqualTo(clientRelevantMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,10 +93,11 @@ class RpcExceptionHandlingTest : IntegrationTest() {
|
|||||||
|
|
||||||
assertThatCode { nodeA.rpc.startFlow(::InitFlow, nodeB.nodeInfo.singleIdentity()).returnValue.getOrThrow() }
|
assertThatCode { nodeA.rpc.startFlow(::InitFlow, nodeB.nodeInfo.singleIdentity()).returnValue.getOrThrow() }
|
||||||
.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
.isInstanceOfSatisfying(InternalNodeException::class.java) { exception ->
|
||||||
assertThat(exception).hasNoCause()
|
|
||||||
assertThat(exception.stackTrace).isEmpty()
|
assertThat(exception).hasNoCause()
|
||||||
assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage())
|
assertThat(exception.stackTrace).isEmpty()
|
||||||
}
|
assertThat(exception.message).isEqualTo(InternalNodeException.defaultMessage())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
package net.corda.node.internal
|
package net.corda.node.internal
|
||||||
|
|
||||||
import com.codahale.metrics.JmxReporter
|
import com.codahale.metrics.JmxReporter
|
||||||
import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme
|
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.internal.Emoji
|
import net.corda.core.internal.Emoji
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
@ -36,6 +36,7 @@ import net.corda.node.internal.artemis.BrokerAddresses
|
|||||||
import net.corda.node.internal.cordapp.CordappLoader
|
import net.corda.node.internal.cordapp.CordappLoader
|
||||||
import net.corda.node.internal.security.RPCSecurityManagerImpl
|
import net.corda.node.internal.security.RPCSecurityManagerImpl
|
||||||
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
|
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
|
||||||
|
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||||
import net.corda.node.serialization.kryo.KryoServerSerializationScheme
|
import net.corda.node.serialization.kryo.KryoServerSerializationScheme
|
||||||
import net.corda.node.services.api.NodePropertiesStore
|
import net.corda.node.services.api.NodePropertiesStore
|
||||||
import net.corda.node.services.api.SchemaService
|
import net.corda.node.services.api.SchemaService
|
||||||
@ -52,7 +53,6 @@ import net.corda.nodeapi.internal.addShutdownHook
|
|||||||
import net.corda.nodeapi.internal.bridging.BridgeControlListener
|
import net.corda.nodeapi.internal.bridging.BridgeControlListener
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.serialization.*
|
import net.corda.nodeapi.internal.serialization.*
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import rx.Scheduler
|
import rx.Scheduler
|
||||||
@ -108,14 +108,14 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
const val scanPackagesSeparator = ","
|
const val scanPackagesSeparator = ","
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
protected fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader {
|
private fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader {
|
||||||
|
|
||||||
return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages ->
|
return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages ->
|
||||||
CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator), versionInfo)
|
CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator), versionInfo)
|
||||||
} ?: CordappLoader.createDefault(configuration.baseDirectory, versionInfo)
|
} ?: CordappLoader.createDefault(configuration.baseDirectory, versionInfo)
|
||||||
}
|
}
|
||||||
|
// TODO: make this configurable.
|
||||||
// TODO Wire up maxMessageSize
|
const val MAX_RPC_MESSAGE_SIZE = 10485760
|
||||||
const val MAX_FILE_SIZE = 10485760
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val log: Logger get() = staticLog
|
override val log: Logger get() = staticLog
|
||||||
@ -185,7 +185,7 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
|
|
||||||
if (!configuration.messagingServerExternal) {
|
if (!configuration.messagingServerExternal) {
|
||||||
val brokerBindAddress = configuration.messagingServerAddress ?: NetworkHostAndPort("0.0.0.0", configuration.p2pAddress.port)
|
val brokerBindAddress = configuration.messagingServerAddress ?: NetworkHostAndPort("0.0.0.0", configuration.p2pAddress.port)
|
||||||
messageBroker = ArtemisMessagingServer(configuration, brokerBindAddress, MAX_FILE_SIZE)
|
messageBroker = ArtemisMessagingServer(configuration, brokerBindAddress, networkParameters.maxMessageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
val serverAddress = configuration.messagingServerAddress ?: NetworkHostAndPort("localhost", configuration.p2pAddress.port)
|
val serverAddress = configuration.messagingServerAddress ?: NetworkHostAndPort("localhost", configuration.p2pAddress.port)
|
||||||
@ -194,18 +194,21 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
} else {
|
} else {
|
||||||
startLocalRpcBroker()
|
startLocalRpcBroker()
|
||||||
}
|
}
|
||||||
|
|
||||||
val advertisedAddress = info.addresses.single()
|
val advertisedAddress = info.addresses.single()
|
||||||
val externalBridge = configuration.enterpriseConfiguration.externalBridge
|
val externalBridge = configuration.enterpriseConfiguration.externalBridge
|
||||||
if (externalBridge == null || !externalBridge) {
|
if (externalBridge == null || !externalBridge) {
|
||||||
bridgeControlListener = BridgeControlListener(configuration, serverAddress, /*networkParameters.maxMessageSize*/MAX_FILE_SIZE)
|
bridgeControlListener = BridgeControlListener(configuration, serverAddress, networkParameters.maxMessageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
printBasicNodeInfo("Advertised P2P messaging addresses", info.addresses.joinToString())
|
printBasicNodeInfo("Advertised P2P messaging addresses", info.addresses.joinToString())
|
||||||
|
|
||||||
val rpcServerConfiguration = RPCServerConfiguration.default.copy(
|
val rpcServerConfiguration = RPCServerConfiguration.default.copy(
|
||||||
rpcThreadPoolSize = configuration.enterpriseConfiguration.tuning.rpcThreadPoolSize
|
rpcThreadPoolSize = configuration.enterpriseConfiguration.tuning.rpcThreadPoolSize
|
||||||
)
|
)
|
||||||
rpcServerAddresses?.let {
|
rpcServerAddresses?.let {
|
||||||
rpcMessagingClient = RPCMessagingClient(configuration.rpcOptions.sslConfig, it.admin, /*networkParameters.maxMessageSize*/MAX_FILE_SIZE, rpcServerConfiguration)
|
rpcMessagingClient = RPCMessagingClient(configuration.rpcOptions.sslConfig, it.admin, MAX_RPC_MESSAGE_SIZE, rpcServerConfiguration)
|
||||||
|
|
||||||
printBasicNodeInfo("RPC connection address", it.primary.toString())
|
printBasicNodeInfo("RPC connection address", it.primary.toString())
|
||||||
printBasicNodeInfo("RPC admin connection address", it.admin.toString())
|
printBasicNodeInfo("RPC admin connection address", it.admin.toString())
|
||||||
}
|
}
|
||||||
@ -227,7 +230,7 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
services.monitoringService.metrics,
|
services.monitoringService.metrics,
|
||||||
info.legalIdentities[0].name.toString(),
|
info.legalIdentities[0].name.toString(),
|
||||||
advertisedAddress,
|
advertisedAddress,
|
||||||
/*networkParameters.maxMessageSize*/MAX_FILE_SIZE,
|
networkParameters.maxMessageSize,
|
||||||
nodeProperties.flowsDrainingMode::isEnabled,
|
nodeProperties.flowsDrainingMode::isEnabled,
|
||||||
nodeProperties.flowsDrainingMode.values)
|
nodeProperties.flowsDrainingMode.values)
|
||||||
}
|
}
|
||||||
@ -239,9 +242,9 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc"
|
val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc"
|
||||||
with(rpcOptions) {
|
with(rpcOptions) {
|
||||||
rpcBroker = if (useSsl) {
|
rpcBroker = if (useSsl) {
|
||||||
ArtemisRpcBroker.withSsl(this.address!!, sslConfig, securityManager, certificateChainCheckPolicies, /*networkParameters.maxMessageSize*/MAX_FILE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory)
|
ArtemisRpcBroker.withSsl(this.address!!, sslConfig, securityManager, certificateChainCheckPolicies, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory)
|
||||||
} else {
|
} else {
|
||||||
ArtemisRpcBroker.withoutSsl(this.address!!, adminAddress!!, sslConfig, securityManager, certificateChainCheckPolicies, /*networkParameters.maxMessageSize*/MAX_FILE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory)
|
ArtemisRpcBroker.withoutSsl(this.address!!, adminAddress!!, sslConfig, securityManager, certificateChainCheckPolicies, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rpcBroker!!.addresses
|
return rpcBroker!!.addresses
|
||||||
@ -412,15 +415,15 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
val classloader = cordappLoader.appClassLoader
|
val classloader = cordappLoader.appClassLoader
|
||||||
nodeSerializationEnv = SerializationEnvironmentImpl(
|
nodeSerializationEnv = SerializationEnvironmentImpl(
|
||||||
SerializationFactoryImpl().apply {
|
SerializationFactoryImpl().apply {
|
||||||
registerScheme(KryoServerSerializationScheme())
|
|
||||||
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps))
|
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps))
|
||||||
registerScheme(KryoClientSerializationScheme())
|
registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps))
|
||||||
|
registerScheme(KryoServerSerializationScheme() )
|
||||||
},
|
},
|
||||||
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
|
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
|
||||||
rpcServerContext = KRYO_RPC_SERVER_CONTEXT.withClassLoader(classloader),
|
rpcServerContext = AMQP_RPC_SERVER_CONTEXT.withClassLoader(classloader),
|
||||||
storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader),
|
storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader),
|
||||||
checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader),
|
checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader),
|
||||||
rpcClientContext = if (configuration.shouldInitCrashShell()) KRYO_RPC_CLIENT_CONTEXT.withClassLoader(classloader) else null) //even Shell embeded in the node connects via RPC to the node
|
rpcClientContext = if (configuration.shouldInitCrashShell()) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classloader) else null) //even Shell embeded in the node connects via RPC to the node
|
||||||
}
|
}
|
||||||
|
|
||||||
private var rpcMessagingClient: RPCMessagingClient? = null
|
private var rpcMessagingClient: RPCMessagingClient? = null
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package net.corda.node.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.cordapp.Cordapp
|
||||||
|
import net.corda.core.serialization.ClassWhitelist
|
||||||
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.serialization.SerializationCustomSerializer
|
||||||
|
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.custom.RxNotificationSerializer
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set as the serialization scheme, defines the RPC Server serialization scheme as using the Corda
|
||||||
|
* AMQP implementation.
|
||||||
|
*/
|
||||||
|
class AMQPServerSerializationScheme(
|
||||||
|
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||||
|
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||||
|
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
||||||
|
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, ConcurrentHashMap())
|
||||||
|
|
||||||
|
constructor() : this(emptySet(), ConcurrentHashMap())
|
||||||
|
|
||||||
|
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rpcServerSerializerFactory(context: SerializationContext) =
|
||||||
|
SerializerFactory(
|
||||||
|
context.whitelist,
|
||||||
|
context.deserializationClassLoader
|
||||||
|
).apply {
|
||||||
|
register(RpcServerObservableSerializer())
|
||||||
|
register(RpcServerCordaFutureSerializer(this))
|
||||||
|
register(RxNotificationSerializer(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
||||||
|
return canDeserializeVersion(magic) &&
|
||||||
|
( target == SerializationContext.UseCase.P2P
|
||||||
|
|| target == SerializationContext.UseCase.Storage
|
||||||
|
|| target == SerializationContext.UseCase.RPCServer)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package net.corda.node.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
import net.corda.core.toObservable
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||||
|
import rx.Observable
|
||||||
|
import java.io.NotSerializableException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializer for [CordaFuture] objects where Futures are converted to Observables and
|
||||||
|
* are thus dealt with by the [RpcServerObservableSerializer]
|
||||||
|
*/
|
||||||
|
class RpcServerCordaFutureSerializer(factory: SerializerFactory)
|
||||||
|
: CustomSerializer.Proxy<CordaFuture<*>,
|
||||||
|
RpcServerCordaFutureSerializer.FutureProxy>(
|
||||||
|
CordaFuture::class.java, RpcServerCordaFutureSerializer.FutureProxy::class.java, factory
|
||||||
|
) {
|
||||||
|
override fun fromProxy(proxy: RpcServerCordaFutureSerializer.FutureProxy): CordaFuture<*> {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toProxy(obj: CordaFuture<*>): RpcServerCordaFutureSerializer.FutureProxy {
|
||||||
|
try {
|
||||||
|
return FutureProxy(obj.toObservable())
|
||||||
|
} catch (e: NotSerializableException) {
|
||||||
|
throw (NotSerializableException("Failed to serialize Future as proxy Observable - ${e.message}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FutureProxy(val observable: Observable<*>)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
|||||||
|
package net.corda.node.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.context.Trace
|
||||||
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.utilities.loggerFor
|
||||||
|
import net.corda.node.services.messaging.ObservableContextInterface
|
||||||
|
import net.corda.node.services.messaging.ObservableSubscription
|
||||||
|
import net.corda.nodeapi.RPCApi
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.*
|
||||||
|
import org.apache.qpid.proton.codec.Data
|
||||||
|
|
||||||
|
import rx.Notification
|
||||||
|
import rx.Observable
|
||||||
|
import rx.Subscriber
|
||||||
|
import java.io.NotSerializableException
|
||||||
|
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server side serializer that notionally serializes RxObservables when used by the RPC
|
||||||
|
* framework for event subscriptions. Notional in the sense that the actual observable
|
||||||
|
* isn't serialized, rather a reference to the observable is, this is then used by
|
||||||
|
* the client side RPC handler to subscribe to the observable stream.
|
||||||
|
*/
|
||||||
|
class RpcServerObservableSerializer : CustomSerializer.Implements<Observable<*>>(
|
||||||
|
Observable::class.java
|
||||||
|
) {
|
||||||
|
// Would be great to make this private, but then it's so much harder to unit test
|
||||||
|
object RpcObservableContextKey
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun createContext(
|
||||||
|
serializationContext: SerializationContext,
|
||||||
|
observableContext: ObservableContextInterface
|
||||||
|
) = serializationContext.withProperty(
|
||||||
|
RpcServerObservableSerializer.RpcObservableContextKey, observableContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val schemaForDocumentation = Schema(
|
||||||
|
listOf(
|
||||||
|
CompositeType(
|
||||||
|
name = type.toString(),
|
||||||
|
label = "",
|
||||||
|
provides = emptyList(),
|
||||||
|
descriptor = descriptor,
|
||||||
|
fields = listOf(
|
||||||
|
Field(
|
||||||
|
name = "observableId",
|
||||||
|
type = "string",
|
||||||
|
requires = emptyList(),
|
||||||
|
default = null,
|
||||||
|
label = null,
|
||||||
|
mandatory = true,
|
||||||
|
multiple = false),
|
||||||
|
Field(
|
||||||
|
name = "observableInstant",
|
||||||
|
type = "long",
|
||||||
|
requires = emptyList(),
|
||||||
|
default = null,
|
||||||
|
label = null,
|
||||||
|
mandatory = true,
|
||||||
|
multiple = false)
|
||||||
|
))))
|
||||||
|
|
||||||
|
override fun readObject(
|
||||||
|
obj: Any, schemas: SerializationSchemas,
|
||||||
|
input: DeserializationInput,
|
||||||
|
context: SerializationContext
|
||||||
|
): Observable<*> {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeDescribedObject(
|
||||||
|
obj: Observable<*>,
|
||||||
|
data: Data,
|
||||||
|
type: Type,
|
||||||
|
output: SerializationOutput,
|
||||||
|
context: SerializationContext
|
||||||
|
) {
|
||||||
|
val observableId = Trace.InvocationId.newInstance()
|
||||||
|
if (RpcServerObservableSerializer.RpcObservableContextKey !in context.properties) {
|
||||||
|
throw NotSerializableException("Missing Observable Key on serialization context - $type")
|
||||||
|
}
|
||||||
|
|
||||||
|
val observableContext = context.properties[RpcServerObservableSerializer.RpcObservableContextKey]
|
||||||
|
as ObservableContextInterface
|
||||||
|
|
||||||
|
data.withList {
|
||||||
|
data.putString(observableId.value)
|
||||||
|
data.putLong(observableId.timestamp.toEpochMilli())
|
||||||
|
}
|
||||||
|
|
||||||
|
val observableWithSubscription = ObservableSubscription(
|
||||||
|
subscription = obj.materialize().subscribe(
|
||||||
|
object : Subscriber<Notification<*>>() {
|
||||||
|
override fun onNext(observation: Notification<*>) {
|
||||||
|
if (!isUnsubscribed) {
|
||||||
|
val message = RPCApi.ServerToClient.Observation(
|
||||||
|
id = observableId,
|
||||||
|
content = observation,
|
||||||
|
deduplicationIdentity = observableContext.deduplicationIdentity
|
||||||
|
)
|
||||||
|
observableContext.sendMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(exception: Throwable) {
|
||||||
|
loggerFor<RpcServerObservableSerializer>().error(
|
||||||
|
"onError called in materialize()d RPC Observable", exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCompleted() {
|
||||||
|
observableContext.clientAddressToObservables.compute(observableContext.clientAddress) { _, observables ->
|
||||||
|
if (observables != null) {
|
||||||
|
observables.remove(observableId)
|
||||||
|
if (observables.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
observables
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
observableContext.clientAddressToObservables.compute(observableContext.clientAddress) { _, observables ->
|
||||||
|
if (observables == null) {
|
||||||
|
hashSetOf(observableId)
|
||||||
|
} else {
|
||||||
|
observables.add(observableId)
|
||||||
|
observables
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observableContext.observableMap.put(observableId, observableWithSubscription)
|
||||||
|
}
|
||||||
|
}
|
@ -14,22 +14,15 @@ import com.esotericsoftware.kryo.pool.KryoPool
|
|||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer
|
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.RPCKryo
|
|
||||||
|
|
||||||
class KryoServerSerializationScheme : AbstractKryoSerializationScheme() {
|
class KryoServerSerializationScheme : AbstractKryoSerializationScheme() {
|
||||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
||||||
return magic == kryoMagic && target != SerializationContext.UseCase.RPCClient
|
return magic == kryoMagic && target == SerializationContext.UseCase.Checkpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun rpcClientKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
|
override fun rpcClientKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
|
||||||
|
|
||||||
override fun rpcServerKryoPool(context: SerializationContext): KryoPool {
|
override fun rpcServerKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
|
||||||
return KryoPool.Builder {
|
|
||||||
DefaultKryoCustomizer.customize(RPCKryo(RpcServerObservableSerializer, context), publicKeySerializer).apply {
|
|
||||||
classLoader = context.deserializationClassLoader
|
|
||||||
}
|
|
||||||
}.build()
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,87 +0,0 @@
|
|||||||
package net.corda.node.serialization.kryo
|
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.Kryo
|
|
||||||
import com.esotericsoftware.kryo.Serializer
|
|
||||||
import com.esotericsoftware.kryo.io.Input
|
|
||||||
import com.esotericsoftware.kryo.io.Output
|
|
||||||
import net.corda.core.context.Trace
|
|
||||||
import net.corda.core.serialization.SerializationContext
|
|
||||||
import net.corda.core.serialization.SerializationDefaults
|
|
||||||
import net.corda.node.services.messaging.ObservableSubscription
|
|
||||||
import net.corda.node.services.messaging.RPCServer
|
|
||||||
import net.corda.nodeapi.RPCApi
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import rx.Notification
|
|
||||||
import rx.Observable
|
|
||||||
import rx.Subscriber
|
|
||||||
|
|
||||||
object RpcServerObservableSerializer : Serializer<Observable<*>>() {
|
|
||||||
private object RpcObservableContextKey
|
|
||||||
|
|
||||||
private val log = LoggerFactory.getLogger(javaClass)
|
|
||||||
fun createContext(observableContext: RPCServer.ObservableContext): SerializationContext {
|
|
||||||
return SerializationDefaults.RPC_SERVER_CONTEXT.withProperty(RpcServerObservableSerializer.RpcObservableContextKey, observableContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(kryo: Kryo?, input: Input?, type: Class<Observable<*>>?): Observable<Any> {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(kryo: Kryo, output: Output, observable: Observable<*>) {
|
|
||||||
val observableId = Trace.InvocationId.newInstance()
|
|
||||||
val observableContext = kryo.context[RpcObservableContextKey] as RPCServer.ObservableContext
|
|
||||||
output.writeInvocationId(observableId)
|
|
||||||
val observableWithSubscription = ObservableSubscription(
|
|
||||||
// We capture [observableContext] in the subscriber. Note that all synchronisation/kryo borrowing
|
|
||||||
// must be done again within the subscriber
|
|
||||||
subscription = observable.materialize().subscribe(
|
|
||||||
object : Subscriber<Notification<*>>() {
|
|
||||||
override fun onNext(observation: Notification<*>) {
|
|
||||||
if (!isUnsubscribed) {
|
|
||||||
val message = RPCApi.ServerToClient.Observation(
|
|
||||||
id = observableId,
|
|
||||||
content = observation,
|
|
||||||
deduplicationIdentity = observableContext.deduplicationIdentity
|
|
||||||
)
|
|
||||||
observableContext.sendMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(exception: Throwable) {
|
|
||||||
log.error("onError called in materialize()d RPC Observable", exception)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCompleted() {
|
|
||||||
observableContext.clientAddressToObservables.compute(observableContext.clientAddress) { _, observables ->
|
|
||||||
if (observables != null) {
|
|
||||||
observables.remove(observableId)
|
|
||||||
if (observables.isEmpty()) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
observables
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
observableContext.clientAddressToObservables.compute(observableContext.clientAddress) { _, observables ->
|
|
||||||
if (observables == null) {
|
|
||||||
hashSetOf(observableId)
|
|
||||||
} else {
|
|
||||||
observables.add(observableId)
|
|
||||||
observables
|
|
||||||
}
|
|
||||||
}
|
|
||||||
observableContext.observableMap.put(observableId, observableWithSubscription)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Output.writeInvocationId(id: Trace.InvocationId) {
|
|
||||||
|
|
||||||
writeString(id.value)
|
|
||||||
writeLong(id.timestamp.toEpochMilli())
|
|
||||||
}
|
|
||||||
}
|
|
@ -27,6 +27,7 @@ import net.corda.nodeapi.internal.config.parseAs
|
|||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag
|
import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import net.corda.tools.shell.SSHDConfiguration
|
import net.corda.tools.shell.SSHDConfiguration
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -70,6 +71,8 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
|||||||
// do not change this value without syncing it with ScheduledFlowsDrainingModeTest
|
// do not change this value without syncing it with ScheduledFlowsDrainingModeTest
|
||||||
val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5)
|
val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5)
|
||||||
val extraNetworkMapKeys: List<UUID>
|
val extraNetworkMapKeys: List<UUID>
|
||||||
|
val tlsCertCrlDistPoint: URL?
|
||||||
|
val tlsCertCrlIssuer: String?
|
||||||
|
|
||||||
fun validate(): List<String>
|
fun validate(): List<String>
|
||||||
|
|
||||||
@ -190,6 +193,8 @@ data class NodeConfigurationImpl(
|
|||||||
override val crlCheckSoftFail: Boolean,
|
override val crlCheckSoftFail: Boolean,
|
||||||
override val dataSourceProperties: Properties,
|
override val dataSourceProperties: Properties,
|
||||||
override val compatibilityZoneURL: URL? = null,
|
override val compatibilityZoneURL: URL? = null,
|
||||||
|
override val tlsCertCrlDistPoint: URL? = null,
|
||||||
|
override val tlsCertCrlIssuer: String? = null,
|
||||||
override val rpcUsers: List<User>,
|
override val rpcUsers: List<User>,
|
||||||
override val security: SecurityConfiguration? = null,
|
override val security: SecurityConfiguration? = null,
|
||||||
override val verifierType: VerifierType,
|
override val verifierType: VerifierType,
|
||||||
@ -241,10 +246,29 @@ data class NodeConfigurationImpl(
|
|||||||
}.asOptions(fallbackSslOptions)
|
}.asOptions(fallbackSslOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun validateTlsCertCrlConfig(): List<String> {
|
||||||
|
val errors = mutableListOf<String>()
|
||||||
|
if (tlsCertCrlIssuer != null) {
|
||||||
|
if (tlsCertCrlDistPoint == null) {
|
||||||
|
errors += "tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL"
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
X500Name(tlsCertCrlIssuer)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
errors += "Error when parsing tlsCertCrlIssuer: ${e.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) {
|
||||||
|
errors += "tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE"
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
override fun validate(): List<String> {
|
override fun validate(): List<String> {
|
||||||
val errors = mutableListOf<String>()
|
val errors = mutableListOf<String>()
|
||||||
errors += validateDevModeOptions()
|
errors += validateDevModeOptions()
|
||||||
errors += validateRpcOptions(rpcOptions)
|
errors += validateRpcOptions(rpcOptions)
|
||||||
|
errors += validateTlsCertCrlConfig()
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,10 @@ import net.corda.node.services.messaging.NodeLoginModule.Companion.VERIFIER_ROLE
|
|||||||
import net.corda.nodeapi.ArtemisTcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.nodeapi.ConnectionDirection
|
||||||
import net.corda.nodeapi.VerifierApi
|
import net.corda.nodeapi.VerifierApi
|
||||||
|
import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor
|
||||||
|
import net.corda.nodeapi.internal.ArtemisMessageSizeChecksInterceptor
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
||||||
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||||
@ -79,7 +82,7 @@ import javax.security.auth.spi.LoginModule
|
|||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
class ArtemisMessagingServer(private val config: NodeConfiguration,
|
class ArtemisMessagingServer(private val config: NodeConfiguration,
|
||||||
private val messagingServerAddress: NetworkHostAndPort,
|
private val messagingServerAddress: NetworkHostAndPort,
|
||||||
val maxMessageSize: Int) : ArtemisBroker, SingletonSerializeAsToken() {
|
private val maxMessageSize: Int) : ArtemisBroker, SingletonSerializeAsToken() {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
@ -127,8 +130,11 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
registerPostQueueCreationCallback { log.debug { "Queue Created: $it" } }
|
registerPostQueueCreationCallback { log.debug { "Queue Created: $it" } }
|
||||||
registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } }
|
registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } }
|
||||||
}
|
}
|
||||||
// Config driven switch between legacy CORE bridges and the newer AMQP protocol bridges.
|
|
||||||
activeMQServer.start()
|
activeMQServer.start()
|
||||||
|
activeMQServer.remotingService.addIncomingInterceptor(ArtemisMessageSizeChecksInterceptor(maxMessageSize))
|
||||||
|
activeMQServer.remotingService.addIncomingInterceptor(AmqpMessageSizeChecksInterceptor(maxMessageSize))
|
||||||
|
// Config driven switch between legacy CORE bridges and the newer AMQP protocol bridges.
|
||||||
log.info("P2P messaging server listening on $messagingServerAddress")
|
log.info("P2P messaging server listening on $messagingServerAddress")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,9 +153,9 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess
|
idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess
|
||||||
isPersistIDCache = true
|
isPersistIDCache = true
|
||||||
isPopulateValidatedUser = true
|
isPopulateValidatedUser = true
|
||||||
journalBufferSize_NIO = maxMessageSize // Artemis default is 490KiB - required to address IllegalArgumentException (when Artemis uses Java NIO): Record is too large to store.
|
journalBufferSize_NIO = maxMessageSize + JOURNAL_HEADER_SIZE // Artemis default is 490KiB - required to address IllegalArgumentException (when Artemis uses Java NIO): Record is too large to store.
|
||||||
journalBufferSize_AIO = maxMessageSize // Required to address IllegalArgumentException (when Artemis uses Linux Async IO): Record is too large to store.
|
journalBufferSize_AIO = maxMessageSize + JOURNAL_HEADER_SIZE // Required to address IllegalArgumentException (when Artemis uses Linux Async IO): Record is too large to store.
|
||||||
journalFileSize = maxMessageSize // The size of each journal file in bytes. Artemis default is 10MiB.
|
journalFileSize = maxMessageSize + JOURNAL_HEADER_SIZE// The size of each journal file in bytes. Artemis default is 10MiB.
|
||||||
managementNotificationAddress = SimpleString(NOTIFICATIONS_ADDRESS)
|
managementNotificationAddress = SimpleString(NOTIFICATIONS_ADDRESS)
|
||||||
connectionTtlCheckInterval = config.enterpriseConfiguration.tuning.brokerConnectionTtlCheckIntervalMs
|
connectionTtlCheckInterval = config.enterpriseConfiguration.tuning.brokerConnectionTtlCheckIntervalMs
|
||||||
// JMX enablement
|
// JMX enablement
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package net.corda.node.services.messaging
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache
|
||||||
|
import net.corda.core.context.Trace
|
||||||
|
import net.corda.nodeapi.RPCApi
|
||||||
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An observable context is constructed on each RPC request. If subsequently a nested Observable is encountered this
|
||||||
|
* same context is propagated by the serialization context. This way all observations rooted in a single RPC will be
|
||||||
|
* muxed correctly. Note that the context construction itself is quite cheap.
|
||||||
|
*/
|
||||||
|
interface ObservableContextInterface {
|
||||||
|
fun sendMessage(serverToClient: RPCApi.ServerToClient)
|
||||||
|
|
||||||
|
val observableMap: Cache<Trace.InvocationId, ObservableSubscription>
|
||||||
|
val clientAddressToObservables: ConcurrentHashMap<SimpleString, HashSet<Trace.InvocationId>>
|
||||||
|
val deduplicationIdentity: String
|
||||||
|
val clientAddress: SimpleString
|
||||||
|
}
|
@ -46,6 +46,7 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
|||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOTIFY
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOTIFY
|
||||||
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress
|
||||||
@ -55,6 +56,7 @@ import net.corda.nodeapi.internal.bridging.BridgeControl
|
|||||||
import net.corda.nodeapi.internal.bridging.BridgeEntry
|
import net.corda.nodeapi.internal.bridging.BridgeEntry
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
|
import net.corda.nodeapi.internal.requireMessageSize
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException
|
import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException
|
||||||
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
|
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
|
||||||
import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER
|
import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER
|
||||||
@ -218,7 +220,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
// would be the default and the two lines below can be deleted.
|
// would be the default and the two lines below can be deleted.
|
||||||
connectionTTL = -1
|
connectionTTL = -1
|
||||||
clientFailureCheckPeriod = -1
|
clientFailureCheckPeriod = -1
|
||||||
minLargeMessageSize = maxMessageSize
|
minLargeMessageSize = maxMessageSize + JOURNAL_HEADER_SIZE
|
||||||
isUseGlobalPools = nodeSerializationEnv != null
|
isUseGlobalPools = nodeSerializationEnv != null
|
||||||
confirmationWindowSize = config.enterpriseConfiguration.tuning.p2pConfirmationWindowSize
|
confirmationWindowSize = config.enterpriseConfiguration.tuning.p2pConfirmationWindowSize
|
||||||
}
|
}
|
||||||
@ -289,7 +291,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
networkChangeSubscription = networkMap.changed.subscribe { updateBridgesOnNetworkChange(it) }
|
networkChangeSubscription = networkMap.changed.subscribe { updateBridgesOnNetworkChange(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendBridgeControl(message: BridgeControl) {
|
private fun sendBridgeControl(message: BridgeControl) {
|
||||||
state.locked {
|
state.locked {
|
||||||
val controlPacket = message.serialize(context = SerializationDefaults.P2P_CONTEXT).bytes
|
val controlPacket = message.serialize(context = SerializationDefaults.P2P_CONTEXT).bytes
|
||||||
val artemisMessage = producerSession!!.createMessage(false)
|
val artemisMessage = producerSession!!.createMessage(false)
|
||||||
@ -397,6 +399,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
|
|
||||||
private fun artemisToCordaMessage(message: ClientMessage): ReceivedMessage? {
|
private fun artemisToCordaMessage(message: ClientMessage): ReceivedMessage? {
|
||||||
try {
|
try {
|
||||||
|
requireMessageSize(message.bodySize, maxMessageSize)
|
||||||
val topic = message.required(P2PMessagingHeaders.topicProperty) { getStringProperty(it) }
|
val topic = message.required(P2PMessagingHeaders.topicProperty) { getStringProperty(it) }
|
||||||
val user = requireNotNull(if (externalBridge) {
|
val user = requireNotNull(if (externalBridge) {
|
||||||
message.getStringProperty(P2PMessagingHeaders.bridgedCertificateSubject) ?: message.getStringProperty(HDR_VALIDATED_USER)
|
message.getStringProperty(P2PMessagingHeaders.bridgedCertificateSubject) ?: message.getStringProperty(HDR_VALIDATED_USER)
|
||||||
@ -531,6 +534,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any) {
|
override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any) {
|
||||||
|
requireMessageSize(message.data.size, maxMessageSize)
|
||||||
messagingExecutor!!.send(message, target)
|
messagingExecutor!!.send(message, target)
|
||||||
retryId?.let {
|
retryId?.let {
|
||||||
database.transaction {
|
database.transaction {
|
||||||
@ -586,7 +590,8 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
} else {
|
} else {
|
||||||
// Otherwise we send the message to an internal queue for the target residing on our broker. It's then the
|
// Otherwise we send the message to an internal queue for the target residing on our broker. It's then the
|
||||||
// broker's job to route the message to the target's P2P queue.
|
// broker's job to route the message to the target's P2P queue.
|
||||||
val internalTargetQueue = (address as? ArtemisAddress)?.queueName ?: throw IllegalArgumentException("Not an Artemis address")
|
val internalTargetQueue = (address as? ArtemisAddress)?.queueName
|
||||||
|
?: throw IllegalArgumentException("Not an Artemis address")
|
||||||
state.locked {
|
state.locked {
|
||||||
createQueueIfAbsent(internalTargetQueue, producerSession!!)
|
createQueueIfAbsent(internalTargetQueue, producerSession!!)
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,14 @@ import net.corda.core.identity.CordaX500Name
|
|||||||
import net.corda.core.internal.LifeCycle
|
import net.corda.core.internal.LifeCycle
|
||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.serialization.SerializationDefaults
|
||||||
import net.corda.core.serialization.SerializationDefaults.RPC_SERVER_CONTEXT
|
import net.corda.core.serialization.SerializationDefaults.RPC_SERVER_CONTEXT
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.utilities.*
|
import net.corda.core.utilities.*
|
||||||
import net.corda.node.internal.security.AuthorizingSubject
|
import net.corda.node.internal.security.AuthorizingSubject
|
||||||
import net.corda.node.internal.security.RPCSecurityManager
|
import net.corda.node.internal.security.RPCSecurityManager
|
||||||
import net.corda.node.serialization.kryo.RpcServerObservableSerializer
|
|
||||||
import net.corda.node.services.logging.pushToLoggingContext
|
import net.corda.node.services.logging.pushToLoggingContext
|
||||||
|
import net.corda.node.serialization.amqp.RpcServerObservableSerializer
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.externalTrace
|
import net.corda.nodeapi.externalTrace
|
||||||
import net.corda.nodeapi.impersonatedActor
|
import net.corda.nodeapi.impersonatedActor
|
||||||
@ -55,6 +56,8 @@ import java.util.*
|
|||||||
import java.util.concurrent.*
|
import java.util.concurrent.*
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
private typealias ObservableSubscriptionMap = Cache<InvocationId, ObservableSubscription>
|
||||||
|
|
||||||
data class RPCServerConfiguration(
|
data class RPCServerConfiguration(
|
||||||
/** The number of threads to use for handling RPC requests */
|
/** The number of threads to use for handling RPC requests */
|
||||||
val rpcThreadPoolSize: Int,
|
val rpcThreadPoolSize: Int,
|
||||||
@ -416,19 +419,22 @@ class RPCServer(
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* We construct an observable context on each RPC request. If subsequently a nested Observable is encountered this
|
* We construct an observable context on each RPC request. If subsequently a nested Observable is encountered this
|
||||||
* same context is propagated by the instrumented KryoPool. This way all observations rooted in a single RPC will be
|
* same context is propagated by serialization context. This way all observations rooted in a single RPC will be
|
||||||
* muxed correctly. Note that the context construction itself is quite cheap.
|
* muxed correctly. Note that the context construction itself is quite cheap.
|
||||||
*/
|
*/
|
||||||
inner class ObservableContext(
|
inner class ObservableContext(
|
||||||
val observableMap: ObservableSubscriptionMap,
|
override val observableMap: ObservableSubscriptionMap,
|
||||||
val clientAddressToObservables: ConcurrentHashMap<SimpleString, HashSet<InvocationId>>,
|
override val clientAddressToObservables: ConcurrentHashMap<SimpleString, HashSet<InvocationId>>,
|
||||||
val deduplicationIdentity: String,
|
override val deduplicationIdentity: String,
|
||||||
val clientAddress: SimpleString
|
override val clientAddress: SimpleString
|
||||||
) {
|
) : ObservableContextInterface {
|
||||||
private val serializationContextWithObservableContext = RpcServerObservableSerializer.createContext(this)
|
private val serializationContextWithObservableContext = RpcServerObservableSerializer.createContext(
|
||||||
|
observableContext = this,
|
||||||
|
serializationContext = SerializationDefaults.RPC_SERVER_CONTEXT)
|
||||||
|
|
||||||
fun sendMessage(serverToClient: RPCApi.ServerToClient) {
|
override fun sendMessage(serverToClient: RPCApi.ServerToClient) {
|
||||||
sendJobQueue.put(RpcSendJob.Send(contextDatabaseOrNull, clientAddress, serializationContextWithObservableContext, serverToClient))
|
sendJobQueue.put(RpcSendJob.Send(contextDatabaseOrNull, clientAddress,
|
||||||
|
serializationContextWithObservableContext, serverToClient))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,4 +494,4 @@ class ObservableSubscription(
|
|||||||
val subscription: Subscription
|
val subscription: Subscription
|
||||||
)
|
)
|
||||||
|
|
||||||
typealias ObservableSubscriptionMap = Cache<InvocationId, ObservableSubscription>
|
|
||||||
|
@ -20,12 +20,12 @@ import net.corda.core.serialization.serialize
|
|||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
|
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||||
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
|
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
|
||||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Scheduler
|
import rx.Scheduler
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
@ -13,6 +13,7 @@ package net.corda.node.utilities.registration
|
|||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.node.NodeRegistrationOption
|
import net.corda.node.NodeRegistrationOption
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
@ -22,6 +23,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
|
|||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||||
import org.bouncycastle.util.io.pem.PemObject
|
import org.bouncycastle.util.io.pem.PemObject
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
@ -226,6 +228,10 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService:
|
|||||||
CORDA_CLIENT_CA,
|
CORDA_CLIENT_CA,
|
||||||
CertRole.NODE_CA) {
|
CertRole.NODE_CA) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val logger = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>) {
|
override fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>) {
|
||||||
createSSLKeystore(nodeCAKeyPair, certificates)
|
createSSLKeystore(nodeCAKeyPair, certificates)
|
||||||
createTruststore(certificates.last())
|
createTruststore(certificates.last())
|
||||||
@ -240,7 +246,10 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService:
|
|||||||
certificates.first(),
|
certificates.first(),
|
||||||
nodeCAKeyPair,
|
nodeCAKeyPair,
|
||||||
config.myLegalName.x500Principal,
|
config.myLegalName.x500Principal,
|
||||||
sslKeyPair.public)
|
sslKeyPair.public,
|
||||||
|
crlDistPoint = config.tlsCertCrlDistPoint?.toString(),
|
||||||
|
crlIssuer = if (config.tlsCertCrlIssuer != null) X500Name(config.tlsCertCrlIssuer) else null)
|
||||||
|
logger.info("Generated TLS certificate: $sslCert")
|
||||||
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates)
|
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates)
|
||||||
}
|
}
|
||||||
println("SSL private key and certificate stored in ${config.sslKeystore}.")
|
println("SSL private key and certificate stored in ${config.sslKeystore}.")
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
package net.corda.node.internal.serialization
|
||||||
|
|
||||||
|
import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext
|
||||||
|
import net.corda.core.internal.ThreadBox
|
||||||
|
import net.corda.core.context.Trace
|
||||||
|
import net.corda.node.internal.serialization.testutils.AMQPRoundTripRPCSerializationScheme
|
||||||
|
import net.corda.node.internal.serialization.testutils.TestObservableContext as ServerObservableContext
|
||||||
|
import net.corda.node.services.messaging.ObservableSubscription
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
|
||||||
|
|
||||||
|
import co.paralleluniverse.common.util.SameThreadExecutor
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
|
import com.github.benmanes.caffeine.cache.RemovalListener
|
||||||
|
import com.nhaarman.mockito_kotlin.mock
|
||||||
|
import net.corda.client.rpc.internal.serialization.amqp.RpcClientObservableSerializer
|
||||||
|
import net.corda.node.internal.serialization.testutils.serializationContext
|
||||||
|
import net.corda.node.serialization.amqp.RpcServerObservableSerializer
|
||||||
|
import net.corda.nodeapi.RPCApi
|
||||||
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
|
import org.junit.Test
|
||||||
|
import rx.Notification
|
||||||
|
import rx.Observable
|
||||||
|
import rx.Subscription
|
||||||
|
import rx.subjects.UnicastSubject
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class RoundTripObservableSerializerTests {
|
||||||
|
private fun getID() = Trace.InvocationId("test1", Instant.now())
|
||||||
|
|
||||||
|
private fun subscriptionMap(
|
||||||
|
id: Trace.InvocationId
|
||||||
|
) : Cache<Trace.InvocationId, ObservableSubscription> {
|
||||||
|
val subMap: Cache<Trace.InvocationId, ObservableSubscription> = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES)
|
||||||
|
.maximumSize(100)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
subMap.put(id, ObservableSubscription(mock<Subscription>()))
|
||||||
|
|
||||||
|
return subMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private val observablesToReap = ThreadBox(object {
|
||||||
|
var observables = ArrayList<Trace.InvocationId>()
|
||||||
|
})
|
||||||
|
|
||||||
|
private fun createRpcObservableMap(): Cache<Trace.InvocationId, UnicastSubject<Notification<*>>> {
|
||||||
|
val onObservableRemove = RemovalListener<Trace.InvocationId, UnicastSubject<Notification<*>>> { key, value, cause ->
|
||||||
|
val observableId = key!!
|
||||||
|
|
||||||
|
observablesToReap.locked { observables.add(observableId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return Caffeine.newBuilder().weakValues().removalListener(onObservableRemove).executor(SameThreadExecutor.getExecutor()).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun roundTripTest1() {
|
||||||
|
val serializationScheme = AMQPRoundTripRPCSerializationScheme(
|
||||||
|
serializationContext, emptySet(), ConcurrentHashMap())
|
||||||
|
|
||||||
|
// 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 received message
|
||||||
|
val id : Trace.InvocationId = getID()
|
||||||
|
|
||||||
|
val serverObservableContext = ServerObservableContext(
|
||||||
|
subscriptionMap(id),
|
||||||
|
clientAddressToObservables = ConcurrentHashMap(),
|
||||||
|
deduplicationIdentity = "thisIsATest",
|
||||||
|
clientAddress = SimpleString("clientAddress"))
|
||||||
|
|
||||||
|
val serverSerializer = serializationScheme.rpcServerSerializerFactory(serverObservableContext)
|
||||||
|
|
||||||
|
val clientObservableContext = ClientObservableContext(
|
||||||
|
callSiteMap = null,
|
||||||
|
observableMap = createRpcObservableMap(),
|
||||||
|
hardReferenceStore = Collections.synchronizedSet(mutableSetOf<Observable<*>>())
|
||||||
|
)
|
||||||
|
|
||||||
|
val clientSerializer = serializationScheme.rpcClientSerializerFactory(clientObservableContext, id)
|
||||||
|
|
||||||
|
|
||||||
|
// What we're actually going to serialize then deserialize
|
||||||
|
val obs = Observable.create<Int>({ 12 })
|
||||||
|
|
||||||
|
val serverSerializationContext = RpcServerObservableSerializer.createContext(
|
||||||
|
serializationContext, serverObservableContext)
|
||||||
|
|
||||||
|
val clientSerializationContext = RpcClientObservableSerializer.createContext(
|
||||||
|
serializationContext, clientObservableContext).withProperty(RPCApi.RpcRequestOrObservableIdKey, id)
|
||||||
|
|
||||||
|
|
||||||
|
val blob = SerializationOutput(serverSerializer).serialize(obs, serverSerializationContext)
|
||||||
|
val obs2 = DeserializationInput(clientSerializer).deserialize(blob, clientSerializationContext)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
package net.corda.node.internal.serialization
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
|
import com.nhaarman.mockito_kotlin.mock
|
||||||
|
import net.corda.core.context.Trace
|
||||||
|
import net.corda.node.internal.serialization.testutils.*
|
||||||
|
import net.corda.node.serialization.amqp.RpcServerObservableSerializer
|
||||||
|
import net.corda.node.services.messaging.ObservableSubscription
|
||||||
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
|
import org.junit.Test
|
||||||
|
import rx.Observable
|
||||||
|
import rx.Subscription
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class RpcServerObservableSerializerTests {
|
||||||
|
|
||||||
|
private fun subscriptionMap(): Cache<Trace.InvocationId, ObservableSubscription> {
|
||||||
|
val subMap: Cache<Trace.InvocationId, ObservableSubscription> = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES)
|
||||||
|
.maximumSize(100)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
subMap.put(Trace.InvocationId("test1", Instant.now()), ObservableSubscription(mock<Subscription>()))
|
||||||
|
|
||||||
|
return subMap
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun canSerializerBeRegistered() {
|
||||||
|
val sf = SerializerFactory(AllWhitelist, javaClass.classLoader)
|
||||||
|
|
||||||
|
try {
|
||||||
|
sf.register(RpcServerObservableSerializer())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Error("Observable serializer must be registerable with factory, unexpected exception - ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun canAssociateWithContext() {
|
||||||
|
val observable = TestObservableContext(
|
||||||
|
subscriptionMap(),
|
||||||
|
clientAddressToObservables = ConcurrentHashMap(),
|
||||||
|
deduplicationIdentity = "thisIsATest",
|
||||||
|
clientAddress = SimpleString("clientAddress"))
|
||||||
|
|
||||||
|
val newContext = RpcServerObservableSerializer.createContext(serializationContext, observable)
|
||||||
|
|
||||||
|
assertEquals(1, newContext.properties.size)
|
||||||
|
assertTrue(newContext.properties.containsKey(RpcServerObservableSerializer.RpcObservableContextKey))
|
||||||
|
assertEquals(observable, newContext.properties[RpcServerObservableSerializer.RpcObservableContextKey])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun serialiseFakeObservable() {
|
||||||
|
val testClientAddress = "clientAddres"
|
||||||
|
val observable = TestObservableContext(
|
||||||
|
subscriptionMap(),
|
||||||
|
clientAddressToObservables = ConcurrentHashMap(),
|
||||||
|
deduplicationIdentity = "thisIsATest",
|
||||||
|
clientAddress = SimpleString(testClientAddress))
|
||||||
|
|
||||||
|
val sf = SerializerFactory(AllWhitelist, javaClass.classLoader).apply {
|
||||||
|
register(RpcServerObservableSerializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
val obs = Observable.create<Int>({ 12 })
|
||||||
|
val newContext = RpcServerObservableSerializer.createContext(serializationContext, observable)
|
||||||
|
|
||||||
|
try {
|
||||||
|
SerializationOutput(sf).serializeAndReturnSchema(obs, newContext)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Error("Serialization of observable should not throw - ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package net.corda.node.internal.serialization.testutils
|
||||||
|
|
||||||
|
import net.corda.client.rpc.internal.serialization.amqp.RpcClientObservableSerializer
|
||||||
|
import net.corda.core.context.Trace
|
||||||
|
import net.corda.core.cordapp.Cordapp
|
||||||
|
import net.corda.core.serialization.ClassWhitelist
|
||||||
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.serialization.SerializationCustomSerializer
|
||||||
|
import net.corda.node.serialization.amqp.RpcServerObservableSerializer
|
||||||
|
import net.corda.nodeapi.RPCApi
|
||||||
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
|
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special serialization context for the round trip tests that allows for both server and client RPC
|
||||||
|
* operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class AMQPRoundTripRPCSerializationScheme(
|
||||||
|
private val serializationContext: SerializationContext,
|
||||||
|
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||||
|
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>)
|
||||||
|
: AbstractAMQPSerializationScheme(
|
||||||
|
cordappCustomSerializers, serializerFactoriesForContexts
|
||||||
|
) {
|
||||||
|
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
|
return SerializerFactory(AllWhitelist, javaClass.classLoader).apply {
|
||||||
|
register(RpcClientObservableSerializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||||
|
return SerializerFactory(AllWhitelist, javaClass.classLoader).apply {
|
||||||
|
register(RpcServerObservableSerializer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canDeserializeVersion(
|
||||||
|
magic: CordaSerializationMagic,
|
||||||
|
target: SerializationContext.UseCase) = true
|
||||||
|
|
||||||
|
fun rpcClientSerializerFactory(observableContext: ClientObservableContext, id: Trace.InvocationId) =
|
||||||
|
rpcClientSerializerFactory(
|
||||||
|
RpcClientObservableSerializer.createContext(serializationContext, observableContext)
|
||||||
|
.withProperty(RPCApi.RpcRequestOrObservableIdKey, id))
|
||||||
|
|
||||||
|
fun rpcServerSerializerFactory(observableContext: TestObservableContext) =
|
||||||
|
rpcServerSerializerFactory(
|
||||||
|
RpcServerObservableSerializer.createContext(serializationContext, observableContext))
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package net.corda.node.internal.serialization.testutils
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache
|
||||||
|
import net.corda.core.context.Trace
|
||||||
|
import net.corda.node.services.messaging.ObservableContextInterface
|
||||||
|
import net.corda.node.services.messaging.ObservableSubscription
|
||||||
|
import net.corda.nodeapi.RPCApi
|
||||||
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
class TestObservableContext(
|
||||||
|
override val observableMap: Cache<Trace.InvocationId, ObservableSubscription>,
|
||||||
|
override val clientAddressToObservables: ConcurrentHashMap<SimpleString, HashSet<Trace.InvocationId>>,
|
||||||
|
override val deduplicationIdentity: String,
|
||||||
|
override val clientAddress: SimpleString
|
||||||
|
) : ObservableContextInterface {
|
||||||
|
override fun sendMessage(serverToClient: RPCApi.ServerToClient) { }
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package net.corda.node.internal.serialization.testutils
|
||||||
|
|
||||||
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
|
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
|
||||||
|
|
||||||
|
val serializationProperties: MutableMap<Any, Any> = mutableMapOf()
|
||||||
|
|
||||||
|
val serializationContext = SerializationContextImpl(
|
||||||
|
preferredSerializationVersion = amqpMagic,
|
||||||
|
deserializationClassLoader = ClassLoader.getSystemClassLoader(),
|
||||||
|
whitelist = AllWhitelist,
|
||||||
|
properties = serializationProperties,
|
||||||
|
objectReferencesEnabled = false,
|
||||||
|
useCase = SerializationContext.UseCase.Testing,
|
||||||
|
encoding = null)
|
@ -25,6 +25,7 @@ import org.assertj.core.api.Assertions.assertThat
|
|||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import java.net.URL
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -42,6 +43,27 @@ class NodeConfigurationImplTest {
|
|||||||
configDebugOptions(false, null)
|
configDebugOptions(false, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can't have tlsCertCrlDistPoint null when tlsCertCrlIssuer is given`() {
|
||||||
|
val configValidationResult = configTlsCertCrlOptions(null, "C=US, L=New York, OU=Corda, O=R3 HoldCo LLC, CN=Corda Root CA").validate()
|
||||||
|
assertTrue { configValidationResult.isNotEmpty() }
|
||||||
|
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `tlsCertCrlIssuer validation fails when misconfigured`() {
|
||||||
|
val configValidationResult = configTlsCertCrlOptions(URL("http://test.com/crl"), "Corda Root CA").validate()
|
||||||
|
assertTrue { configValidationResult.isNotEmpty() }
|
||||||
|
assertThat(configValidationResult.first()).contains("Error when parsing tlsCertCrlIssuer:")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can't have tlsCertCrlDistPoint null when crlCheckSoftFail is false`() {
|
||||||
|
val configValidationResult = configTlsCertCrlOptions(null, null, false).validate()
|
||||||
|
assertTrue { configValidationResult.isNotEmpty() }
|
||||||
|
assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `check devModeOptions flag helper`() {
|
fun `check devModeOptions flag helper`() {
|
||||||
assertTrue { configDebugOptions(true, null).shouldCheckCheckpoints() }
|
assertTrue { configDebugOptions(true, null).shouldCheckCheckpoints() }
|
||||||
@ -155,6 +177,10 @@ class NodeConfigurationImplTest {
|
|||||||
return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions)
|
return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration {
|
||||||
|
return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer, crlCheckSoftFail = crlCheckSoftFail)
|
||||||
|
}
|
||||||
|
|
||||||
private fun testConfiguration(dataSourceProperties: Properties): NodeConfigurationImpl {
|
private fun testConfiguration(dataSourceProperties: Properties): NodeConfigurationImpl {
|
||||||
return testConfiguration.copy(dataSourceProperties = dataSourceProperties)
|
return testConfiguration.copy(dataSourceProperties = dataSourceProperties)
|
||||||
}
|
}
|
||||||
@ -190,7 +216,8 @@ class NodeConfigurationImplTest {
|
|||||||
rpcSettings = rpcSettings,
|
rpcSettings = rpcSettings,
|
||||||
relay = null,
|
relay = null,
|
||||||
enterpriseConfiguration = EnterpriseConfiguration((MutualExclusionConfiguration(false, "", 20000, 40000))),
|
enterpriseConfiguration = EnterpriseConfiguration((MutualExclusionConfiguration(false, "", 20000, 40000))),
|
||||||
crlCheckSoftFail = true
|
crlCheckSoftFail = true,
|
||||||
|
tlsCertCrlDistPoint = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import net.corda.testing.node.MockServices.Companion.makeTestDataSourcePropertie
|
|||||||
import net.corda.testing.node.internal.MOCK_VERSION_INFO
|
import net.corda.testing.node.internal.MOCK_VERSION_INFO
|
||||||
import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER
|
import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
|
import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@ -50,6 +51,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
|
|||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class ArtemisMessagingTest {
|
class ArtemisMessagingTest {
|
||||||
companion object {
|
companion object {
|
||||||
@ -86,7 +88,8 @@ class ArtemisMessagingTest {
|
|||||||
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
||||||
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
||||||
doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration
|
doReturn(EnterpriseConfiguration(MutualExclusionConfiguration(false, "", 20000, 40000))).whenever(it).enterpriseConfiguration
|
||||||
doReturn(P2PMessagingRetryConfiguration(5.seconds, 3, backoffBase=1.0)).whenever(it).p2pMessagingRetry
|
doReturn(P2PMessagingRetryConfiguration(5.seconds, 3, backoffBase = 1.0)).whenever(it).p2pMessagingRetry
|
||||||
|
|
||||||
}
|
}
|
||||||
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
LogHelper.setLevel(PersistentUniquenessProvider::class)
|
||||||
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
|
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
|
||||||
@ -148,6 +151,41 @@ class ArtemisMessagingTest {
|
|||||||
assertNull(receivedMessages.poll(200, MILLISECONDS))
|
assertNull(receivedMessages.poll(200, MILLISECONDS))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `client should fail if message exceed maxMessageSize limit`() {
|
||||||
|
val (messagingClient, receivedMessages) = createAndStartClientAndServer()
|
||||||
|
val message = messagingClient.createMessage(TOPIC, data = ByteArray(MAX_MESSAGE_SIZE))
|
||||||
|
messagingClient.send(message, messagingClient.myAddress)
|
||||||
|
|
||||||
|
val actual: Message = receivedMessages.take()
|
||||||
|
assertTrue(ByteArray(MAX_MESSAGE_SIZE).contentEquals(actual.data.bytes))
|
||||||
|
assertNull(receivedMessages.poll(200, MILLISECONDS))
|
||||||
|
|
||||||
|
val tooLagerMessage = messagingClient.createMessage(TOPIC, data = ByteArray(MAX_MESSAGE_SIZE + 1))
|
||||||
|
assertThatThrownBy {
|
||||||
|
messagingClient.send(tooLagerMessage, messagingClient.myAddress)
|
||||||
|
}.isInstanceOf(IllegalArgumentException::class.java)
|
||||||
|
.hasMessageContaining("Message exceeds maxMessageSize network parameter")
|
||||||
|
|
||||||
|
assertNull(receivedMessages.poll(200, MILLISECONDS))
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun `server should not process if incoming message exceed maxMessageSize limit`() {
|
||||||
|
val (messagingClient, receivedMessages) = createAndStartClientAndServer(clientMaxMessageSize = 100_000, serverMaxMessageSize = 50_000)
|
||||||
|
val message = messagingClient.createMessage(TOPIC, data = ByteArray(50_000))
|
||||||
|
messagingClient.send(message, messagingClient.myAddress)
|
||||||
|
|
||||||
|
val actual: Message = receivedMessages.take()
|
||||||
|
assertTrue(ByteArray(50_000).contentEquals(actual.data.bytes))
|
||||||
|
assertNull(receivedMessages.poll(200, MILLISECONDS))
|
||||||
|
|
||||||
|
val tooLagerMessage = messagingClient.createMessage(TOPIC, data = ByteArray(100_000))
|
||||||
|
assertThatThrownBy {
|
||||||
|
messagingClient.send(tooLagerMessage, messagingClient.myAddress)
|
||||||
|
}.isInstanceOf(ActiveMQConnectionTimedOutException::class.java)
|
||||||
|
assertNull(receivedMessages.poll(200, MILLISECONDS))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `platform version is included in the message`() {
|
fun `platform version is included in the message`() {
|
||||||
val (messagingClient, receivedMessages) = createAndStartClientAndServer(platformVersion = 3)
|
val (messagingClient, receivedMessages) = createAndStartClientAndServer(platformVersion = 3)
|
||||||
@ -325,12 +363,15 @@ class ArtemisMessagingTest {
|
|||||||
messagingClient!!.start()
|
messagingClient!!.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAndStartClientAndServer(platformVersion: Int = 1, dontAckCondition: (msg: ReceivedMessage) -> Boolean = { false }): Pair<P2PMessagingClient, BlockingQueue<ReceivedMessage>> {
|
private fun createAndStartClientAndServer(platformVersion: Int = 1, serverMaxMessageSize: Int = MAX_MESSAGE_SIZE,
|
||||||
|
clientMaxMessageSize: Int = MAX_MESSAGE_SIZE,
|
||||||
|
dontAckCondition: (msg: ReceivedMessage) -> Boolean = { false }
|
||||||
|
): Pair<P2PMessagingClient, BlockingQueue<ReceivedMessage>> {
|
||||||
val receivedMessages = LinkedBlockingQueue<ReceivedMessage>()
|
val receivedMessages = LinkedBlockingQueue<ReceivedMessage>()
|
||||||
|
|
||||||
createMessagingServer().start()
|
createMessagingServer(maxMessageSize = serverMaxMessageSize).start()
|
||||||
|
|
||||||
val messagingClient = createMessagingClient(platformVersion = platformVersion)
|
val messagingClient = createMessagingClient(platformVersion = platformVersion, maxMessageSize = clientMaxMessageSize)
|
||||||
messagingClient.addMessageHandler(TOPIC) { message, _, handle ->
|
messagingClient.addMessageHandler(TOPIC) { message, _, handle ->
|
||||||
if (dontAckCondition(message)) return@addMessageHandler
|
if (dontAckCondition(message)) return@addMessageHandler
|
||||||
database.transaction { handle.insideDatabaseTransaction() }
|
database.transaction { handle.insideDatabaseTransaction() }
|
||||||
|
@ -68,6 +68,8 @@ class NetworkRegistrationHelperTest {
|
|||||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||||
doReturn(nodeLegalName).whenever(it).myLegalName
|
doReturn(nodeLegalName).whenever(it).myLegalName
|
||||||
doReturn("").whenever(it).emailAddress
|
doReturn("").whenever(it).emailAddress
|
||||||
|
doReturn(null).whenever(it).tlsCertCrlDistPoint
|
||||||
|
doReturn(null).whenever(it).tlsCertCrlIssuer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@
|
|||||||
package net.corda.testing.node.internal
|
package net.corda.testing.node.internal
|
||||||
|
|
||||||
import net.corda.client.mock.Generator
|
import net.corda.client.mock.Generator
|
||||||
import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme
|
|
||||||
import net.corda.client.rpc.internal.RPCClient
|
import net.corda.client.rpc.internal.RPCClient
|
||||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
||||||
|
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.context.AuthServiceId
|
import net.corda.core.context.AuthServiceId
|
||||||
import net.corda.core.context.Trace
|
import net.corda.core.context.Trace
|
||||||
@ -33,7 +33,7 @@ import net.corda.node.services.messaging.RPCServerConfiguration
|
|||||||
import net.corda.nodeapi.ArtemisTcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.nodeapi.ConnectionDirection
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT
|
import net.corda.nodeapi.internal.serialization.AMQP_RPC_CLIENT_CONTEXT
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
import net.corda.testing.core.MAX_MESSAGE_SIZE
|
import net.corda.testing.core.MAX_MESSAGE_SIZE
|
||||||
import net.corda.testing.driver.JmxPolicy
|
import net.corda.testing.driver.JmxPolicy
|
||||||
@ -523,8 +523,8 @@ class RandomRpcUser {
|
|||||||
val hostAndPort = NetworkHostAndPort.parse(args[1])
|
val hostAndPort = NetworkHostAndPort.parse(args[1])
|
||||||
val username = args[2]
|
val username = args[2]
|
||||||
val password = args[3]
|
val password = args[3]
|
||||||
KryoClientSerializationScheme.initialiseSerialization()
|
AMQPClientSerializationScheme.initialiseSerialization()
|
||||||
val handle = RPCClient<RPCOps>(hostAndPort, null, serializationContext = KRYO_RPC_CLIENT_CONTEXT).start(rpcClass, username, password)
|
val handle = RPCClient<RPCOps>(hostAndPort, null, serializationContext = AMQP_RPC_CLIENT_CONTEXT).start(rpcClass, username, password)
|
||||||
val callGenerators = rpcClass.declaredMethods.map { method ->
|
val callGenerators = rpcClass.declaredMethods.map { method ->
|
||||||
Generator.sequence(method.parameters.map {
|
Generator.sequence(method.parameters.map {
|
||||||
generatorStore[it.type] ?: throw Exception("No generator for ${it.type}")
|
generatorStore[it.type] ?: throw Exception("No generator for ${it.type}")
|
||||||
|
@ -12,7 +12,7 @@ package net.corda.smoketesting
|
|||||||
|
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
import net.corda.client.rpc.CordaRPCConnection
|
import net.corda.client.rpc.CordaRPCConnection
|
||||||
import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme
|
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
@ -74,7 +74,7 @@ class NodeProcess(
|
|||||||
val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java")
|
val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java")
|
||||||
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault())
|
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(systemDefault())
|
||||||
val defaultNetworkParameters = run {
|
val defaultNetworkParameters = run {
|
||||||
KryoClientSerializationScheme.createSerializationEnv().asContextEnv {
|
AMQPClientSerializationScheme.createSerializationEnv().asContextEnv {
|
||||||
// There are no notaries in the network parameters for smoke test nodes. If this is required then we would
|
// There are no notaries in the network parameters for smoke test nodes. If this is required then we would
|
||||||
// need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork
|
// need to introduce the concept of a "network" which predefines the notaries, like the driver and MockNetwork
|
||||||
NetworkParametersCopier(testNetworkParameters())
|
NetworkParametersCopier(testNetworkParameters())
|
||||||
|
@ -13,6 +13,7 @@ package net.corda.testing.core
|
|||||||
import com.nhaarman.mockito_kotlin.any
|
import com.nhaarman.mockito_kotlin.any
|
||||||
import com.nhaarman.mockito_kotlin.doAnswer
|
import com.nhaarman.mockito_kotlin.doAnswer
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
|
import net.corda.core.DoNotImplement
|
||||||
import net.corda.core.internal.staticField
|
import net.corda.core.internal.staticField
|
||||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||||
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
||||||
|
@ -12,13 +12,12 @@ package net.corda.testing.internal
|
|||||||
|
|
||||||
import com.nhaarman.mockito_kotlin.doNothing
|
import com.nhaarman.mockito_kotlin.doNothing
|
||||||
import com.nhaarman.mockito_kotlin.whenever
|
import com.nhaarman.mockito_kotlin.whenever
|
||||||
import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme
|
|
||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
|
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
import net.corda.core.serialization.internal.*
|
import net.corda.core.serialization.internal.*
|
||||||
|
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||||
import net.corda.node.serialization.kryo.KryoServerSerializationScheme
|
import net.corda.node.serialization.kryo.KryoServerSerializationScheme
|
||||||
import net.corda.nodeapi.internal.serialization.*
|
import net.corda.nodeapi.internal.serialization.*
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
|
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
@ -41,16 +40,16 @@ fun <T> withoutTestSerialization(callable: () -> T): T { // TODO: Delete this, s
|
|||||||
|
|
||||||
internal fun createTestSerializationEnv(label: String): SerializationEnvironmentImpl {
|
internal fun createTestSerializationEnv(label: String): SerializationEnvironmentImpl {
|
||||||
val factory = SerializationFactoryImpl().apply {
|
val factory = SerializationFactoryImpl().apply {
|
||||||
registerScheme(KryoClientSerializationScheme())
|
|
||||||
registerScheme(KryoServerSerializationScheme())
|
|
||||||
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
||||||
registerScheme(AMQPServerSerializationScheme(emptyList()))
|
registerScheme(AMQPServerSerializationScheme(emptyList()))
|
||||||
|
// needed for checkpointing
|
||||||
|
registerScheme(KryoServerSerializationScheme())
|
||||||
}
|
}
|
||||||
return object : SerializationEnvironmentImpl(
|
return object : SerializationEnvironmentImpl(
|
||||||
factory,
|
factory,
|
||||||
AMQP_P2P_CONTEXT,
|
AMQP_P2P_CONTEXT,
|
||||||
KRYO_RPC_SERVER_CONTEXT,
|
AMQP_RPC_SERVER_CONTEXT,
|
||||||
KRYO_RPC_CLIENT_CONTEXT,
|
AMQP_RPC_CLIENT_CONTEXT,
|
||||||
AMQP_STORAGE_CONTEXT,
|
AMQP_STORAGE_CONTEXT,
|
||||||
KRYO_CHECKPOINT_CONTEXT
|
KRYO_CHECKPOINT_CONTEXT
|
||||||
) {
|
) {
|
||||||
|
@ -32,6 +32,7 @@ apply plugin: 'kotlin'
|
|||||||
apply plugin: 'application'
|
apply plugin: 'application'
|
||||||
|
|
||||||
evaluationDependsOn(':tools:explorer:capsule')
|
evaluationDependsOn(':tools:explorer:capsule')
|
||||||
|
evaluationDependsOn(':webserver:webcapsule')
|
||||||
|
|
||||||
mainClassName = 'net.corda.demobench.DemoBench'
|
mainClassName = 'net.corda.demobench.DemoBench'
|
||||||
applicationDefaultJvmArgs = [
|
applicationDefaultJvmArgs = [
|
||||||
|
@ -11,13 +11,12 @@
|
|||||||
package net.corda.demobench
|
package net.corda.demobench
|
||||||
|
|
||||||
import javafx.scene.image.Image
|
import javafx.scene.image.Image
|
||||||
import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme
|
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
import net.corda.demobench.views.DemoBenchView
|
import net.corda.demobench.views.DemoBenchView
|
||||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
|
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.nio.charset.StandardCharsets.UTF_8
|
import java.nio.charset.StandardCharsets.UTF_8
|
||||||
@ -69,8 +68,7 @@ class DemoBench : App(DemoBenchView::class) {
|
|||||||
private fun initialiseSerialization() {
|
private fun initialiseSerialization() {
|
||||||
nodeSerializationEnv = SerializationEnvironmentImpl(
|
nodeSerializationEnv = SerializationEnvironmentImpl(
|
||||||
SerializationFactoryImpl().apply {
|
SerializationFactoryImpl().apply {
|
||||||
registerScheme(KryoClientSerializationScheme())
|
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
||||||
registerScheme(AMQPClientSerializationScheme())
|
|
||||||
},
|
},
|
||||||
AMQP_P2P_CONTEXT)
|
AMQP_P2P_CONTEXT)
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ import org.bouncycastle.util.io.Streams
|
|||||||
import org.junit.ClassRule
|
import org.junit.ClassRule
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.lang.Thread.sleep
|
||||||
import java.net.ConnectException
|
import java.net.ConnectException
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
@ -75,7 +76,7 @@ class SSHServerTest : IntegrationTest() {
|
|||||||
// The driver will automatically pick up the annotated flows below
|
// The driver will automatically pick up the annotated flows below
|
||||||
driver {
|
driver {
|
||||||
val node = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user),
|
val node = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user),
|
||||||
customOverrides = mapOf("sshd" to mapOf("port" to 2222)))
|
customOverrides = mapOf("sshd" to mapOf("port" to 2222)) /*, startInSameProcess = true */)
|
||||||
node.getOrThrow()
|
node.getOrThrow()
|
||||||
|
|
||||||
val session = JSch().getSession("u", "localhost", 2222)
|
val session = JSch().getSession("u", "localhost", 2222)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user