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:
Matthew Nesbit 2018-09-05 17:46:46 +01:00 committed by GitHub
parent e3ece00bea
commit 304dba704e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 133 additions and 75 deletions

View File

@ -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
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.
: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
initial checkpoint. Currently only used for notarisation requests: if a notary replica dies while processing a notarisation request,

View File

@ -6,7 +6,6 @@ import net.corda.core.messaging.MessageRecipientGroup
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.SingleMessageRecipient
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.SimpleString
import java.security.PublicKey
@ -77,9 +76,7 @@ class ArtemisMessagingComponent {
val queueName: String
}
interface ArtemisPeerAddress : ArtemisAddress, SingleMessageRecipient {
val hostAndPort: NetworkHostAndPort
}
interface ArtemisPeerAddress : ArtemisAddress, SingleMessageRecipient
/**
* 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.
*
* @param queueName The name of the queue this address is associated with.
* @param hostAndPort The address of the node.
*/
@CordaSerializable
data class NodeAddress(override val queueName: String, override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress {
constructor(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort) :
this("$PEERS_PREFIX${peerIdentity.toStringShort()}", hostAndPort)
data class NodeAddress(override val queueName: String) : ArtemisPeerAddress {
constructor(peerIdentity: PublicKey) :
this("$PEERS_PREFIX${peerIdentity.toStringShort()}")
}
/**

View File

@ -4,16 +4,13 @@ import io.netty.channel.EventLoopGroup
import io.netty.channel.nio.NioEventLoopGroup
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.VisibleForTesting
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
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.P2PMessagingHeaders
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress
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.MutualSslConfiguration
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 {
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,
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,
* however Artemis and the remote Corda instanced will deduplicate these messages.
*/
private class AMQPBridge(private val queueName: String,
private val target: NetworkHostAndPort,
private class AMQPBridge(val queueName: String,
val targets: List<NetworkHostAndPort>,
private val legalNames: Set<CordaX500Name>,
private val amqpConfig: AMQPConfiguration,
sharedEventGroup: EventLoopGroup,
private val artemis: ArtemisSessionProvider) {
companion object {
fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort"
private val log = contextLogger()
}
@ -81,8 +77,7 @@ class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, pri
val oldMDC = MDC.getCopyOfContextMap()
try {
MDC.put("queueName", queueName)
MDC.put("target", target.toString())
MDC.put("bridgeName", bridgeName)
MDC.put("targets", targets.joinToString(separator = ";") { it.toString() })
MDC.put("legalNames", legalNames.joinToString(separator = ";") { it.toString() })
MDC.put("maxMessageSize", amqpConfig.maxMessageSize.toString())
block()
@ -101,8 +96,7 @@ class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, pri
private fun logWarnWithMDC(msg: String) = withMDC { log.warn(msg) }
val amqpClient = AMQPClient(listOf(target), legalNames, amqpConfig, sharedThreadPool = sharedEventGroup)
val bridgeName: String get() = getBridgeName(queueName, target)
val amqpClient = AMQPClient(targets, legalNames, amqpConfig, sharedThreadPool = sharedEventGroup)
private val lock = ReentrantLock() // lock to serialise session level access
private var session: ClientSession? = 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> {
return node.legalIdentitiesAndCerts.map { ArtemisMessagingComponent.NodeAddress(it.party.owningKey, node.addresses[0]) }
}
override fun deployBridge(queueName: String, target: NetworkHostAndPort, legalNames: Set<CordaX500Name>) {
if (bridgeExists(getBridgeName(queueName, target))) {
return
}
val newBridge = AMQPBridge(queueName, target, legalNames, amqpConfig, sharedEventLoopGroup!!, artemis!!)
lock.withLock {
bridgeNameToBridgeMap[newBridge.bridgeName] = newBridge
override fun deployBridge(queueName: String, targets: List<NetworkHostAndPort>, legalNames: Set<CordaX500Name>) {
val newBridge = lock.withLock {
val bridges = queueNamesToBridgesMap.getOrPut(queueName) { mutableListOf() }
for (target in targets) {
if (bridges.any { it.targets.contains(target) }) {
return
}
}
val newBridge = AMQPBridge(queueName, targets, legalNames, amqpConfig, sharedEventLoopGroup!!, artemis!!)
bridges += newBridge
newBridge
}
newBridge.start()
}
override fun destroyBridges(node: NodeInfo) {
override fun destroyBridge(queueName: String, targets: List<NetworkHostAndPort>) {
lock.withLock {
gatherAddresses(node).forEach {
val bridge = bridgeNameToBridgeMap.remove(getBridgeName(it.queueName, it.hostAndPort))
bridge?.stop()
val bridges = queueNamesToBridgesMap[queueName] ?: mutableListOf()
for (target in targets) {
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() {
sharedEventLoopGroup = NioEventLoopGroup(NUM_BRIDGE_THREADS)
val artemis = artemisMessageClientFactory()
@ -238,13 +230,13 @@ class AMQPBridgeManager(config: MutualSslConfiguration, maxMessageSize: Int, pri
override fun close() {
lock.withLock {
for (bridge in bridgeNameToBridgeMap.values) {
for (bridge in queueNamesToBridgesMap.values.flatten()) {
bridge.stop()
}
sharedEventLoopGroup?.shutdownGracefully()
sharedEventLoopGroup?.terminationFuture()?.sync()
sharedEventLoopGroup = null
bridgeNameToBridgeMap.clear()
queueNamesToBridgesMap.clear()
artemis?.stop()
}
}

View File

@ -98,7 +98,7 @@ class BridgeControlListener(val config: MutualSslConfiguration,
return
}
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)
}
@ -110,14 +110,14 @@ class BridgeControlListener(val config: MutualSslConfiguration,
log.error("Invalid queue names in control message $controlMessage")
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 -> {
if (!controlMessage.bridgeInfo.queueName.startsWith(PEERS_PREFIX)) {
log.error("Invalid queue names in control message $controlMessage")
return
}
bridgeManager.destroyBridge(controlMessage.bridgeInfo.queueName, controlMessage.bridgeInfo.targets.first())
bridgeManager.destroyBridge(controlMessage.bridgeInfo.queueName, controlMessage.bridgeInfo.targets)
}
}
}

View File

@ -2,7 +2,6 @@ package net.corda.nodeapi.internal.bridging
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.VisibleForTesting
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.NetworkHostAndPort
/**
@ -10,13 +9,9 @@ import net.corda.core.utilities.NetworkHostAndPort
*/
@VisibleForTesting
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, hostAndPort: NetworkHostAndPort)
fun bridgeExists(bridgeName: String): Boolean
fun destroyBridge(queueName: String, targets: List<NetworkHostAndPort>)
fun start()

View File

@ -193,7 +193,7 @@ class AMQPBridgeTest {
if (sourceQueueName != null) {
// Local queue for outgoing messages
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)
}

View File

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

View File

@ -42,11 +42,7 @@ import net.corda.node.services.Permissions
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.config.NodeConfiguration
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.config.*
import net.corda.node.services.messaging.*
import net.corda.node.services.rpc.ArtemisRpcBroker
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.config.User
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.serialization.internal.*
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.serialization.internal.*
import org.h2.jdbc.JdbcSQLException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@ -71,10 +67,10 @@ import java.net.InetAddress
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Clock
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import javax.management.ObjectName
import kotlin.system.exitProcess
import java.util.concurrent.TimeUnit
class NodeWithInfo(val node: Node, val info: NodeInfo) {
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 {
return with(configuration) {

View File

@ -50,6 +50,7 @@ interface NodeConfiguration {
val notary: NotaryConfig?
val additionalNodeInfoPollingFrequencyMsec: Long
val p2pAddress: NetworkHostAndPort
val additionalP2PAddresses: List<NetworkHostAndPort>
val rpcOptions: NodeRpcOptions
val messagingServerAddress: NetworkHostAndPort?
val messagingServerExternal: Boolean
@ -198,6 +199,7 @@ data class NodeConfigurationImpl(
override val verifierType: VerifierType,
override val flowTimeout: FlowTimeoutConfiguration,
override val p2pAddress: NetworkHostAndPort,
override val additionalP2PAddresses: List<NetworkHostAndPort> = emptyList(),
private val rpcAddress: NetworkHostAndPort? = null,
private val rpcSettings: NodeRpcSettings,
override val messagingServerAddress: NetworkHostAndPort?,

View File

@ -120,7 +120,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
private lateinit var advertisedAddress: NetworkHostAndPort
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()
private val state = ThreadBox(InnerState())
@ -233,7 +233,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
fun gatherAddresses(node: NodeInfo): Sequence<BridgeEntry> {
return state.locked {
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 })
}.filter { producerSession!!.queueQuery(SimpleString(it.queueName)).isExists }.asSequence()
}
@ -242,14 +242,14 @@ class P2PMessagingClient(val config: NodeConfiguration,
fun deployBridges(node: NodeInfo) {
gatherAddresses(node)
.forEach {
sendBridgeControl(BridgeControl.Create(myIdentity.toStringShort(), it))
sendBridgeControl(BridgeControl.Create(config.myLegalName.toString(), it))
}
}
fun destroyBridges(node: NodeInfo) {
gatherAddresses(node)
.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()
}
}
val startupMessage = BridgeControl.NodeToBridgeSnapshot(myIdentity.toStringShort(), inboxes, requiredBridges)
val startupMessage = BridgeControl.NodeToBridgeSnapshot(config.myLegalName.toString(), inboxes, requiredBridges)
sendBridgeControl(startupMessage)
}
@ -495,7 +495,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
val peers = networkMap.getNodesByOwningKeyIndex(keyHash)
for (node in peers) {
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)
}
}
@ -540,7 +540,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients {
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)
}
}

View File

@ -3,6 +3,7 @@ keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"
crlCheckSoftFail = true
lazyBridgeStart = true
additionalP2PAddresses = []
dataSourceProperties = {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
dataSource.url = "jdbc:h2:file:"${baseDirectory}"/persistence;DB_CLOSE_ON_EXIT=FALSE;WRITE_DELAY=0;LOCK_TIMEOUT=10000"

View File

@ -197,7 +197,8 @@ class DriverDSLImpl(
): CordaFuture<NodeHandle> {
val p2pAddress = portAllocation.nextHostAndPort()
// 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) {
// 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 {
val processDeathFuture = poll(executorService, "process death while waiting for RPC (${config.corda.myLegalName})") {
if (process.isAlive) null else process
@ -614,7 +616,7 @@ class DriverDSLImpl(
val networkMapFuture = executorService.fork { visibilityHandle.listen(rpc) }.flatMap { it }
firstOf(processDeathFuture, networkMapFuture) {
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
// successfully started and reflected itself in the NetworkMap.
@ -689,6 +691,7 @@ class DriverDSLImpl(
executorService: ScheduledExecutorService,
config: NodeConfig
): CordaFuture<Pair<NodeWithInfo, Thread>> {
val effectiveP2PAddress = config.corda.messagingServerAddress ?: config.corda.p2pAddress
return executorService.fork {
log.info("Starting in-process Node ${config.corda.myLegalName.organisation}")
if (!(ManagementFactory.getRuntimeMXBean().inputArguments.any { it.contains("quasar") })) {
@ -705,7 +708,7 @@ class DriverDSLImpl(
}
nodeWithInfo to nodeThread
}.flatMap { nodeAndThread ->
addressMustBeBoundFuture(executorService, config.corda.p2pAddress).map { nodeAndThread }
addressMustBeBoundFuture(executorService, effectiveP2PAddress).map { nodeAndThread }
}
}