Merge pull request #853 from corda/kat-merge-20180517

OS -> Ent merge
This commit is contained in:
Matthew Nesbit 2018-05-21 09:48:39 +01:00 committed by GitHub
commit b07d5b3f26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 6587 additions and 3445 deletions

File diff suppressed because it is too large Load Diff

View File

@ -49,11 +49,11 @@ class AMQPListenerTest {
@Test @Test
fun `Basic AMPQListenerService lifecycle test`() { fun `Basic AMPQListenerService lifecycle test`() {
val configResource = "/net/corda/bridge/singleprocess/bridge.conf" val configResource = "/net/corda/bridge/singleprocess/bridge.conf"
createNetworkParams(tempFolder.root.toPath()) val maxMessageSize = createNetworkParams(tempFolder.root.toPath())
val bridgeConfig = createAndLoadConfigFromResource(tempFolder.root.toPath() / "listener", configResource) val bridgeConfig = createAndLoadConfigFromResource(tempFolder.root.toPath() / "listener", configResource)
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME) bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
val auditService = TestAuditService() val auditService = TestAuditService()
val amqpListenerService = BridgeAMQPListenerServiceImpl(bridgeConfig, auditService) val amqpListenerService = BridgeAMQPListenerServiceImpl(bridgeConfig, maxMessageSize, auditService)
val stateFollower = amqpListenerService.activeChange.toBlocking().iterator val stateFollower = amqpListenerService.activeChange.toBlocking().iterator
val connectionFollower = amqpListenerService.onConnection.toBlocking().iterator val connectionFollower = amqpListenerService.onConnection.toBlocking().iterator
val auditFollower = auditService.onAuditEvent.toBlocking().iterator val auditFollower = auditService.onAuditEvent.toBlocking().iterator
@ -78,7 +78,7 @@ class AMQPListenerTest {
// Fire lots of activity to prove we are good // Fire lots of activity to prove we are good
assertEquals(TestAuditService.AuditEvent.STATUS_CHANGE, auditFollower.next()) assertEquals(TestAuditService.AuditEvent.STATUS_CHANGE, auditFollower.next())
assertEquals(true, amqpListenerService.active) assertEquals(true, amqpListenerService.active)
// Definitely a socket tehre // Definitely a socket there
assertEquals(true, serverListening("localhost", 10005)) assertEquals(true, serverListening("localhost", 10005))
// But not a valid SSL link // But not a valid SSL link
assertEquals(false, connectionFollower.next().connected) assertEquals(false, connectionFollower.next().connected)
@ -95,7 +95,8 @@ class AMQPListenerTest {
clientKeyStore, clientKeyStore,
clientConfig.keyStorePassword, clientConfig.keyStorePassword,
clientTrustStore, clientTrustStore,
true) true,
maxMessageSize = maxMessageSize)
amqpClient.start() amqpClient.start()
// Should see events to show we got a valid connection // Should see events to show we got a valid connection
@ -134,11 +135,11 @@ class AMQPListenerTest {
@Test @Test
fun `Bad certificate audit check`() { fun `Bad certificate audit check`() {
val configResource = "/net/corda/bridge/singleprocess/bridge.conf" val configResource = "/net/corda/bridge/singleprocess/bridge.conf"
createNetworkParams(tempFolder.root.toPath()) val maxMessageSize = createNetworkParams(tempFolder.root.toPath())
val bridgeConfig = createAndLoadConfigFromResource(tempFolder.root.toPath() / "listener", configResource) val bridgeConfig = createAndLoadConfigFromResource(tempFolder.root.toPath() / "listener", configResource)
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME) bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
val auditService = TestAuditService() val auditService = TestAuditService()
val amqpListenerService = BridgeAMQPListenerServiceImpl(bridgeConfig, auditService) val amqpListenerService = BridgeAMQPListenerServiceImpl(bridgeConfig, maxMessageSize, auditService)
amqpListenerService.start() amqpListenerService.start()
auditService.start() auditService.start()
val keyStoreBytes = bridgeConfig.sslKeystore.readAll() val keyStoreBytes = bridgeConfig.sslKeystore.readAll()
@ -165,7 +166,8 @@ class AMQPListenerTest {
clientKeyStore.internal, clientKeyStore.internal,
"password", "password",
clientTrustStore.internal, clientTrustStore.internal,
true) true,
maxMessageSize = maxMessageSize)
amqpClient.start() amqpClient.start()
val connectionEvent = connectionFollower.next() val connectionEvent = connectionFollower.next()
assertEquals(false, connectionEvent.connected) assertEquals(false, connectionEvent.connected)

View File

@ -71,13 +71,13 @@ class TunnelControlTest {
val bridgeConfigResource = "/net/corda/bridge/withfloat/bridge/bridge.conf" val bridgeConfigResource = "/net/corda/bridge/withfloat/bridge/bridge.conf"
val bridgePath = tempFolder.root.toPath() / "bridge" val bridgePath = tempFolder.root.toPath() / "bridge"
bridgePath.createDirectories() bridgePath.createDirectories()
createNetworkParams(bridgePath) val maxMessageSize = createNetworkParams(bridgePath)
val bridgeConfig = createAndLoadConfigFromResource(bridgePath, bridgeConfigResource) val bridgeConfig = createAndLoadConfigFromResource(bridgePath, bridgeConfigResource)
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME) bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
val bridgeAuditService = TestAuditService() val bridgeAuditService = TestAuditService()
val haService = SingleInstanceMasterService(bridgeConfig, bridgeAuditService) val haService = SingleInstanceMasterService(bridgeConfig, bridgeAuditService)
val filterService = createPartialMock<TestIncomingMessageFilterService>() val filterService = createPartialMock<TestIncomingMessageFilterService>()
val bridgeProxiedReceiverService = TunnelingBridgeReceiverService(bridgeConfig, bridgeAuditService, haService, filterService) val bridgeProxiedReceiverService = TunnelingBridgeReceiverService(bridgeConfig, maxMessageSize, bridgeAuditService, haService, filterService)
val bridgeStateFollower = bridgeProxiedReceiverService.activeChange.toBlocking().iterator val bridgeStateFollower = bridgeProxiedReceiverService.activeChange.toBlocking().iterator
bridgeProxiedReceiverService.start() bridgeProxiedReceiverService.start()
assertEquals(false, bridgeStateFollower.next()) assertEquals(false, bridgeStateFollower.next())
@ -101,7 +101,7 @@ class TunnelControlTest {
doReturn(Observable.never<ConnectionChange>()).whenever(it).onConnection doReturn(Observable.never<ConnectionChange>()).whenever(it).onConnection
doReturn(Observable.never<ReceivedMessage>()).whenever(it).onReceive doReturn(Observable.never<ReceivedMessage>()).whenever(it).onReceive
} }
val floatControlListener = FloatControlListenerService(floatConfig, floatAuditService, amqpListenerService) val floatControlListener = FloatControlListenerService(floatConfig, maxMessageSize, floatAuditService, amqpListenerService)
val floatStateFollower = floatControlListener.activeChange.toBlocking().iterator val floatStateFollower = floatControlListener.activeChange.toBlocking().iterator
assertEquals(false, floatStateFollower.next()) assertEquals(false, floatStateFollower.next())
assertEquals(false, floatControlListener.active) assertEquals(false, floatControlListener.active)
@ -149,7 +149,7 @@ class TunnelControlTest {
val bridgeConfigResource = "/net/corda/bridge/withfloat/bridge/bridge.conf" val bridgeConfigResource = "/net/corda/bridge/withfloat/bridge/bridge.conf"
val bridgePath = tempFolder.root.toPath() / "bridge" val bridgePath = tempFolder.root.toPath() / "bridge"
bridgePath.createDirectories() bridgePath.createDirectories()
createNetworkParams(bridgePath) val maxMessageSize = createNetworkParams(bridgePath)
val bridgeConfig = createAndLoadConfigFromResource(bridgePath, bridgeConfigResource) val bridgeConfig = createAndLoadConfigFromResource(bridgePath, bridgeConfigResource)
bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME) bridgeConfig.createBridgeKeyStores(DUMMY_BANK_A_NAME)
val bridgeAuditService = TestAuditService() val bridgeAuditService = TestAuditService()
@ -162,7 +162,7 @@ class TunnelControlTest {
Unit Unit
}.whenever(it).sendMessageToLocalBroker(any()) }.whenever(it).sendMessageToLocalBroker(any())
} }
val bridgeProxiedReceiverService = TunnelingBridgeReceiverService(bridgeConfig, bridgeAuditService, haService, filterService) val bridgeProxiedReceiverService = TunnelingBridgeReceiverService(bridgeConfig, maxMessageSize, bridgeAuditService, haService, filterService)
val bridgeStateFollower = bridgeProxiedReceiverService.activeChange.toBlocking().iterator val bridgeStateFollower = bridgeProxiedReceiverService.activeChange.toBlocking().iterator
bridgeProxiedReceiverService.start() bridgeProxiedReceiverService.start()
bridgeAuditService.start() bridgeAuditService.start()
@ -183,7 +183,7 @@ class TunnelControlTest {
doReturn(Observable.never<ConnectionChange>()).whenever(it).onConnection doReturn(Observable.never<ConnectionChange>()).whenever(it).onConnection
doReturn(receiveObserver).whenever(it).onReceive doReturn(receiveObserver).whenever(it).onReceive
} }
val floatControlListener = FloatControlListenerService(floatConfig, floatAuditService, amqpListenerService) val floatControlListener = FloatControlListenerService(floatConfig, maxMessageSize, floatAuditService, amqpListenerService)
floatControlListener.start() floatControlListener.start()
floatAuditService.start() floatAuditService.start()
amqpListenerService.start() amqpListenerService.start()

View File

@ -0,0 +1,29 @@
package net.corda.bridge.internal
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.amqpMagic
import java.util.concurrent.ConcurrentHashMap
class AMQPBridgeSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, 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)
= (magic == amqpMagic && target == SerializationContext.UseCase.P2P)
}

View File

@ -34,7 +34,6 @@ import net.corda.nodeapi.internal.network.SignedNetworkParameters
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.AMQP_STORAGE_CONTEXT import net.corda.nodeapi.internal.serialization.AMQP_STORAGE_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.Subscription import rx.Subscription
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -71,12 +70,9 @@ class BridgeInstance(val conf: BridgeConfiguration,
val classloader = this.javaClass.classLoader val classloader = this.javaClass.classLoader
nodeSerializationEnv = SerializationEnvironmentImpl( nodeSerializationEnv = SerializationEnvironmentImpl(
SerializationFactoryImpl().apply { SerializationFactoryImpl().apply {
registerScheme(AMQPServerSerializationScheme(emptyList())) registerScheme(AMQPBridgeSerializationScheme(emptyList()))
}, },
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader), p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader))
rpcServerContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader),
checkpointContext = AMQP_P2P_CONTEXT.withClassLoader(classloader))
} }
} }

View File

@ -80,7 +80,7 @@ class SimpleMessageFilterService(val conf: BridgeConfiguration,
require(inboundMessage.payload.size > 0) { "No valid payload" } require(inboundMessage.payload.size > 0) { "No valid payload" }
val validInboxTopic = bridgeSenderService.validateReceiveTopic(inboundMessage.topic, sourceLegalName) val validInboxTopic = bridgeSenderService.validateReceiveTopic(inboundMessage.topic, sourceLegalName)
require(validInboxTopic) { "Topic not a legitimate Inbox for a node on this Artemis Broker ${inboundMessage.topic}" } require(validInboxTopic) { "Topic not a legitimate Inbox for a node on this Artemis Broker ${inboundMessage.topic}" }
require(inboundMessage.applicationProperties.keys.all { it!!.toString() in whiteListedAMQPHeaders }) { "Disallowed header present in ${inboundMessage.applicationProperties.keys.map { it.toString() }}" } require(inboundMessage.applicationProperties.keys.all { it in whiteListedAMQPHeaders }) { "Disallowed header present in ${inboundMessage.applicationProperties.keys}" }
} }
override fun sendMessageToLocalBroker(inboundMessage: ReceivedMessage) { override fun sendMessageToLocalBroker(inboundMessage: ReceivedMessage) {

View File

@ -31,6 +31,7 @@ import java.security.KeyStore
import java.util.* import java.util.*
class BridgeAMQPListenerServiceImpl(val conf: BridgeConfiguration, class BridgeAMQPListenerServiceImpl(val conf: BridgeConfiguration,
val maxMessageSize: Int,
val auditService: BridgeAuditService, val auditService: BridgeAuditService,
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeAMQPListenerService, ServiceStateSupport by stateHelper { private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeAMQPListenerService, ServiceStateSupport by stateHelper {
companion object { companion object {
@ -60,7 +61,16 @@ class BridgeAMQPListenerServiceImpl(val conf: BridgeConfiguration,
val keyStore = loadKeyStoreAndWipeKeys(keyStoreBytes, keyStorePassword) val keyStore = loadKeyStoreAndWipeKeys(keyStoreBytes, keyStorePassword)
val trustStore = loadKeyStoreAndWipeKeys(trustStoreBytes, trustStorePassword) val trustStore = loadKeyStoreAndWipeKeys(trustStoreBytes, trustStorePassword)
val bindAddress = conf.inboundConfig!!.listeningAddress val bindAddress = conf.inboundConfig!!.listeningAddress
val server = AMQPServer(bindAddress.host, bindAddress.port, PEER_USER, PEER_USER, keyStore, keyStorePrivateKeyPassword, trustStore, conf.crlCheckSoftFail, conf.enableAMQPPacketTrace) val server = AMQPServer(bindAddress.host,
bindAddress.port,
PEER_USER,
PEER_USER,
keyStore,
keyStorePrivateKeyPassword,
trustStore,
conf.crlCheckSoftFail,
maxMessageSize,
conf.enableAMQPPacketTrace)
onConnectSubscription = server.onConnection.subscribe(_onConnection) onConnectSubscription = server.onConnection.subscribe(_onConnection)
onConnectAuditSubscription = server.onConnection.subscribe { onConnectAuditSubscription = server.onConnection.subscribe {
if (it.connected) { if (it.connected) {

View File

@ -33,6 +33,7 @@ import kotlin.concurrent.withLock
class FloatControlListenerService(val conf: BridgeConfiguration, class FloatControlListenerService(val conf: BridgeConfiguration,
val maxMessageSize: Int,
val auditService: BridgeAuditService, val auditService: BridgeAuditService,
val amqpListener: BridgeAMQPListenerService, val amqpListener: BridgeAMQPListenerService,
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : FloatControlService, ServiceStateSupport by stateHelper { private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : FloatControlService, ServiceStateSupport by stateHelper {
@ -82,7 +83,16 @@ class FloatControlListenerService(val conf: BridgeConfiguration,
private fun startControlListener() { private fun startControlListener() {
lock.withLock { lock.withLock {
val controlServer = AMQPServer(floatControlAddress.host, floatControlAddress.port, null, null, keyStore, keyStorePrivateKeyPassword, trustStore, conf.crlCheckSoftFail, conf.enableAMQPPacketTrace) val controlServer = AMQPServer(floatControlAddress.host,
floatControlAddress.port,
null,
null,
keyStore,
keyStorePrivateKeyPassword,
trustStore,
conf.crlCheckSoftFail,
maxMessageSize,
conf.enableAMQPPacketTrace)
connectSubscriber = controlServer.onConnection.subscribe { onConnectToControl(it) } connectSubscriber = controlServer.onConnection.subscribe { onConnectToControl(it) }
receiveSubscriber = controlServer.onReceive.subscribe { onControlMessage(it) } receiveSubscriber = controlServer.onReceive.subscribe { onControlMessage(it) }
amqpControlServer = controlServer amqpControlServer = controlServer
@ -214,7 +224,7 @@ class FloatControlListenerService(val conf: BridgeConfiguration,
message.complete(true) // consume message so it isn't resent forever message.complete(true) // consume message so it isn't resent forever
return return
} }
val appProperties = message.applicationProperties.map { Pair(it.key!!.toString(), it.value) }.toList() val appProperties = message.applicationProperties.map { Pair(it.key, it.value) }.toList()
try { try {
val wrappedMessage = FloatDataPacket(message.topic, val wrappedMessage = FloatDataPacket(message.topic,
appProperties, appProperties,

View File

@ -36,6 +36,7 @@ import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException import java.util.concurrent.TimeoutException
class TunnelingBridgeReceiverService(val conf: BridgeConfiguration, class TunnelingBridgeReceiverService(val conf: BridgeConfiguration,
val maxMessageSize: Int,
val auditService: BridgeAuditService, val auditService: BridgeAuditService,
haService: BridgeMasterService, haService: BridgeMasterService,
val filterService: IncomingMessageFilterService, val filterService: IncomingMessageFilterService,
@ -72,7 +73,16 @@ class TunnelingBridgeReceiverService(val conf: BridgeConfiguration,
statusSubscriber = statusFollower.activeChange.subscribe { statusSubscriber = statusFollower.activeChange.subscribe {
if (it) { if (it) {
val floatAddresses = conf.bridgeInnerConfig!!.floatAddresses val floatAddresses = conf.bridgeInnerConfig!!.floatAddresses
val controlClient = AMQPClient(floatAddresses, setOf(expectedCertificateSubject), null, null, controlLinkKeyStore, controLinkKeyStorePrivateKeyPassword, controlLinkTrustStore, conf.crlCheckSoftFail, conf.enableAMQPPacketTrace) val controlClient = AMQPClient(floatAddresses,
setOf(expectedCertificateSubject),
null,
null,
controlLinkKeyStore,
controLinkKeyStorePrivateKeyPassword,
controlLinkTrustStore,
conf.crlCheckSoftFail,
maxMessageSize,
conf.enableAMQPPacketTrace)
connectSubscriber = controlClient.onConnection.subscribe { onConnectToControl(it) } connectSubscriber = controlClient.onConnection.subscribe { onConnectToControl(it) }
receiveSubscriber = controlClient.onReceive.subscribe { onFloatMessage(it) } receiveSubscriber = controlClient.onReceive.subscribe { onFloatMessage(it) }
amqpControlClient = controlClient amqpControlClient = controlClient

View File

@ -21,8 +21,9 @@ import net.corda.nodeapi.internal.bridging.BridgeControlListener
import rx.Subscription import rx.Subscription
class DirectBridgeSenderService(val conf: BridgeConfiguration, class DirectBridgeSenderService(val conf: BridgeConfiguration,
val maxMessageSize: Int,
val auditService: BridgeAuditService, val auditService: BridgeAuditService,
val haService: BridgeMasterService, haService: BridgeMasterService,
val artemisConnectionService: BridgeArtemisConnectionService, val artemisConnectionService: BridgeArtemisConnectionService,
private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeSenderService, ServiceStateSupport by stateHelper { private val stateHelper: ServiceStateHelper = ServiceStateHelper(log)) : BridgeSenderService, ServiceStateSupport by stateHelper {
companion object { companion object {
@ -33,7 +34,7 @@ class DirectBridgeSenderService(val conf: BridgeConfiguration,
private var statusSubscriber: Subscription? = null private var statusSubscriber: Subscription? = null
private var connectionSubscriber: Subscription? = null private var connectionSubscriber: Subscription? = null
private var listenerActiveSubscriber: Subscription? = null private var listenerActiveSubscriber: Subscription? = null
private var bridgeControlListener: BridgeControlListener = BridgeControlListener(conf, conf.outboundConfig!!.socksProxyConfig, { ForwardingArtemisMessageClient(artemisConnectionService) }) private var bridgeControlListener: BridgeControlListener = BridgeControlListener(conf, conf.outboundConfig!!.socksProxyConfig, maxMessageSize, { ForwardingArtemisMessageClient(artemisConnectionService) })
init { init {
statusFollower = ServiceStateCombiner(listOf(auditService, artemisConnectionService, haService)) statusFollower = ServiceStateCombiner(listOf(auditService, artemisConnectionService, haService))

View File

@ -49,13 +49,13 @@ class BridgeSupervisorServiceImpl(val conf: BridgeConfiguration,
haService = ExternalMasterElectionService(conf, auditService) haService = ExternalMasterElectionService(conf, auditService)
} }
artemisService = BridgeArtemisConnectionServiceImpl(conf, maxMessageSize, auditService) artemisService = BridgeArtemisConnectionServiceImpl(conf, maxMessageSize, auditService)
senderService = DirectBridgeSenderService(conf, auditService, haService, artemisService) senderService = DirectBridgeSenderService(conf, maxMessageSize, auditService, haService, artemisService)
filterService = SimpleMessageFilterService(conf, auditService, artemisService, senderService) filterService = SimpleMessageFilterService(conf, auditService, artemisService, senderService)
receiverService = if (conf.bridgeMode == BridgeMode.SenderReceiver) { receiverService = if (conf.bridgeMode == BridgeMode.SenderReceiver) {
InProcessBridgeReceiverService(conf, auditService, haService, inProcessAMQPListenerService!!, filterService) InProcessBridgeReceiverService(conf, auditService, haService, inProcessAMQPListenerService!!, filterService)
} else { } else {
require(inProcessAMQPListenerService == null) { "Should not have an in process instance of the AMQPListenerService" } require(inProcessAMQPListenerService == null) { "Should not have an in process instance of the AMQPListenerService" }
TunnelingBridgeReceiverService(conf, auditService, haService, filterService) TunnelingBridgeReceiverService(conf, maxMessageSize, auditService, haService, filterService)
} }
statusFollower = ServiceStateCombiner(listOf(haService, senderService, receiverService, filterService)) statusFollower = ServiceStateCombiner(listOf(haService, senderService, receiverService, filterService))
activeChange.subscribe { activeChange.subscribe {

View File

@ -34,10 +34,10 @@ class FloatSupervisorServiceImpl(val conf: BridgeConfiguration,
private var statusSubscriber: Subscription? = null private var statusSubscriber: Subscription? = null
init { init {
amqpListenerService = BridgeAMQPListenerServiceImpl(conf, auditService) amqpListenerService = BridgeAMQPListenerServiceImpl(conf, maxMessageSize, auditService)
floatControlService = if (conf.bridgeMode == BridgeMode.FloatOuter) { floatControlService = if (conf.bridgeMode == BridgeMode.FloatOuter) {
require(conf.haConfig == null) { "Float process should not have HA config, that is controlled via the bridge." } require(conf.haConfig == null) { "Float process should not have HA config, that is controlled via the bridge." }
FloatControlListenerService(conf, auditService, amqpListenerService) FloatControlListenerService(conf, maxMessageSize, auditService, amqpListenerService)
} else { } else {
null null
} }

View File

@ -33,10 +33,10 @@ import java.nio.file.Path
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.time.Instant import java.time.Instant
fun createNetworkParams(baseDirectory: Path) { fun createNetworkParams(baseDirectory: Path): Int {
val dummyNotaryParty = TestIdentity(DUMMY_NOTARY_NAME) val dummyNotaryParty = TestIdentity(DUMMY_NOTARY_NAME)
val notaryInfo = NotaryInfo(dummyNotaryParty.party, false) val notaryInfo = NotaryInfo(dummyNotaryParty.party, false)
val copier = NetworkParametersCopier(NetworkParameters( val networkParameters = NetworkParameters(
minimumPlatformVersion = 1, minimumPlatformVersion = 1,
notaries = listOf(notaryInfo), notaries = listOf(notaryInfo),
modifiedTime = Instant.now(), modifiedTime = Instant.now(),
@ -44,8 +44,10 @@ fun createNetworkParams(baseDirectory: Path) {
maxTransactionSize = 40000, maxTransactionSize = 40000,
epoch = 1, epoch = 1,
whitelistedContractImplementations = emptyMap<String, List<AttachmentId>>() whitelistedContractImplementations = emptyMap<String, List<AttachmentId>>()
), overwriteFile = true) )
val copier = NetworkParametersCopier(networkParameters, overwriteFile = true)
copier.install(baseDirectory) copier.install(baseDirectory)
return networkParameters.maxMessageSize
} }

View File

@ -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}"

View File

@ -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")
} }
} }
} }

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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()
}
}

View File

@ -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<*>)
}

View File

@ -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()
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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))
} }

View File

@ -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.")
} }
} }

View File

@ -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.

View File

@ -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.

View File

@ -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>,

View File

@ -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).
*/ */

View File

@ -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> {

View File

@ -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 {

View File

@ -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.

View File

@ -242,8 +242,8 @@ absolute path to the node's base directory.
Note: If crlCheckSoftFail is FALSE (meaning that there is the strict CRL checking mode) this value needs to be set. 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, :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. 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. Note: If this parameter is set then the tlsCertCrlDistPoint needs to be set as well.
Examples Examples
-------- --------
@ -334,4 +334,4 @@ path to the node's base directory.
:permissions: A list of permissions for starting flows via RPC. To give the user the permission to start the flow :permissions: A list of permissions for starting flows via RPC. To give the user the permission to start the flow
``foo.bar.FlowClass``, add the string ``StartFlow.foo.bar.FlowClass`` to the list. If the list ``foo.bar.FlowClass``, add the string ``StartFlow.foo.bar.FlowClass`` to the list. If the list
contains the string ``ALL``, the user can start any flow via RPC. This value is intended for administrator contains the string ``ALL``, the user can start any flow via RPC. This value is intended for administrator
users and for development. users and for development.

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,30 @@
package com.r3.corda.networkmanage.common.utils
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.amqpMagic
import java.util.concurrent.ConcurrentHashMap
class AMQPNetworkServicesSerializationScheme (
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
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) =
(magic == amqpMagic && target == SerializationContext.UseCase.P2P)
}

View File

@ -23,7 +23,6 @@ import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509KeyStore
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 org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.asn1.x500.style.BCStyle
@ -60,7 +59,7 @@ fun initialiseSerialization() {
val context = AMQP_P2P_CONTEXT val context = AMQP_P2P_CONTEXT
nodeSerializationEnv = SerializationEnvironmentImpl( nodeSerializationEnv = SerializationEnvironmentImpl(
SerializationFactoryImpl().apply { SerializationFactoryImpl().apply {
registerScheme(AMQPClientSerializationScheme()) registerScheme(AMQPNetworkServicesSerializationScheme())
}, },
context) context)
} }

View File

@ -8,10 +8,7 @@ import net.corda.nodeapi.internal.crypto.X509KeyStore
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 org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Before import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
@ -89,7 +86,8 @@ class CertificateRevocationListNodeTests {
sslS, sslS,
"cordacadevpass", "cordacadevpass",
tS, tS,
false) false,
MAX_MESSAGE_SIZE)
} }
private fun createServer(port: Int): AMQPServer { private fun createServer(port: Int): AMQPServer {
@ -103,6 +101,7 @@ class CertificateRevocationListNodeTests {
sslS, sslS,
"cordacadevpass", "cordacadevpass",
tS, tS,
false) false,
MAX_MESSAGE_SIZE)
} }
} }

View File

@ -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 {

View File

@ -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()
}
} }
} }
} }

View File

@ -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)

View File

@ -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

View File

@ -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]" }
}

View File

@ -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?
}

View File

@ -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
} }

View File

@ -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()

View File

@ -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
}
}
} }

View File

@ -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

View File

@ -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
@ -66,6 +67,7 @@ class AMQPClient(val targets: List<NetworkHostAndPort>,
private val keyStorePrivateKeyPassword: String, private val keyStorePrivateKeyPassword: String,
private val trustStore: KeyStore, private val trustStore: KeyStore,
private val crlCheckSoftFail: Boolean, private val crlCheckSoftFail: Boolean,
private val maxMessageSize: Int,
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) : AutoCloseable {
@ -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)
} }

View File

@ -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,6 +52,7 @@ 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 maxMessageSize: Int,
private val trace: Boolean = false) : AutoCloseable { private val trace: Boolean = false) : AutoCloseable {
companion object { companion object {
@ -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) maxMessageSize: Int,
trace: Boolean = false) : this(hostName, port, userName, password, keyStore, keyStorePrivateKeyPassword.toCharArray(), trustStore, crlCheckSoftFail, maxMessageSize, trace)
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"

View File

@ -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()),

View File

@ -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
} }

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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()
} }

View File

@ -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)
}
}
}

View File

@ -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,

View File

@ -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");
}
}); });
} }

View File

@ -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(),

View File

@ -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

View File

@ -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,

View File

@ -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())
} }
} }

View File

@ -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
) )
} }
} }

View File

@ -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 {

View File

@ -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)
} }
} }

View File

@ -57,7 +57,7 @@ class SocksTests {
private abstract class AbstractNodeConfiguration : NodeConfiguration private abstract class AbstractNodeConfiguration : NodeConfiguration
private class SocksServer(val port: Int) { private class SocksServer(port: Int) {
private val bossGroup = NioEventLoopGroup(1) private val bossGroup = NioEventLoopGroup(1)
private val workerGroup = NioEventLoopGroup() private val workerGroup = NioEventLoopGroup()
private var closeFuture: ChannelFuture? = null private var closeFuture: ChannelFuture? = null
@ -314,7 +314,10 @@ class SocksTests {
clientKeystore, clientKeystore,
clientConfig.keyStorePassword, clientConfig.keyStorePassword,
clientTruststore, true, clientTruststore, true,
socksProxyConfig = SocksProxyConfig(SocksProxyVersion.SOCKS5, NetworkHostAndPort("127.0.0.1", socksPort), null, null)) MAX_MESSAGE_SIZE,
socksProxyConfig = SocksProxyConfig(SocksProxyVersion.SOCKS5,
NetworkHostAndPort("127.0.0.1", socksPort), null, null)
)
} }
private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int): AMQPClient { private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int): AMQPClient {
@ -335,8 +338,14 @@ class SocksTests {
PEER_USER, PEER_USER,
clientKeystore, clientKeystore,
clientConfig.keyStorePassword, clientConfig.keyStorePassword,
clientTruststore, true, true, sharedEventGroup, clientTruststore,
socksProxyConfig = SocksProxyConfig(SocksProxyVersion.SOCKS5, NetworkHostAndPort("127.0.0.1", socksPort), null, null)) true,
MAX_MESSAGE_SIZE,
true,
sharedEventGroup,
socksProxyConfig = SocksProxyConfig(SocksProxyVersion.SOCKS5,
NetworkHostAndPort("127.0.0.1", socksPort), null, null)
)
} }
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME): AMQPServer { private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME): AMQPServer {
@ -358,6 +367,7 @@ class SocksTests {
serverKeystore, serverKeystore,
serverConfig.keyStorePassword, serverConfig.keyStorePassword,
serverTruststore, serverTruststore,
true) true,
MAX_MESSAGE_SIZE)
} }
} }

View File

@ -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())
}
} }
} }
} }

View File

@ -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
@ -109,13 +109,13 @@ open class Node(configuration: NodeConfiguration,
@JvmStatic @JvmStatic
protected fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader { protected 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

View File

@ -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)
}
}

View File

@ -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<*>)
}

View File

@ -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)
}
}

View File

@ -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()
}
} }

View File

@ -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())
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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!!)
} }

View File

@ -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>

View File

@ -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

View File

@ -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)
}
}

View File

@ -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}")
}
}
}

View File

@ -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))
}

View File

@ -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) { }
}

View File

@ -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)

View File

@ -18,10 +18,6 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.internal.configureDatabase import net.corda.node.internal.configureDatabase
import net.corda.node.services.config.* import net.corda.node.services.config.*
import net.corda.node.services.config.CertChainPolicyConfig
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.P2PMessagingRetryConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.network.NetworkMapCacheImpl
import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
@ -50,6 +46,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 +83,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 +146,42 @@ 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))
thread {
messagingClient.send(tooLagerMessage, messagingClient.myAddress)
}.join(10.seconds.toMillis())
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 +359,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() }

View File

@ -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}")

View File

@ -1,6 +1,6 @@
package net.corda.behave.service.proxy package net.corda.behave.service.proxy
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.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
@ -36,8 +36,8 @@ class CordaRPCProxyClient(private val targetHostAndPort: NetworkHostAndPort) : C
init { init {
try { try {
KryoClientSerializationScheme.initialiseSerialization() AMQPClientSerializationScheme.initialiseSerialization()
} catch (e: Exception) { log.warn("Kryo RPC Client serialization already initialised.")} } catch (e: Exception) { log.warn("AMQP RPC Client serialization already initialised.")}
} }
override fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T> { override fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T> {

View File

@ -2,15 +2,14 @@ package net.corda.behave.service.proxy
import net.corda.behave.service.proxy.RPCProxyServer.Companion.initialiseSerialization import net.corda.behave.service.proxy.RPCProxyServer.Companion.initialiseSerialization
import net.corda.behave.service.proxy.RPCProxyServer.Companion.log import net.corda.behave.service.proxy.RPCProxyServer.Companion.log
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.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT 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.AMQP_RPC_CLIENT_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.handler.HandlerCollection import org.eclipse.jetty.server.handler.HandlerCollection
@ -74,11 +73,10 @@ class RPCProxyServer(hostAndPort: NetworkHostAndPort, val webService: RPCProxyWe
nodeSerializationEnv = nodeSerializationEnv =
SerializationEnvironmentImpl( SerializationEnvironmentImpl(
SerializationFactoryImpl().apply { SerializationFactoryImpl().apply {
registerScheme(KryoClientSerializationScheme())
registerScheme(AMQPClientSerializationScheme(emptyList())) registerScheme(AMQPClientSerializationScheme(emptyList()))
}, },
AMQP_P2P_CONTEXT, AMQP_P2P_CONTEXT,
rpcClientContext = KRYO_RPC_CLIENT_CONTEXT) rpcClientContext = AMQP_RPC_CLIENT_CONTEXT)
} }
catch(e: Exception) { log.warn("Skipping initialiseSerialization: ${e.message}") } catch(e: Exception) { log.warn("Skipping initialiseSerialization: ${e.message}") }
} }

View File

@ -3,7 +3,7 @@ package net.corda.behave.service.proxy
import net.corda.behave.service.proxy.RPCProxyWebService.Companion.RPC_PROXY_PATH import net.corda.behave.service.proxy.RPCProxyWebService.Companion.RPC_PROXY_PATH
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.internal.serialization.kryo.KryoClientSerializationScheme import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
@ -36,7 +36,7 @@ class RPCProxyWebService(targetHostAndPort: NetworkHostAndPort) {
effectiveSerializationEnv effectiveSerializationEnv
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
try { try {
KryoClientSerializationScheme.initialiseSerialization() 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.
} }

View File

@ -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())

View File

@ -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

View File

@ -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
) { ) {

View File

@ -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 = [

View File

@ -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)
} }

View File

@ -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)