mirror of
https://github.com/corda/corda.git
synced 2025-06-16 14:18:20 +00:00
Support HA without load balancer (#3889)
Allow configuration in node for additional advertised addresses. fix logic error Use empty list as default config not null Allow multiple addresses in NodeInfo Describe new additionalP2PAddresses property in docs. Add integration test of additionalP2PAddress feature Fixup after rebase Address PR comment Address PR comments by removing unused element of NodeAddress
This commit is contained in:
@ -96,6 +96,9 @@ absolute path to the node's base directory.
|
|||||||
note that the host is the included as the advertised entry in the network map. As a result the value listed
|
note that the host is the included as the advertised entry in the network map. As a result the value listed
|
||||||
here must be externally accessible when running nodes across a cluster of machines. If the provided host is unreachable,
|
here must be externally accessible when running nodes across a cluster of machines. If the provided host is unreachable,
|
||||||
the node will try to auto-discover its public one.
|
the node will try to auto-discover its public one.
|
||||||
|
|
||||||
|
:additionalP2PAddresses: An array of additional host:port values, which will be included in the advertised NodeInfo in the network map in addition to the ``p2pAddress``.
|
||||||
|
Nodes can use this configuration option to advertise HA endpoints and aliases to external parties. If not specified the default value is an empty list.
|
||||||
|
|
||||||
:flowTimeout: When a flow implementing the ``TimedFlow`` interface does not complete in time, it is restarted from the
|
:flowTimeout: When a flow implementing the ``TimedFlow`` interface does not complete in time, it is restarted from the
|
||||||
initial checkpoint. Currently only used for notarisation requests: if a notary replica dies while processing a notarisation request,
|
initial checkpoint. Currently only used for notarisation requests: if a notary replica dies while processing a notarisation request,
|
||||||
|
@ -6,7 +6,6 @@ import net.corda.core.messaging.MessageRecipientGroup
|
|||||||
import net.corda.core.messaging.MessageRecipients
|
import net.corda.core.messaging.MessageRecipients
|
||||||
import net.corda.core.messaging.SingleMessageRecipient
|
import net.corda.core.messaging.SingleMessageRecipient
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
|
||||||
import org.apache.activemq.artemis.api.core.Message
|
import org.apache.activemq.artemis.api.core.Message
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -77,9 +76,7 @@ class ArtemisMessagingComponent {
|
|||||||
val queueName: String
|
val queueName: String
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ArtemisPeerAddress : ArtemisAddress, SingleMessageRecipient {
|
interface ArtemisPeerAddress : ArtemisAddress, SingleMessageRecipient
|
||||||
val hostAndPort: NetworkHostAndPort
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the class used to implement [SingleMessageRecipient], for now. Note that in future this class
|
* This is the class used to implement [SingleMessageRecipient], for now. Note that in future this class
|
||||||
@ -90,12 +87,11 @@ class ArtemisMessagingComponent {
|
|||||||
* an advertised service's queue.
|
* an advertised service's queue.
|
||||||
*
|
*
|
||||||
* @param queueName The name of the queue this address is associated with.
|
* @param queueName The name of the queue this address is associated with.
|
||||||
* @param hostAndPort The address of the node.
|
|
||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class NodeAddress(override val queueName: String, override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress {
|
data class NodeAddress(override val queueName: String) : ArtemisPeerAddress {
|
||||||
constructor(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort) :
|
constructor(peerIdentity: PublicKey) :
|
||||||
this("$PEERS_PREFIX${peerIdentity.toStringShort()}", hostAndPort)
|
this("$PEERS_PREFIX${peerIdentity.toStringShort()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,16 +4,13 @@ import io.netty.channel.EventLoopGroup
|
|||||||
import io.netty.channel.nio.NioEventLoopGroup
|
import io.netty.channel.nio.NioEventLoopGroup
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.node.NodeInfo
|
|
||||||
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.ArtemisMessagingClient
|
import net.corda.nodeapi.internal.ArtemisMessagingClient
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_USER
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress
|
||||||
import net.corda.nodeapi.internal.ArtemisSessionProvider
|
import net.corda.nodeapi.internal.ArtemisSessionProvider
|
||||||
import net.corda.nodeapi.internal.bridging.AMQPBridgeManager.AMQPBridge.Companion.getBridgeName
|
|
||||||
import net.corda.nodeapi.internal.config.CertificateStore
|
import net.corda.nodeapi.internal.config.CertificateStore
|
||||||
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
||||||
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
|
import net.corda.nodeapi.internal.protonwrapper.messages.MessageStatus
|
||||||
@ -40,7 +37,7 @@ import kotlin.concurrent.withLock
|
|||||||
class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, private val artemisMessageClientFactory: () -> ArtemisSessionProvider) : BridgeManager {
|
class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, private val artemisMessageClientFactory: () -> ArtemisSessionProvider) : BridgeManager {
|
||||||
|
|
||||||
private val lock = ReentrantLock()
|
private val lock = ReentrantLock()
|
||||||
private val bridgeNameToBridgeMap = mutableMapOf<String, AMQPBridge>()
|
private val queueNamesToBridgesMap = mutableMapOf<String, MutableList<AMQPBridge>>()
|
||||||
|
|
||||||
private class AMQPConfigurationImpl private constructor(override val keyStore: CertificateStore,
|
private class AMQPConfigurationImpl private constructor(override val keyStore: CertificateStore,
|
||||||
override val trustStore: CertificateStore,
|
override val trustStore: CertificateStore,
|
||||||
@ -66,14 +63,13 @@ class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, pri
|
|||||||
* If the delivery fails the session is rolled back to prevent loss of the message. This may cause duplicate delivery,
|
* If the delivery fails the session is rolled back to prevent loss of the message. This may cause duplicate delivery,
|
||||||
* however Artemis and the remote Corda instanced will deduplicate these messages.
|
* however Artemis and the remote Corda instanced will deduplicate these messages.
|
||||||
*/
|
*/
|
||||||
private class AMQPBridge(private val queueName: String,
|
private class AMQPBridge(val queueName: String,
|
||||||
private val target: NetworkHostAndPort,
|
val targets: List<NetworkHostAndPort>,
|
||||||
private val legalNames: Set<CordaX500Name>,
|
private val legalNames: Set<CordaX500Name>,
|
||||||
private val amqpConfig: AMQPConfiguration,
|
private val amqpConfig: AMQPConfiguration,
|
||||||
sharedEventGroup: EventLoopGroup,
|
sharedEventGroup: EventLoopGroup,
|
||||||
private val artemis: ArtemisSessionProvider) {
|
private val artemis: ArtemisSessionProvider) {
|
||||||
companion object {
|
companion object {
|
||||||
fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort"
|
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,8 +77,7 @@ class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, pri
|
|||||||
val oldMDC = MDC.getCopyOfContextMap()
|
val oldMDC = MDC.getCopyOfContextMap()
|
||||||
try {
|
try {
|
||||||
MDC.put("queueName", queueName)
|
MDC.put("queueName", queueName)
|
||||||
MDC.put("target", target.toString())
|
MDC.put("targets", targets.joinToString(separator = ";") { it.toString() })
|
||||||
MDC.put("bridgeName", bridgeName)
|
|
||||||
MDC.put("legalNames", legalNames.joinToString(separator = ";") { it.toString() })
|
MDC.put("legalNames", legalNames.joinToString(separator = ";") { it.toString() })
|
||||||
MDC.put("maxMessageSize", amqpConfig.maxMessageSize.toString())
|
MDC.put("maxMessageSize", amqpConfig.maxMessageSize.toString())
|
||||||
block()
|
block()
|
||||||
@ -101,8 +96,7 @@ class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, pri
|
|||||||
|
|
||||||
private fun logWarnWithMDC(msg: String) = withMDC { log.warn(msg) }
|
private fun logWarnWithMDC(msg: String) = withMDC { log.warn(msg) }
|
||||||
|
|
||||||
val amqpClient = AMQPClient(listOf(target), legalNames, amqpConfig, sharedThreadPool = sharedEventGroup)
|
val amqpClient = AMQPClient(targets, legalNames, amqpConfig, sharedThreadPool = sharedEventGroup)
|
||||||
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
|
||||||
private var consumer: ClientConsumer? = null
|
private var consumer: ClientConsumer? = null
|
||||||
@ -194,39 +188,37 @@ class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, pri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun gatherAddresses(node: NodeInfo): List<ArtemisMessagingComponent.NodeAddress> {
|
override fun deployBridge(queueName: String, targets: List<NetworkHostAndPort>, legalNames: Set<CordaX500Name>) {
|
||||||
return node.legalIdentitiesAndCerts.map { ArtemisMessagingComponent.NodeAddress(it.party.owningKey, node.addresses[0]) }
|
val newBridge = lock.withLock {
|
||||||
}
|
val bridges = queueNamesToBridgesMap.getOrPut(queueName) { mutableListOf() }
|
||||||
|
for (target in targets) {
|
||||||
override fun deployBridge(queueName: String, target: NetworkHostAndPort, legalNames: Set<CordaX500Name>) {
|
if (bridges.any { it.targets.contains(target) }) {
|
||||||
if (bridgeExists(getBridgeName(queueName, target))) {
|
return
|
||||||
return
|
}
|
||||||
}
|
}
|
||||||
val newBridge = AMQPBridge(queueName, target, legalNames, amqpConfig, sharedEventLoopGroup!!, artemis!!)
|
val newBridge = AMQPBridge(queueName, targets, legalNames, amqpConfig, sharedEventLoopGroup!!, artemis!!)
|
||||||
lock.withLock {
|
bridges += newBridge
|
||||||
bridgeNameToBridgeMap[newBridge.bridgeName] = newBridge
|
newBridge
|
||||||
}
|
}
|
||||||
newBridge.start()
|
newBridge.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun destroyBridges(node: NodeInfo) {
|
override fun destroyBridge(queueName: String, targets: List<NetworkHostAndPort>) {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
gatherAddresses(node).forEach {
|
val bridges = queueNamesToBridgesMap[queueName] ?: mutableListOf()
|
||||||
val bridge = bridgeNameToBridgeMap.remove(getBridgeName(it.queueName, it.hostAndPort))
|
for (target in targets) {
|
||||||
bridge?.stop()
|
val bridge = bridges.firstOrNull { it.targets.contains(target) }
|
||||||
|
if (bridge != null) {
|
||||||
|
bridges -= bridge
|
||||||
|
if (bridges.isEmpty()) {
|
||||||
|
queueNamesToBridgesMap.remove(queueName)
|
||||||
|
}
|
||||||
|
bridge.stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun destroyBridge(queueName: String, hostAndPort: NetworkHostAndPort) {
|
|
||||||
lock.withLock {
|
|
||||||
val bridge = bridgeNameToBridgeMap.remove(getBridgeName(queueName, hostAndPort))
|
|
||||||
bridge?.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bridgeExists(bridgeName: String): Boolean = lock.withLock { bridgeNameToBridgeMap.containsKey(bridgeName) }
|
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
sharedEventLoopGroup = NioEventLoopGroup(NUM_BRIDGE_THREADS)
|
sharedEventLoopGroup = NioEventLoopGroup(NUM_BRIDGE_THREADS)
|
||||||
val artemis = artemisMessageClientFactory()
|
val artemis = artemisMessageClientFactory()
|
||||||
@ -238,13 +230,13 @@ class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, pri
|
|||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
for (bridge in bridgeNameToBridgeMap.values) {
|
for (bridge in queueNamesToBridgesMap.values.flatten()) {
|
||||||
bridge.stop()
|
bridge.stop()
|
||||||
}
|
}
|
||||||
sharedEventLoopGroup?.shutdownGracefully()
|
sharedEventLoopGroup?.shutdownGracefully()
|
||||||
sharedEventLoopGroup?.terminationFuture()?.sync()
|
sharedEventLoopGroup?.terminationFuture()?.sync()
|
||||||
sharedEventLoopGroup = null
|
sharedEventLoopGroup = null
|
||||||
bridgeNameToBridgeMap.clear()
|
queueNamesToBridgesMap.clear()
|
||||||
artemis?.stop()
|
artemis?.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ class BridgeControlListener(val config: MutualSslConfiguration,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (outQueue in controlMessage.sendQueues) {
|
for (outQueue in controlMessage.sendQueues) {
|
||||||
bridgeManager.deployBridge(outQueue.queueName, outQueue.targets.first(), outQueue.legalNames.toSet())
|
bridgeManager.deployBridge(outQueue.queueName, outQueue.targets, outQueue.legalNames.toSet())
|
||||||
}
|
}
|
||||||
validInboundQueues.addAll(controlMessage.inboxQueues)
|
validInboundQueues.addAll(controlMessage.inboxQueues)
|
||||||
}
|
}
|
||||||
@ -110,14 +110,14 @@ class BridgeControlListener(val config: MutualSslConfiguration,
|
|||||||
log.error("Invalid queue names in control message $controlMessage")
|
log.error("Invalid queue names in control message $controlMessage")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bridgeManager.deployBridge(controlMessage.bridgeInfo.queueName, controlMessage.bridgeInfo.targets.first(), controlMessage.bridgeInfo.legalNames.toSet())
|
bridgeManager.deployBridge(controlMessage.bridgeInfo.queueName, controlMessage.bridgeInfo.targets, controlMessage.bridgeInfo.legalNames.toSet())
|
||||||
}
|
}
|
||||||
is BridgeControl.Delete -> {
|
is BridgeControl.Delete -> {
|
||||||
if (!controlMessage.bridgeInfo.queueName.startsWith(PEERS_PREFIX)) {
|
if (!controlMessage.bridgeInfo.queueName.startsWith(PEERS_PREFIX)) {
|
||||||
log.error("Invalid queue names in control message $controlMessage")
|
log.error("Invalid queue names in control message $controlMessage")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bridgeManager.destroyBridge(controlMessage.bridgeInfo.queueName, controlMessage.bridgeInfo.targets.first())
|
bridgeManager.destroyBridge(controlMessage.bridgeInfo.queueName, controlMessage.bridgeInfo.targets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package net.corda.nodeapi.internal.bridging
|
|||||||
|
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.node.NodeInfo
|
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -10,13 +9,9 @@ import net.corda.core.utilities.NetworkHostAndPort
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
interface BridgeManager : AutoCloseable {
|
interface BridgeManager : AutoCloseable {
|
||||||
fun deployBridge(queueName: String, target: NetworkHostAndPort, legalNames: Set<CordaX500Name>)
|
fun deployBridge(queueName: String, targets: List<NetworkHostAndPort>, legalNames: Set<CordaX500Name>)
|
||||||
|
|
||||||
fun destroyBridges(node: NodeInfo)
|
fun destroyBridge(queueName: String, targets: List<NetworkHostAndPort>)
|
||||||
|
|
||||||
fun destroyBridge(queueName: String, hostAndPort: NetworkHostAndPort)
|
|
||||||
|
|
||||||
fun bridgeExists(bridgeName: String): Boolean
|
|
||||||
|
|
||||||
fun start()
|
fun start()
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ class AMQPBridgeTest {
|
|||||||
if (sourceQueueName != null) {
|
if (sourceQueueName != null) {
|
||||||
// Local queue for outgoing messages
|
// Local queue for outgoing messages
|
||||||
artemis.session.createQueue(sourceQueueName, RoutingType.ANYCAST, sourceQueueName, true)
|
artemis.session.createQueue(sourceQueueName, RoutingType.ANYCAST, sourceQueueName, true)
|
||||||
bridgeManager.deployBridge(sourceQueueName, amqpAddress, setOf(BOB.name))
|
bridgeManager.deployBridge(sourceQueueName, listOf(amqpAddress), setOf(BOB.name))
|
||||||
}
|
}
|
||||||
return Triple(artemisServer, artemisClient, bridgeManager)
|
return Triple(artemisServer, artemisClient, bridgeManager)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package net.corda.services.messaging
|
||||||
|
|
||||||
|
import com.typesafe.config.ConfigValueFactory
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
|
import net.corda.core.contracts.Amount
|
||||||
|
import net.corda.core.contracts.Issued
|
||||||
|
import net.corda.core.contracts.withoutIssuer
|
||||||
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.finance.DOLLARS
|
||||||
|
import net.corda.finance.contracts.asset.Cash
|
||||||
|
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||||
|
import net.corda.node.services.Permissions.Companion.all
|
||||||
|
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||||
|
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||||
|
import net.corda.testing.core.expect
|
||||||
|
import net.corda.testing.core.expectEvents
|
||||||
|
import net.corda.testing.driver.DriverParameters
|
||||||
|
import net.corda.testing.driver.PortAllocation
|
||||||
|
import net.corda.testing.driver.driver
|
||||||
|
import net.corda.testing.node.User
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class AdditionP2PAddressModeTest {
|
||||||
|
private val portAllocation = PortAllocation.Incremental(27182)
|
||||||
|
@Test
|
||||||
|
fun `runs nodes with one configured to use additionalP2PAddresses`() {
|
||||||
|
val testUser = User("test", "test", setOf(all()))
|
||||||
|
driver(DriverParameters(startNodesInProcess = true, inMemoryDB = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) {
|
||||||
|
val mainAddress = portAllocation.nextHostAndPort().toString()
|
||||||
|
val altAddress = portAllocation.nextHostAndPort().toString()
|
||||||
|
val haConfig = mutableMapOf<String, Any?>()
|
||||||
|
haConfig["detectPublicIp"] = false
|
||||||
|
haConfig["p2pAddress"] = mainAddress //advertise this as primary
|
||||||
|
haConfig["messagingServerAddress"] = altAddress // but actually host on the alternate address
|
||||||
|
haConfig["messagingServerExternal"] = false
|
||||||
|
haConfig["additionalP2PAddresses"] = ConfigValueFactory.fromIterable(listOf(altAddress)) // advertise this secondary address
|
||||||
|
|
||||||
|
val (nodeA, nodeB) = listOf(
|
||||||
|
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = listOf(testUser), customOverrides = haConfig),
|
||||||
|
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = listOf(testUser), customOverrides = mapOf("p2pAddress" to portAllocation.nextHostAndPort().toString()))
|
||||||
|
).map { it.getOrThrow() }
|
||||||
|
val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map {
|
||||||
|
val client = CordaRPCClient(it.rpcAddress)
|
||||||
|
client.start(testUser.username, testUser.password).proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
val nodeBVaultUpdates = nodeBRpc.vaultTrack(Cash.State::class.java).updates
|
||||||
|
|
||||||
|
val issueRef = OpaqueBytes.of(0.toByte())
|
||||||
|
nodeARpc.startFlowDynamic(
|
||||||
|
CashIssueAndPaymentFlow::class.java,
|
||||||
|
DOLLARS(1234),
|
||||||
|
issueRef,
|
||||||
|
nodeB.nodeInfo.legalIdentities.get(0),
|
||||||
|
true,
|
||||||
|
defaultNotaryIdentity
|
||||||
|
).returnValue.getOrThrow()
|
||||||
|
nodeBVaultUpdates.expectEvents {
|
||||||
|
expect { update ->
|
||||||
|
println("Bob got vault update of $update")
|
||||||
|
val amount: Amount<Issued<Currency>> = update.produced.first().state.data.amount
|
||||||
|
assertEquals(1234.DOLLARS, amount.withoutIssuer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,11 +42,7 @@ import net.corda.node.services.Permissions
|
|||||||
import net.corda.node.services.api.FlowStarter
|
import net.corda.node.services.api.FlowStarter
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
import net.corda.node.services.api.StartedNodeServices
|
import net.corda.node.services.api.StartedNodeServices
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.*
|
||||||
import net.corda.node.services.config.SecurityConfiguration
|
|
||||||
import net.corda.node.services.config.shouldInitCrashShell
|
|
||||||
import net.corda.node.services.config.shouldStartLocalShell
|
|
||||||
import net.corda.node.services.config.JmxReporterType
|
|
||||||
import net.corda.node.services.messaging.*
|
import net.corda.node.services.messaging.*
|
||||||
import net.corda.node.services.rpc.ArtemisRpcBroker
|
import net.corda.node.services.rpc.ArtemisRpcBroker
|
||||||
import net.corda.node.utilities.AddressUtils
|
import net.corda.node.utilities.AddressUtils
|
||||||
@ -58,8 +54,8 @@ 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.config.User
|
import net.corda.nodeapi.internal.config.User
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.serialization.internal.*
|
|
||||||
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
|
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
|
||||||
|
import net.corda.serialization.internal.*
|
||||||
import org.h2.jdbc.JdbcSQLException
|
import org.h2.jdbc.JdbcSQLException
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@ -71,10 +67,10 @@ import java.net.InetAddress
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import javax.management.ObjectName
|
import javax.management.ObjectName
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class NodeWithInfo(val node: Node, val info: NodeInfo) {
|
class NodeWithInfo(val node: Node, val info: NodeInfo) {
|
||||||
val services: StartedNodeServices = object : StartedNodeServices, ServiceHubInternal by node.services, FlowStarter by node.flowStarter {}
|
val services: StartedNodeServices = object : StartedNodeServices, ServiceHubInternal by node.services, FlowStarter by node.flowStarter {}
|
||||||
@ -282,7 +278,7 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun myAddresses(): List<NetworkHostAndPort> = listOf(getAdvertisedAddress())
|
override fun myAddresses(): List<NetworkHostAndPort> = listOf(getAdvertisedAddress()) + configuration.additionalP2PAddresses
|
||||||
|
|
||||||
private fun getAdvertisedAddress(): NetworkHostAndPort {
|
private fun getAdvertisedAddress(): NetworkHostAndPort {
|
||||||
return with(configuration) {
|
return with(configuration) {
|
||||||
|
@ -50,6 +50,7 @@ interface NodeConfiguration {
|
|||||||
val notary: NotaryConfig?
|
val notary: NotaryConfig?
|
||||||
val additionalNodeInfoPollingFrequencyMsec: Long
|
val additionalNodeInfoPollingFrequencyMsec: Long
|
||||||
val p2pAddress: NetworkHostAndPort
|
val p2pAddress: NetworkHostAndPort
|
||||||
|
val additionalP2PAddresses: List<NetworkHostAndPort>
|
||||||
val rpcOptions: NodeRpcOptions
|
val rpcOptions: NodeRpcOptions
|
||||||
val messagingServerAddress: NetworkHostAndPort?
|
val messagingServerAddress: NetworkHostAndPort?
|
||||||
val messagingServerExternal: Boolean
|
val messagingServerExternal: Boolean
|
||||||
@ -198,6 +199,7 @@ data class NodeConfigurationImpl(
|
|||||||
override val verifierType: VerifierType,
|
override val verifierType: VerifierType,
|
||||||
override val flowTimeout: FlowTimeoutConfiguration,
|
override val flowTimeout: FlowTimeoutConfiguration,
|
||||||
override val p2pAddress: NetworkHostAndPort,
|
override val p2pAddress: NetworkHostAndPort,
|
||||||
|
override val additionalP2PAddresses: List<NetworkHostAndPort> = emptyList(),
|
||||||
private val rpcAddress: NetworkHostAndPort? = null,
|
private val rpcAddress: NetworkHostAndPort? = null,
|
||||||
private val rpcSettings: NodeRpcSettings,
|
private val rpcSettings: NodeRpcSettings,
|
||||||
override val messagingServerAddress: NetworkHostAndPort?,
|
override val messagingServerAddress: NetworkHostAndPort?,
|
||||||
|
@ -120,7 +120,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
private lateinit var advertisedAddress: NetworkHostAndPort
|
private lateinit var advertisedAddress: NetworkHostAndPort
|
||||||
private var maxMessageSize: Int = -1
|
private var maxMessageSize: Int = -1
|
||||||
|
|
||||||
override val myAddress: SingleMessageRecipient get() = NodeAddress(myIdentity, advertisedAddress)
|
override val myAddress: SingleMessageRecipient get() = NodeAddress(myIdentity)
|
||||||
override val ourSenderUUID = UUID.randomUUID().toString()
|
override val ourSenderUUID = UUID.randomUUID().toString()
|
||||||
|
|
||||||
private val state = ThreadBox(InnerState())
|
private val state = ThreadBox(InnerState())
|
||||||
@ -233,7 +233,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
fun gatherAddresses(node: NodeInfo): Sequence<BridgeEntry> {
|
fun gatherAddresses(node: NodeInfo): Sequence<BridgeEntry> {
|
||||||
return state.locked {
|
return state.locked {
|
||||||
node.legalIdentitiesAndCerts.map {
|
node.legalIdentitiesAndCerts.map {
|
||||||
val messagingAddress = NodeAddress(it.party.owningKey, node.addresses.first())
|
val messagingAddress = NodeAddress(it.party.owningKey)
|
||||||
BridgeEntry(messagingAddress.queueName, node.addresses, node.legalIdentities.map { it.name })
|
BridgeEntry(messagingAddress.queueName, node.addresses, node.legalIdentities.map { it.name })
|
||||||
}.filter { producerSession!!.queueQuery(SimpleString(it.queueName)).isExists }.asSequence()
|
}.filter { producerSession!!.queueQuery(SimpleString(it.queueName)).isExists }.asSequence()
|
||||||
}
|
}
|
||||||
@ -242,14 +242,14 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
fun deployBridges(node: NodeInfo) {
|
fun deployBridges(node: NodeInfo) {
|
||||||
gatherAddresses(node)
|
gatherAddresses(node)
|
||||||
.forEach {
|
.forEach {
|
||||||
sendBridgeControl(BridgeControl.Create(myIdentity.toStringShort(), it))
|
sendBridgeControl(BridgeControl.Create(config.myLegalName.toString(), it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun destroyBridges(node: NodeInfo) {
|
fun destroyBridges(node: NodeInfo) {
|
||||||
gatherAddresses(node)
|
gatherAddresses(node)
|
||||||
.forEach {
|
.forEach {
|
||||||
sendBridgeControl(BridgeControl.Delete(myIdentity.toStringShort(), it))
|
sendBridgeControl(BridgeControl.Delete(config.myLegalName.toString(), it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,7 +289,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
delayStartQueues += queue.toString()
|
delayStartQueues += queue.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val startupMessage = BridgeControl.NodeToBridgeSnapshot(myIdentity.toStringShort(), inboxes, requiredBridges)
|
val startupMessage = BridgeControl.NodeToBridgeSnapshot(config.myLegalName.toString(), inboxes, requiredBridges)
|
||||||
sendBridgeControl(startupMessage)
|
sendBridgeControl(startupMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,7 +495,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
val peers = networkMap.getNodesByOwningKeyIndex(keyHash)
|
val peers = networkMap.getNodesByOwningKeyIndex(keyHash)
|
||||||
for (node in peers) {
|
for (node in peers) {
|
||||||
val bridge = BridgeEntry(queueName, node.addresses, node.legalIdentities.map { it.name })
|
val bridge = BridgeEntry(queueName, node.addresses, node.legalIdentities.map { it.name })
|
||||||
val createBridgeMessage = BridgeControl.Create(myIdentity.toStringShort(), bridge)
|
val createBridgeMessage = BridgeControl.Create(config.myLegalName.toString(), bridge)
|
||||||
sendBridgeControl(createBridgeMessage)
|
sendBridgeControl(createBridgeMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -540,7 +540,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
|
|
||||||
override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients {
|
override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients {
|
||||||
return when (partyInfo) {
|
return when (partyInfo) {
|
||||||
is PartyInfo.SingleNode -> NodeAddress(partyInfo.party.owningKey, partyInfo.addresses.single())
|
is PartyInfo.SingleNode -> NodeAddress(partyInfo.party.owningKey)
|
||||||
is PartyInfo.DistributedNode -> ServiceAddress(partyInfo.party.owningKey)
|
is PartyInfo.DistributedNode -> ServiceAddress(partyInfo.party.owningKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ keyStorePassword = "cordacadevpass"
|
|||||||
trustStorePassword = "trustpass"
|
trustStorePassword = "trustpass"
|
||||||
crlCheckSoftFail = true
|
crlCheckSoftFail = true
|
||||||
lazyBridgeStart = true
|
lazyBridgeStart = true
|
||||||
|
additionalP2PAddresses = []
|
||||||
dataSourceProperties = {
|
dataSourceProperties = {
|
||||||
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
|
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
|
||||||
dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0;LOCK_TIMEOUT=10000"
|
dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0;LOCK_TIMEOUT=10000"
|
||||||
|
@ -197,7 +197,8 @@ class DriverDSLImpl(
|
|||||||
): CordaFuture<NodeHandle> {
|
): CordaFuture<NodeHandle> {
|
||||||
val p2pAddress = portAllocation.nextHostAndPort()
|
val p2pAddress = portAllocation.nextHostAndPort()
|
||||||
// TODO: Derive name from the full picked name, don't just wrap the common name
|
// TODO: Derive name from the full picked name, don't just wrap the common name
|
||||||
val name = providedName ?: CordaX500Name("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB")
|
val name = providedName
|
||||||
|
?: CordaX500Name("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB")
|
||||||
|
|
||||||
val registrationFuture = if (compatibilityZone?.rootCert != null) {
|
val registrationFuture = if (compatibilityZone?.rootCert != null) {
|
||||||
// We don't need the network map to be available to be able to register the node
|
// We don't need the network map to be available to be able to register the node
|
||||||
@ -604,7 +605,8 @@ class DriverDSLImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val p2pReadyFuture = addressMustBeBoundFuture(executorService, config.corda.p2pAddress, process)
|
val effectiveP2PAddress = config.corda.messagingServerAddress ?: config.corda.p2pAddress
|
||||||
|
val p2pReadyFuture = addressMustBeBoundFuture(executorService, effectiveP2PAddress, process)
|
||||||
return p2pReadyFuture.flatMap {
|
return p2pReadyFuture.flatMap {
|
||||||
val processDeathFuture = poll(executorService, "process death while waiting for RPC (${config.corda.myLegalName})") {
|
val processDeathFuture = poll(executorService, "process death while waiting for RPC (${config.corda.myLegalName})") {
|
||||||
if (process.isAlive) null else process
|
if (process.isAlive) null else process
|
||||||
@ -614,7 +616,7 @@ class DriverDSLImpl(
|
|||||||
val networkMapFuture = executorService.fork { visibilityHandle.listen(rpc) }.flatMap { it }
|
val networkMapFuture = executorService.fork { visibilityHandle.listen(rpc) }.flatMap { it }
|
||||||
firstOf(processDeathFuture, networkMapFuture) {
|
firstOf(processDeathFuture, networkMapFuture) {
|
||||||
if (it == processDeathFuture) {
|
if (it == processDeathFuture) {
|
||||||
throw ListenProcessDeathException(config.corda.p2pAddress, process)
|
throw ListenProcessDeathException(effectiveP2PAddress, process)
|
||||||
}
|
}
|
||||||
// Will interrupt polling for process death as this is no longer relevant since the process been
|
// Will interrupt polling for process death as this is no longer relevant since the process been
|
||||||
// successfully started and reflected itself in the NetworkMap.
|
// successfully started and reflected itself in the NetworkMap.
|
||||||
@ -689,6 +691,7 @@ class DriverDSLImpl(
|
|||||||
executorService: ScheduledExecutorService,
|
executorService: ScheduledExecutorService,
|
||||||
config: NodeConfig
|
config: NodeConfig
|
||||||
): CordaFuture<Pair<NodeWithInfo, Thread>> {
|
): CordaFuture<Pair<NodeWithInfo, Thread>> {
|
||||||
|
val effectiveP2PAddress = config.corda.messagingServerAddress ?: config.corda.p2pAddress
|
||||||
return executorService.fork {
|
return executorService.fork {
|
||||||
log.info("Starting in-process Node ${config.corda.myLegalName.organisation}")
|
log.info("Starting in-process Node ${config.corda.myLegalName.organisation}")
|
||||||
if (!(ManagementFactory.getRuntimeMXBean().inputArguments.any { it.contains("quasar") })) {
|
if (!(ManagementFactory.getRuntimeMXBean().inputArguments.any { it.contains("quasar") })) {
|
||||||
@ -705,7 +708,7 @@ class DriverDSLImpl(
|
|||||||
}
|
}
|
||||||
nodeWithInfo to nodeThread
|
nodeWithInfo to nodeThread
|
||||||
}.flatMap { nodeAndThread ->
|
}.flatMap { nodeAndThread ->
|
||||||
addressMustBeBoundFuture(executorService, config.corda.p2pAddress).map { nodeAndThread }
|
addressMustBeBoundFuture(executorService, effectiveP2PAddress).map { nodeAndThread }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user