From 502088d79886f3f71bf4c2bf9407efb0659b65c2 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Tue, 15 May 2018 17:36:17 +0100 Subject: [PATCH 1/7] Update generating-a-node.rst --- docs/source/generating-a-node.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/generating-a-node.rst b/docs/source/generating-a-node.rst index 9e02b826b5..f8f0a3f437 100644 --- a/docs/source/generating-a-node.rst +++ b/docs/source/generating-a-node.rst @@ -139,7 +139,7 @@ The Dockerform task The ``Dockerform`` is a sister task of ``Cordform``. It has nearly the same syntax and produces very similar results - enhanced by an extra file to enable easy spin up of nodes using ``docker-compose``. -Below you can find the example task from the ``IRS Demo`` +Below you can find the example task from the ``IRS Demo `` included in the samples directory of main Corda GitHub repository: .. sourcecode:: groovy From 0c38a63486eed9b67ff1dcf0e7f1cd589fa8e2da Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Tue, 15 May 2018 17:37:09 +0100 Subject: [PATCH 2/7] Update generating-a-node.rst --- docs/source/generating-a-node.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/source/generating-a-node.rst b/docs/source/generating-a-node.rst index f8f0a3f437..ed7091beb3 100644 --- a/docs/source/generating-a-node.rst +++ b/docs/source/generating-a-node.rst @@ -139,8 +139,7 @@ The Dockerform task The ``Dockerform`` is a sister task of ``Cordform``. It has nearly the same syntax and produces very similar results - enhanced by an extra file to enable easy spin up of nodes using ``docker-compose``. -Below you can find the example task from the ``IRS Demo `` -included in the samples directory of main Corda GitHub repository: +Below you can find the example task from the `IRS Demo `_ included in the samples directory of main Corda GitHub repository: .. sourcecode:: groovy From b06738b371a3f68ba9ce3053febf7f0167a69605 Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Wed, 16 May 2018 11:45:59 +0100 Subject: [PATCH 3/7] CORDA-1093 Deleting node info from directory (#3115) * CORDA-1093 Deleting node info from directory Deleting NodeInfo from additional-node-infos directory should remove it from cache. --- .../services/network/NodeInfoWatcherTest.kt | 7 +- .../services/network/NetworkMapUpdater.kt | 12 +++- .../node/services/network/NodeInfoWatcher.kt | 42 +++++++----- .../network/PersistentNetworkMapCache.kt | 4 +- .../services/network/NetworkMapUpdaterTest.kt | 68 +++++++++++++++++-- 5 files changed, 103 insertions(+), 30 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt index 9528078c41..1b7a1d8ab9 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt @@ -6,7 +6,6 @@ import net.corda.cordform.CordformNode import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.size -import net.corda.core.node.NodeInfo import net.corda.core.node.services.KeyManagementService import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.network.NodeInfoFilesCopier @@ -37,7 +36,7 @@ class NodeInfoWatcherTest { val tempFolder = TemporaryFolder() private val scheduler = TestScheduler() - private val testSubscriber = TestSubscriber() + private val testSubscriber = TestSubscriber() private lateinit var nodeInfoAndSigned: NodeInfoAndSigned private lateinit var nodeInfoPath: Path @@ -101,7 +100,7 @@ class NodeInfoWatcherTest { try { val readNodes = testSubscriber.onNextEvents.distinct() assertEquals(1, readNodes.size) - assertEquals(nodeInfoAndSigned.nodeInfo, readNodes.first()) + assertEquals(nodeInfoAndSigned.nodeInfo, (readNodes.first() as? NodeInfoUpdate.Add)?.nodeInfo) } finally { subscription.unsubscribe() } @@ -126,7 +125,7 @@ class NodeInfoWatcherTest { testSubscriber.awaitValueCount(1, 5, TimeUnit.SECONDS) // The same folder can be reported more than once, so take unique values. val readNodes = testSubscriber.onNextEvents.distinct() - assertEquals(nodeInfoAndSigned.nodeInfo, readNodes.first()) + assertEquals(nodeInfoAndSigned.nodeInfo, (readNodes.first() as? NodeInfoUpdate.Add)?.nodeInfo) } finally { subscription.unsubscribe() } diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt index 648488537f..c4b3980959 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapUpdater.kt @@ -63,7 +63,17 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal, fun subscribeToNetworkMap() { require(fileWatcherSubscription == null) { "Should not call this method twice." } // Subscribe to file based networkMap - fileWatcherSubscription = fileWatcher.nodeInfoUpdates().subscribe(networkMapCache::addNode) + fileWatcherSubscription = fileWatcher.nodeInfoUpdates().subscribe { + when (it) { + is NodeInfoUpdate.Add -> { + networkMapCache.addNode(it.nodeInfo) + } + is NodeInfoUpdate.Remove -> { + val nodeInfo = networkMapCache.getNodeByHash(it.hash) + nodeInfo?.let { networkMapCache.removeNode(it) } + } + } + } if (networkMapClient == null) return diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt index bbcbb37e89..2e30a1b711 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt @@ -26,13 +26,18 @@ import java.time.Duration import java.util.concurrent.TimeUnit import kotlin.streams.toList +sealed class NodeInfoUpdate { + data class Add(val nodeInfo: NodeInfo) : NodeInfoUpdate() + data class Remove(val hash: SecureHash) : NodeInfoUpdate() +} + /** * Class containing the logic to * - Serialize and de-serialize a [NodeInfo] to disk and reading it back. * - Poll a directory for new serialized [NodeInfo] * * @param nodePath the base path of a node. - * @param pollInterval how often to poll the filesystem in milliseconds. Must be longer then 5 seconds. + * @param pollInterval how often to poll the filesystem in milliseconds. Must be longer than 5 seconds. * @param scheduler a [Scheduler] for the rx [Observable] returned by [nodeInfoUpdates], this is mainly useful for * testing. It defaults to the io scheduler which is the appropriate value for production uses. */ @@ -58,10 +63,10 @@ class NodeInfoWatcher(private val nodePath: Path, } } + internal data class NodeInfoFromFile(val nodeInfohash: SecureHash, val lastModified: FileTime) private val nodeInfosDir = nodePath / CordformNode.NODE_INFO_DIRECTORY - private val nodeInfoFiles = HashMap() - private val _processedNodeInfoHashes = HashSet() - val processedNodeInfoHashes: Set get() = _processedNodeInfoHashes + private val nodeInfoFilesMap = HashMap() + val processedNodeInfoHashes: Set get() = nodeInfoFilesMap.values.map { it.nodeInfohash }.toSet() init { require(pollInterval >= 5.seconds) { "Poll interval must be 5 seconds or longer." } @@ -75,33 +80,31 @@ class NodeInfoWatcher(private val nodePath: Path, * We simply list the directory content every 5 seconds, the Java implementation of WatchService has been proven to * be unreliable on MacOs and given the fairly simple use case we have, this simple implementation should do. * - * @return an [Observable] returning [NodeInfo]s, at most one [NodeInfo] is returned for each processed file. + * @return an [Observable] returning [NodeInfoUpdate]s, at most one [NodeInfo] is returned for each processed file. */ - fun nodeInfoUpdates(): Observable { + fun nodeInfoUpdates(): Observable { return Observable.interval(pollInterval.toMillis(), TimeUnit.MILLISECONDS, scheduler) .flatMapIterable { loadFromDirectory() } } - // TODO This method doesn't belong in this class - fun saveToFile(nodeInfoAndSigned: NodeInfoAndSigned) { - return Companion.saveToFile(nodePath, nodeInfoAndSigned) - } - - private fun loadFromDirectory(): List { + private fun loadFromDirectory(): List { + val processedPaths = HashSet() val result = nodeInfosDir.list { paths -> paths .filter { it.isRegularFile() } .filter { file -> val lastModifiedTime = file.lastModifiedTime() - val previousLastModifiedTime = nodeInfoFiles[file] + val previousLastModifiedTime = nodeInfoFilesMap[file]?.lastModified val newOrChangedFile = previousLastModifiedTime == null || lastModifiedTime > previousLastModifiedTime - nodeInfoFiles[file] = lastModifiedTime + processedPaths.add(file) newOrChangedFile } .mapNotNull { file -> logger.debug { "Reading SignedNodeInfo from $file" } try { - NodeInfoAndSigned(file.readObject()) + val nodeInfoSigned = NodeInfoAndSigned(file.readObject()) + nodeInfoFilesMap[file] = NodeInfoFromFile(nodeInfoSigned.signed.raw.hash, file.lastModifiedTime()) + nodeInfoSigned } catch (e: Exception) { logger.warn("Unable to read SignedNodeInfo from $file", e) null @@ -109,10 +112,13 @@ class NodeInfoWatcher(private val nodePath: Path, } .toList() } - + val removedFiles = nodeInfoFilesMap.keys - processedPaths + val removedHashes = removedFiles.map { file -> + NodeInfoUpdate.Remove(nodeInfoFilesMap.remove(file)!!.nodeInfohash) + } logger.debug { "Read ${result.size} NodeInfo files from $nodeInfosDir" } - _processedNodeInfoHashes += result.map { it.signed.raw.hash } - return result.map { it.nodeInfo } + logger.debug { "Number of removed NodeInfo files ${removedHashes.size}" } + return result.map { NodeInfoUpdate.Add(it.nodeInfo) } + removedHashes } } diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index 6ca2a10009..5c998b71be 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -251,8 +251,8 @@ open class PersistentNetworkMapCache( } private fun removeInfoDB(session: Session, nodeInfo: NodeInfo) { - val info = findByIdentityKey(session, nodeInfo.legalIdentitiesAndCerts.first().owningKey).single() - session.remove(info) + val info = findByIdentityKey(session, nodeInfo.legalIdentitiesAndCerts.first().owningKey).singleOrNull() + info?.let { session.remove(it) } // invalidate cache last - this way, we might serve up the wrong info for a short time, but it will get refreshed // on the next load invalidateCaches(nodeInfo) diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt index 0b4a73c5fb..381328a484 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapUpdaterTest.kt @@ -5,6 +5,7 @@ import com.google.common.jimfs.Jimfs import com.nhaarman.mockito_kotlin.* import net.corda.cordform.CordformNode.NODE_INFO_DIRECTORY import net.corda.core.crypto.Crypto +import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.* @@ -19,6 +20,7 @@ import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.* import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.DEV_ROOT_CA +import net.corda.testing.internal.TestNodeInfoBuilder import net.corda.testing.internal.createNodeInfoAndSigned import net.corda.testing.node.internal.network.NetworkMapServer import org.assertj.core.api.Assertions @@ -46,6 +48,7 @@ class NetworkMapUpdaterTest { private val privateNetUUID = UUID.randomUUID() private val fs = Jimfs.newFileSystem(unix()) private val baseDir = fs.getPath("/node") + private val nodeInfoDir = baseDir / NODE_INFO_DIRECTORY private val scheduler = TestScheduler() private val fileWatcher = NodeInfoWatcher(baseDir, scheduler) private val networkMapCache = createMockNetworkMapCache() @@ -90,7 +93,7 @@ class NetworkMapUpdaterTest { verify(networkMapCache, times(1)).addNode(nodeInfo1) verify(networkMapCache, times(1)).addNode(nodeInfo2) - NodeInfoWatcher.saveToFile(baseDir / NODE_INFO_DIRECTORY, fileNodeInfoAndSigned) + NodeInfoWatcher.saveToFile(nodeInfoDir, fileNodeInfoAndSigned) networkMapClient.publish(signedNodeInfo3) networkMapClient.publish(signedNodeInfo4) scheduler.advanceTimeBy(10, TimeUnit.SECONDS) @@ -112,7 +115,7 @@ class NetworkMapUpdaterTest { val fileNodeInfoAndSigned = createNodeInfoAndSigned("Info from file") // Add all nodes. - NodeInfoWatcher.saveToFile(baseDir / NODE_INFO_DIRECTORY, fileNodeInfoAndSigned) + NodeInfoWatcher.saveToFile(nodeInfoDir, fileNodeInfoAndSigned) networkMapClient.publish(signedNodeInfo1) networkMapClient.publish(signedNodeInfo2) networkMapClient.publish(signedNodeInfo3) @@ -152,7 +155,7 @@ class NetworkMapUpdaterTest { updater.subscribeToNetworkMap() - NodeInfoWatcher.saveToFile(baseDir / NODE_INFO_DIRECTORY, fileNodeInfoAndSigned) + NodeInfoWatcher.saveToFile(nodeInfoDir, fileNodeInfoAndSigned) scheduler.advanceTimeBy(10, TimeUnit.SECONDS) verify(networkMapCache, times(1)).addNode(any()) @@ -215,17 +218,72 @@ class NetworkMapUpdaterTest { assertEquals(aliceInfo, networkMapClient.getNodeInfo(aliceHash)) } + @Test + fun `remove node from filesystem deletes it from network map cache`() { + val fileNodeInfoAndSigned1 = createNodeInfoAndSigned("Info from file 1") + val fileNodeInfoAndSigned2 = createNodeInfoAndSigned("Info from file 2") + updater.subscribeToNetworkMap() + + NodeInfoWatcher.saveToFile(nodeInfoDir, fileNodeInfoAndSigned1) + NodeInfoWatcher.saveToFile(nodeInfoDir, fileNodeInfoAndSigned2) + scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + verify(networkMapCache, times(2)).addNode(any()) + verify(networkMapCache, times(1)).addNode(fileNodeInfoAndSigned1.nodeInfo) + verify(networkMapCache, times(1)).addNode(fileNodeInfoAndSigned2.nodeInfo) + assertThat(networkMapCache.allNodeHashes).containsExactlyInAnyOrder(fileNodeInfoAndSigned1.signed.raw.hash, fileNodeInfoAndSigned2.signed.raw.hash) + // Remove one of the nodes + val fileName1 = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}${fileNodeInfoAndSigned1.nodeInfo.legalIdentities[0].name.serialize().hash}" + (nodeInfoDir / fileName1).delete() + scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + verify(networkMapCache, times(1)).removeNode(any()) + verify(networkMapCache, times(1)).removeNode(fileNodeInfoAndSigned1.nodeInfo) + assertThat(networkMapCache.allNodeHashes).containsOnly(fileNodeInfoAndSigned2.signed.raw.hash) + } + + @Test + fun `remove node info file, but node in network map server`() { + val nodeInfoBuilder = TestNodeInfoBuilder() + val (_, key) = nodeInfoBuilder.addLegalIdentity(CordaX500Name("Info", "London", "GB")) + val (serverNodeInfo, serverSignedNodeInfo) = nodeInfoBuilder.buildWithSigned(1, 1) + // Construct node for exactly same identity, but different serial. This one will go to additional-node-infos only. + val localNodeInfo = serverNodeInfo.copy(serial = 17) + val localSignedNodeInfo = NodeInfoAndSigned(localNodeInfo) { _, serialised -> + key.sign(serialised.bytes) + } + // The one with higher serial goes to additional-node-infos. + NodeInfoWatcher.saveToFile(nodeInfoDir, localSignedNodeInfo) + // Publish to network map the one with lower serial. + networkMapClient.publish(serverSignedNodeInfo) + updater.subscribeToNetworkMap() + scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + verify(networkMapCache, times(1)).addNode(localNodeInfo) + Thread.sleep(2L * cacheExpiryMs) + // Node from file has higher serial than the one from NetworkMapServer + assertThat(networkMapCache.allNodeHashes).containsOnly(localSignedNodeInfo.signed.raw.hash) + val fileName = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}${localNodeInfo.legalIdentities[0].name.serialize().hash}" + (nodeInfoDir / fileName).delete() + scheduler.advanceTimeBy(10, TimeUnit.SECONDS) + verify(networkMapCache, times(1)).removeNode(any()) + verify(networkMapCache).removeNode(localNodeInfo) + Thread.sleep(2L * cacheExpiryMs) + // Instead of node from file we should have now the one from NetworkMapServer + assertThat(networkMapCache.allNodeHashes).containsOnly(serverSignedNodeInfo.raw.hash) + } + private fun createMockNetworkMapCache(): NetworkMapCacheInternal { return mock { val data = ConcurrentHashMap() on { addNode(any()) }.then { val nodeInfo = it.arguments[0] as NodeInfo - data.put(nodeInfo.legalIdentities[0], nodeInfo) + val party = nodeInfo.legalIdentities[0] + data.compute(party) { _, current -> + if (current == null || current.serial < nodeInfo.serial) nodeInfo else current + } } on { removeNode(any()) }.then { data.remove((it.arguments[0] as NodeInfo).legalIdentities[0]) } on { getNodeByLegalIdentity(any()) }.then { data[it.arguments[0]] } on { allNodeHashes }.then { data.values.map { it.serialize().hash } } - on { getNodeByHash(any()) }.then { mock -> data.values.single { it.serialize().hash == mock.arguments[0] } } + on { getNodeByHash(any()) }.then { mock -> data.values.singleOrNull { it.serialize().hash == mock.arguments[0] } } } } From 3392c15c0b6e6363138240e62e257f432744c54b Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Wed, 16 May 2018 12:20:33 +0100 Subject: [PATCH 4/7] CORDA-1491: Use MessagingExecutor for deduplication ACK codepath --- .../net/corda/node/services/messaging/P2PMessagingClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt index 3acae7898b..190cefbe60 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/P2PMessagingClient.kt @@ -420,7 +420,7 @@ class P2PMessagingClient(val config: NodeConfiguration, deliver(cordaMessage, artemisMessage) } else { log.trace { "Discard duplicate message ${cordaMessage.uniqueMessageId} for ${cordaMessage.topic}" } - artemisMessage.individualAcknowledge() + messagingExecutor!!.acknowledge(artemisMessage) } } } From 65b782c206450dcc564114335512b73403daa9a9 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Wed, 16 May 2018 13:56:41 +0100 Subject: [PATCH 5/7] ENT-933 Add spectator and participant profiles of rigorousMock (#3157) --- build.gradle | 2 +- .../contracts/asset/CashTestsJava.java | 2 +- .../services/vault/VaultQueryJavaTests.java | 2 +- .../events/NodeSchedulerServiceTest.kt | 22 +-- samples/irs-demo/build.gradle | 3 +- samples/network-visualiser/build.gradle | 2 +- testing/test-utils/build.gradle | 3 +- .../testing/internal/InternalTestUtils.kt | 29 ---- .../corda/testing/internal/RigorousMock.kt | 119 +++++++++++++++++ .../org.mockito.plugins.MockMaker | 1 + .../testing/internal/RigorousMockTest.kt | 126 ++++++++++++++++++ tools/demobench/build.gradle | 1 - 12 files changed, 260 insertions(+), 52 deletions(-) create mode 100644 testing/test-utils/src/main/kotlin/net/corda/testing/internal/RigorousMock.kt create mode 100644 testing/test-utils/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 testing/test-utils/src/test/kotlin/net/corda/testing/internal/RigorousMockTest.kt diff --git a/build.gradle b/build.gradle index d823ed5246..b2136ccf87 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ buildscript { ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion") ext.fileupload_version = '1.3.3' ext.junit_version = '4.12' - ext.mockito_version = '2.10.0' + ext.mockito_version = '2.18.3' ext.jopt_simple_version = '5.0.2' ext.jansi_version = '1.14' ext.hibernate_version = '5.2.6.Final' diff --git a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java index 3e28b1aee1..f7f29b445d 100644 --- a/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java +++ b/finance/src/test/java/net/corda/finance/contracts/asset/CashTestsJava.java @@ -16,7 +16,7 @@ import static java.util.Collections.emptyList; import static net.corda.finance.Currencies.DOLLARS; import static net.corda.finance.Currencies.issuedBy; import static net.corda.testing.node.NodeTestUtils.transaction; -import static net.corda.testing.internal.InternalTestUtilsKt.rigorousMock; +import static net.corda.testing.internal.RigorousMockKt.rigorousMock; import static net.corda.testing.core.TestConstants.DUMMY_NOTARY_NAME; import static org.mockito.Mockito.doReturn; diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index 7500afbddb..3c9b727313 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -49,7 +49,7 @@ import static net.corda.core.node.services.vault.Builder.sum; import static net.corda.core.node.services.vault.QueryCriteriaUtils.*; import static net.corda.core.utilities.ByteArrays.toHexString; import static net.corda.testing.core.TestConstants.*; -import static net.corda.testing.internal.InternalTestUtilsKt.rigorousMock; +import static net.corda.testing.internal.RigorousMockKt.rigorousMock; import static net.corda.testing.node.MockServices.makeTestDatabaseAndMockServices; import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; import static org.assertj.core.api.Assertions.assertThat; diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index d5787acec9..ec6eb06604 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -8,7 +8,6 @@ import net.corda.core.flows.FlowLogicRef import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.uncheckedCast import net.corda.core.node.ServicesForResolution import net.corda.core.utilities.days import net.corda.node.internal.configureDatabase @@ -20,6 +19,7 @@ import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.testing.internal.doLookup import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.spectator import net.corda.testing.node.MockServices import net.corda.testing.node.TestClock import org.junit.Ignore @@ -44,16 +44,9 @@ open class NodeSchedulerServiceTestBase { protected val testClock = TestClock(rigorousMock().also { doReturn(mark).whenever(it).instant() }) - private val database = rigorousMock().also { - doAnswer { - val block: DatabaseTransaction.() -> Any? = uncheckedCast(it.arguments[0]) - rigorousMock().block() - }.whenever(it).transaction(any()) - } - protected val flowStarter = rigorousMock().also { doAnswer { - val dedupe = it.arguments[2] as DeduplicationHandler + val dedupe: DeduplicationHandler = it.getArgument(2) dedupe.insideDatabaseTransaction() dedupe.afterDatabaseTransaction() openFuture>() @@ -74,11 +67,8 @@ open class NodeSchedulerServiceTestBase { protected val servicesForResolution = rigorousMock().also { doLookup(transactionStates).whenever(it).loadState(any()) } - protected val log = rigorousMock().also { + protected val log = spectator().also { doReturn(false).whenever(it).isTraceEnabled - doNothing().whenever(it).trace(any(), any()) - doNothing().whenever(it).info(any()) - doNothing().whenever(it).error(any(), any()) } protected fun assertWaitingFor(ssr: ScheduledStateRef, total: Int = 1) { @@ -90,7 +80,7 @@ open class NodeSchedulerServiceTestBase { protected fun assertStarted(flowLogic: FlowLogic<*>) { // Like in assertWaitingFor, use timeout to make verify wait as we often race the call to startFlow: - verify(flowStarter, timeout(5000)).startFlow(same(flowLogic)!!, any(), any()) + verify(flowStarter, timeout(5000)).startFlow(same(flowLogic), any(), any()) } protected fun assertStarted(event: Event) = assertStarted(event.flowLogic) @@ -124,7 +114,7 @@ class MockScheduledFlowRepository : ScheduledFlowRepository { class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() { private val database = rigorousMock().also { doAnswer { - val block: DatabaseTransaction.() -> Any? = uncheckedCast(it.arguments[0]) + val block: DatabaseTransaction.() -> Any? = it.getArgument(0) rigorousMock().block() }.whenever(it).transaction(any()) } @@ -154,7 +144,7 @@ class NodeSchedulerServiceTest : NodeSchedulerServiceTestBase() { val logicRef = rigorousMock() transactionStates[stateRef] = rigorousMock>().also { doReturn(rigorousMock().also { - doReturn(ScheduledActivity(logicRef, time)).whenever(it).nextScheduledActivity(same(stateRef)!!, any()) + doReturn(ScheduledActivity(logicRef, time)).whenever(it).nextScheduledActivity(same(stateRef), any()) }).whenever(it).data } flows[logicRef] = flowLogic diff --git a/samples/irs-demo/build.gradle b/samples/irs-demo/build.gradle index 938dd005af..b116b7d8e9 100644 --- a/samples/irs-demo/build.gradle +++ b/samples/irs-demo/build.gradle @@ -19,6 +19,7 @@ ext['hibernate.version'] = "$hibernate_version" ext['selenium.version'] = "$selenium_version" ext['jackson.version'] = "$jackson_version" ext['dropwizard-metrics.version'] = "$metrics_version" +ext['mockito.version'] = "$mockito_version" apply plugin: 'java' apply plugin: 'kotlin' @@ -111,4 +112,4 @@ idea { downloadJavadoc = true // defaults to false downloadSources = true } -} \ No newline at end of file +} diff --git a/samples/network-visualiser/build.gradle b/samples/network-visualiser/build.gradle index 25b8c89ae7..b45f06246f 100644 --- a/samples/network-visualiser/build.gradle +++ b/samples/network-visualiser/build.gradle @@ -19,7 +19,7 @@ ext['artemis.version'] = "$artemis_version" ext['hibernate.version'] = "$hibernate_version" ext['jackson.version'] = "$jackson_version" ext['dropwizard-metrics.version'] = "$metrics_version" - +ext['mockito.version'] = "$mockito_version" apply plugin: 'java' apply plugin: 'kotlin' diff --git a/testing/test-utils/build.gradle b/testing/test-utils/build.gradle index e11cea5ebc..98beb3372a 100644 --- a/testing/test-utils/build.gradle +++ b/testing/test-utils/build.gradle @@ -21,7 +21,8 @@ dependencies { // Unit testing helpers. compile "junit:junit:$junit_version" compile 'org.hamcrest:hamcrest-library:1.3' - compile "com.nhaarman:mockito-kotlin:1.1.0" + compile 'com.nhaarman:mockito-kotlin:1.5.0' + compile "org.mockito:mockito-core:$mockito_version" compile "org.assertj:assertj-core:$assertj_version" // Guava: Google test library (collections test suite) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt index c620ee6731..4f80f6992d 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt @@ -16,12 +16,8 @@ import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.serialization.amqp.AMQP_ENABLED -import org.mockito.Mockito -import org.mockito.internal.stubbing.answers.ThrowsException -import java.lang.reflect.Modifier import java.nio.file.Files import java.security.KeyPair -import java.util.* import javax.security.auth.x500.X500Principal @Suppress("unused") @@ -38,28 +34,6 @@ inline fun T.amqpSpecific(reason: String, function: () -> Unit loggerFor().info("Ignoring AMQP specific test, reason: $reason") } -/** - * A method on a mock was called, but no behaviour was previously specified for that method. - * You can use [com.nhaarman.mockito_kotlin.doReturn] or similar to specify behaviour, see Mockito documentation for details. - */ -class UndefinedMockBehaviorException(message: String) : RuntimeException(message) - -inline fun rigorousMock() = rigorousMock(T::class.java) -/** - * Create a Mockito mock that has [UndefinedMockBehaviorException] as the default behaviour of all abstract methods, - * and [org.mockito.invocation.InvocationOnMock.callRealMethod] as the default for all concrete methods. - * @param T the type to mock. Note if you want concrete methods of a Kotlin interface to be invoked, - * it won't work unless you mock a (trivial) abstract implementation of that interface instead. - */ -fun rigorousMock(clazz: Class): T = Mockito.mock(clazz) { - if (Modifier.isAbstract(it.method.modifiers)) { - // Use ThrowsException to hack the stack trace, and lazily so we can customise the message: - ThrowsException(UndefinedMockBehaviorException("Please specify what should happen when '${it.method}' is called, or don't call it. Args: ${Arrays.toString(it.arguments)}")).answer(it) - } else { - it.callRealMethod() - } -} - fun configureTestSSL(legalName: CordaX500Name): SSLConfiguration { return object : SSLConfiguration { override val certificatesDirectory = Files.createTempDirectory("certs") @@ -118,9 +92,6 @@ fun createDevNodeCaCertPath( return Triple(rootCa, intermediateCa, nodeCa) } -/** Application of [doAnswer] that gets a value from the given [map] using the arg at [argIndex] as key. */ -fun doLookup(map: Map<*, *>, argIndex: Int = 0) = doAnswer { map[it.arguments[argIndex]] } - fun SSLConfiguration.useSslRpcOverrides(): Map { return mapOf( "rpcSettings.useSsl" to "true", diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/RigorousMock.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/RigorousMock.kt new file mode 100644 index 0000000000..72f4942635 --- /dev/null +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/RigorousMock.kt @@ -0,0 +1,119 @@ +package net.corda.testing.internal + +import com.nhaarman.mockito_kotlin.doAnswer +import net.corda.core.utilities.contextLogger +import org.mockito.Mockito +import org.mockito.exceptions.base.MockitoException +import org.mockito.internal.stubbing.answers.ThrowsException +import org.mockito.invocation.InvocationOnMock +import org.mockito.stubbing.Answer +import java.lang.reflect.Method +import java.lang.reflect.Modifier +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +/** + * A method on a mock was called, but no behaviour was previously specified for that method. + * You can use [com.nhaarman.mockito_kotlin.doReturn] or similar to specify behaviour, see Mockito documentation for details. + */ +class UndefinedMockBehaviorException(message: String) : RuntimeException(message) + +inline fun spectator() = spectator(T::class.java) +inline fun rigorousMock() = rigorousMock(T::class.java) +inline fun participant() = participant(T::class.java) +/** + * Create a Mockito mock where void methods do nothing, and any method of mockable return type will return another spectator, + * and multiple calls to such a method with equal args will return the same spectator. + * This is useful for an inconsequential service such as metrics or logging. + * Unlike plain old Mockito, methods that return primitives and unmockable types such as [String] remain unimplemented. + * Use sparingly, as any invalid behaviour caused by the implicitly-created spectators is likely to be difficult to diagnose. + * As in the other profiles, the exception is [toString] which has a simple reliable implementation for ease of debugging. + */ +fun spectator(clazz: Class) = Mockito.mock(clazz, SpectatorDefaultAnswer())!! + +/** + * Create a Mockito mock that inherits the implementations of all concrete methods from the given type. + * In particular this is convenient for mocking a Kotlin interface via a trivial abstract class. + * As in the other profiles, the exception is [toString] which has a simple reliable implementation for ease of debugging. + */ +fun rigorousMock(clazz: Class) = Mockito.mock(clazz, RigorousMockDefaultAnswer)!! + +/** + * Create a Mockito mock where all methods throw [UndefinedMockBehaviorException]. + * Such mocks are useful when testing a grey box, for complete visibility and control over what methods it calls. + * As in the other profiles, the exception is [toString] which has a simple reliable implementation for ease of debugging. + */ +fun participant(clazz: Class) = Mockito.mock(clazz, ParticipantDefaultAnswer)!! + +private abstract class DefaultAnswer : Answer { + internal abstract fun answerImpl(invocation: InvocationOnMock): Any? + override fun answer(invocation: InvocationOnMock): Any? { + val method = invocation.method + if (method.name == "toString" && method.parameterCount == 0) { + // Regular toString doesn't cache so neither do we: + val mock = invocation.mock + return "${mock.javaClass.simpleName}@${Integer.toHexString(mock.hashCode())}" + } + return answerImpl(invocation) + } +} + +private class SpectatorDefaultAnswer : DefaultAnswer() { + private companion object { + private val log = contextLogger() + } + + private class MethodInfo(invocation: InvocationOnMock) { + // FIXME LATER: The type resolution code probably doesn't cover all cases. + private val type = run { + val method = invocation.method + fun resolveType(context: Type, type: Type): Type { + context as? ParameterizedType ?: return type + val clazz = context.rawType as Class<*> + return context.actualTypeArguments[clazz.typeParameters.indexOf(resolveType(clazz.genericSuperclass, type))] + } + resolveType(invocation.mock.javaClass.genericSuperclass, method.genericReturnType) as? Class<*> + ?: method.returnType!! + } + + private fun newSpectator(invocation: InvocationOnMock) = spectator(type)!!.also { log.info("New spectator {} for: {}", it, invocation.arguments) } + private val spectators = try { + val first = newSpectator(invocation) + ConcurrentHashMap().apply { put(invocation, first) } + } catch (e: MockitoException) { + null // A few types can't be mocked e.g. String. + } + + internal fun spectator(invocation: InvocationOnMock) = spectators?.computeIfAbsent(invocation, ::newSpectator) + } + + private val methods by lazy { ConcurrentHashMap() } + override fun answerImpl(invocation: InvocationOnMock): Any? { + invocation.method.returnType.let { + it == Void.TYPE && return null + it.isPrimitive && return ParticipantDefaultAnswer.answerImpl(invocation) + } + return methods.computeIfAbsent(invocation.method) { MethodInfo(invocation) }.spectator(invocation) + ?: ParticipantDefaultAnswer.answerImpl(invocation) + } +} + +private object RigorousMockDefaultAnswer : DefaultAnswer() { + override fun answerImpl(invocation: InvocationOnMock): Any? { + return if (Modifier.isAbstract(invocation.method.modifiers)) ParticipantDefaultAnswer.answerImpl(invocation) else invocation.callRealMethod() + } +} + +private object ParticipantDefaultAnswer : DefaultAnswer() { + override fun answerImpl(invocation: InvocationOnMock): Any? { + // Use ThrowsException to hack the stack trace, and lazily so we can customise the message: + return ThrowsException(UndefinedMockBehaviorException( + "Please specify what should happen when '${invocation.method}' is called, or don't call it. Args: ${Arrays.toString(invocation.arguments)}")) + .answer(invocation) + } +} + +/** Application of [doAnswer] that gets a value from the given [map] using the arg at [argIndex] as key. */ +fun doLookup(map: Map<*, *>, argIndex: Int = 0) = doAnswer { map[it.getArgument(argIndex)] } diff --git a/testing/test-utils/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker b/testing/test-utils/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..1f0955d450 --- /dev/null +++ b/testing/test-utils/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/testing/test-utils/src/test/kotlin/net/corda/testing/internal/RigorousMockTest.kt b/testing/test-utils/src/test/kotlin/net/corda/testing/internal/RigorousMockTest.kt new file mode 100644 index 0000000000..7a03b5b359 --- /dev/null +++ b/testing/test-utils/src/test/kotlin/net/corda/testing/internal/RigorousMockTest.kt @@ -0,0 +1,126 @@ +package net.corda.testing.internal + +import org.assertj.core.api.Assertions.catchThrowable +import org.hamcrest.Matchers.isA +import org.junit.Assert.assertThat +import org.junit.Test +import java.io.Closeable +import java.io.InputStream +import java.io.Serializable +import java.util.stream.Stream +import kotlin.test.* + +private interface MyInterface { + fun abstractFun(): Int + fun kotlinDefaultFun() = 5 +} + +private abstract class MyAbstract : MyInterface +private open class MyImpl : MyInterface { + override fun abstractFun() = 4 + open fun openFun() = 6 + fun finalFun() = 7 + override fun toString() = "8" +} + +private interface MySpectator { + fun sideEffect() + fun noClearDefault(): Int + fun collaborator(arg: Int): MySpectator +} + +class RigorousMockTest { + @Test + fun `toString has a reliable default answer in all cases`() { + Stream.of<(Class) -> Any>(::spectator, ::rigorousMock, ::participant).forEach { profile -> + Stream.of(MyInterface::class, MyAbstract::class, MyImpl::class).forEach { type -> + val mock = profile(type.java) + assertEquals("${mock.javaClass.simpleName}@${Integer.toHexString(mock.hashCode())}", mock.toString()) + } + } + } + + @Test + fun `callRealMethod is preferred by rigorousMock`() { + rigorousMock().let { m -> + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.abstractFun() }.javaClass) + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.kotlinDefaultFun() }.javaClass) + } + rigorousMock().let { m -> + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.abstractFun() }.javaClass) + assertEquals(5, m.kotlinDefaultFun()) + } + rigorousMock().let { m -> + assertEquals(4, m.abstractFun()) + assertEquals(5, m.kotlinDefaultFun()) + assertEquals(6, m.openFun()) + assertEquals(7, m.finalFun()) + } + } + + @Test + fun `throw exception is preferred by participant`() { + participant().let { m -> + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.abstractFun() }.javaClass) + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.kotlinDefaultFun() }.javaClass) + } + participant().let { m -> + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.abstractFun() }.javaClass) + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.kotlinDefaultFun() }.javaClass) // Broken in older Mockito. + } + participant().let { m -> + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.abstractFun() }.javaClass) + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.kotlinDefaultFun() }.javaClass) + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.openFun() }.javaClass) + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.finalFun() }.javaClass) + } + } + + @Test + fun `doing nothing is preferred by spectator`() { + val mock: MySpectator = spectator() + mock.sideEffect() + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { mock.noClearDefault() }.javaClass) + val collaborator = mock.collaborator(1) + assertNotSame(mock, collaborator) + assertSame(collaborator, mock.collaborator(1)) + assertNotSame(collaborator, mock.collaborator(2)) + collaborator.sideEffect() + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { collaborator.noClearDefault() }.javaClass) + } + + private open class AB { + val a: A get() = throw UnsupportedOperationException() + val b: B get() = throw UnsupportedOperationException() + } + + private open class CD : AB() + private class CDImpl : CD() + + @Test + fun `method return type resolution works`() { + val m = spectator() + assertThat(m.b, isA(Runnable::class.java)) + assertSame(UndefinedMockBehaviorException::class.java, catchThrowable { m.a }.javaClass) // Can't mock String. + } + + private interface RS : Runnable, Serializable + private class TU where T : Runnable, T : Serializable { + fun t(): T = throw UnsupportedOperationException() + fun u(): U = throw UnsupportedOperationException() + } + + @Test + fun `method return type erasure cases`() { + val m = spectator>() + m.t().let { t: Any -> + assertFalse(t is RS) + assertTrue(t is Runnable) + assertFalse(t is Serializable) // Erasure picks the first bound. + } + m.u().let { u: Any -> + assertFalse(u is InputStream) + assertTrue(u is Closeable) + } + } +} diff --git a/tools/demobench/build.gradle b/tools/demobench/build.gradle index 35d5c82e2b..a064dcef6e 100644 --- a/tools/demobench/build.gradle +++ b/tools/demobench/build.gradle @@ -74,7 +74,6 @@ dependencies { testCompile "junit:junit:$junit_version" testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testCompile "org.assertj:assertj-core:${assertj_version}" - testCompile "org.mockito:mockito-core:$mockito_version" } jar { From 573210486d6cb3f234e1a017c4b26ae12bb5e9bb Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Wed, 16 May 2018 14:40:47 +0100 Subject: [PATCH 6/7] snapshot should be all uppercase (#3123) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b2136ccf87..5c1db73817 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { file("$projectDir/constants.properties").withInputStream { constants.load(it) } // Our version: bump this on release. - ext.corda_release_version = "corda-4.0-snapshot" + ext.corda_release_version = "corda-4.0-SNAPSHOT" // Increment this on any release that changes public APIs anywhere in the Corda platform ext.corda_platform_version = constants.getProperty("platformVersion") ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion") From 3d50e73271cb3bbd95822575349995c9771729dd Mon Sep 17 00:00:00 2001 From: Thomas Schroeter Date: Tue, 15 May 2018 12:58:18 +0100 Subject: [PATCH 7/7] Add configuration `notary.serviceLegalName` for clustered notaries --- .../src/main/kotlin/net/corda/node/internal/AbstractNode.kt | 6 ++++-- .../net/corda/node/services/config/NodeConfiguration.kt | 3 ++- .../main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt | 2 +- .../main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt | 2 +- .../kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt | 3 +-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 96dafeecc8..19b19e8197 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -825,10 +825,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } val subject = CordaX500Name.build(certificates[0].subjectX500Principal) - // TODO Include the name of the distributed notary, which the node is part of, in the notary config so that we - // can cross-check the identity we get from the key store if (singleName != null && subject != singleName) { throw ConfigurationException("The name '$singleName' for $id doesn't match what's in the key store: $subject") + } else if (notaryConfig != null && notaryConfig.isClusterConfig && notaryConfig.serviceLegalName != null && subject != notaryConfig.serviceLegalName) { + // Note that we're not checking if `notaryConfig.serviceLegalName` is not present for backwards compatibility. + throw ConfigurationException("The name of the notary service '${notaryConfig.serviceLegalName}' for $id doesn't match what's in the key store: $subject. "+ + "You might need to adjust the configuration of `notary.serviceLegalName`.") } val certPath = X509Utilities.buildCertPath(certificates) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 5eccaa8b39..dda74f670a 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -82,7 +82,8 @@ fun NodeConfiguration.shouldInitCrashShell() = shouldStartLocalShell() || should data class NotaryConfig(val validating: Boolean, val raft: RaftConfig? = null, val bftSMaRt: BFTSMaRtConfiguration? = null, - val custom: Boolean = false + val custom: Boolean = false, + val serviceLegalName: CordaX500Name? = null ) { init { require(raft == null || bftSMaRt == null || !custom) { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index 0a90853a70..2891c662e4 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -48,7 +48,7 @@ class BFTNotaryCordform : CordformDefinition() { val clusterAddresses = (0 until clusterSize).map { NetworkHostAndPort("localhost", 11000 + it * 10) } fun notaryNode(replicaId: Int, configure: CordformNode.() -> Unit) = node { name(notaryNames[replicaId]) - notary(NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses))) + notary(NotaryConfig(validating = false, serviceLegalName = clusterName, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses))) configure() } notaryNode(0) { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index d914d8b3c4..09d7fc603e 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -48,7 +48,7 @@ class RaftNotaryCordform : CordformDefinition() { fun notaryNode(index: Int, nodePort: Int, clusterPort: Int? = null, configure: CordformNode.() -> Unit) = node { name(notaryNames[index]) val clusterAddresses = if (clusterPort != null) listOf(NetworkHostAndPort("localhost", clusterPort)) else emptyList() - notary(NotaryConfig(validating = true, raft = RaftConfig(NetworkHostAndPort("localhost", nodePort), clusterAddresses))) + notary(NotaryConfig(validating = true, serviceLegalName = clusterName, raft = RaftConfig(NetworkHostAndPort("localhost", nodePort), clusterAddresses))) configure() devMode(true) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 8276e21e27..ade6f236f0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -536,6 +536,7 @@ class DriverDSLImpl( val clusterAddresses = if (clusterAddress != null) listOf(clusterAddress) else emptyList() val config = NotaryConfig( validating = spec.validating, + serviceLegalName = spec.name, raft = RaftConfig(nodeAddress = nodeAddress, clusterAddresses = clusterAddresses)) return config.toConfigMap() } @@ -1124,5 +1125,3 @@ fun writeConfig(path: Path, filename: String, config: Config) { private fun Config.toNodeOnly(): Config { return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this } - -private operator fun Config.plus(property: Pair) = withValue(property.first, ConfigValueFactory.fromAnyRef(property.second)) \ No newline at end of file