From 06bc761cf2d71409b452ae3ca34661fa0ee3eb41 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 19 Dec 2018 12:33:30 +0000 Subject: [PATCH 1/4] CORDA-2345: Simplified TestCordapp to make it inline with the recent CorDapp versioning changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TestCordapp has now two implementations to clearly separate the two use cases it has in the Corda repo: * TestCordappImpl which implements the revised public API of TestCordapp; namely that a TestCordapp instance references a real CorDapp jar on the classpath. This is either an external dependency jar in which case it’s taken as is and given to the node, or it’s a local gradle project in which case it’s compiled using the gradle “jar” task to generate the CorDapp jar. This approach means the jar has all the original CorDapp versioning information, which is important that it’s correct when testing. To this end, TestCordapp only needs to expose the ability to specify the app’s config. All the remaining properties have moved to CustomCordapp. * CustomCordapp for creating arbitrary custom CorDapps, including specifying the jar’s MANIFEST values. This is internal API and only used for testing the platform. Technically this shouldn’t implement TestCordapp but does so to reduce the complexity of the driver and mock network. --- .../net/corda/core/internal/InternalUtils.kt | 2 +- .../net/corda/core/flows/FinalityFlowTests.kt | 5 +- docs/source/changelog.rst | 12 +- .../test/JavaIntegrationTestingTutorial.java | 4 +- .../test/KotlinIntegrationTestingTutorial.kt | 3 +- docs/source/upgrade-notes.rst | 7 +- .../internal/CashConfigDataFlowTest.kt | 4 +- ...tachmentsClassLoaderStaticContractTests.kt | 6 +- .../SignatureConstraintVersioningTests.kt | 36 +++-- .../net/corda/node/CordappConstraintsTests.kt | 137 +++++++++-------- .../net/corda/node/NodePerformanceTests.kt | 4 +- ...owCheckpointVersionNodeStartupCheckTest.kt | 56 ++++--- .../registration/NodeRegistrationTest.kt | 4 +- .../cordapp/JarScanningCordappLoaderTest.kt | 7 +- .../node/services/FinalityHandlerTest.kt | 4 +- .../amqp/custom/OptionalSerializerTest.kt | 12 +- .../kotlin/net/corda/testing/driver/Driver.kt | 23 ++- .../corda/testing/driver/NodeParameters.kt | 4 - .../net/corda/testing/node/MockNetwork.kt | 121 +++++++-------- .../net/corda/testing/node/MockServices.kt | 5 +- .../net/corda/testing/node/TestCordapp.kt | 88 +++-------- .../testing/node/internal/CustomCordapp.kt | 139 ++++++++++++++++++ .../testing/node/internal/DriverDSLImpl.kt | 113 ++++++-------- .../node/internal/InternalMockNetwork.kt | 18 ++- .../node/internal/InternalTestUtils.kt | 14 ++ .../testing/node/internal/NodeBasedTest.kt | 13 +- .../corda/testing/node/internal/RPCDriver.kt | 11 +- .../node/internal/TestCordappDirectories.kt | 64 -------- .../testing/node/internal/TestCordappImpl.kt | 104 ++++++++++--- .../node/internal/TestCordappInternal.kt | 49 ++++++ .../node/internal/TestCordappsUtils.kt | 102 ++++--------- .../node/internal/CustomCordappTest.kt | 80 ++++++++++ .../node/internal/TestCordappsUtilsTest.kt | 79 ---------- .../net/corda/explorer/ExplorerSimulation.kt | 4 +- 34 files changed, 719 insertions(+), 615 deletions(-) rename {samples/bank-of-corda-demo => serialization}/src/test/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializerTest.kt (81%) create mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt delete mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappDirectories.kt create mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt create mode 100644 testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/CustomCordappTest.kt diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 56ccac16466..8b230242089 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -387,7 +387,7 @@ val Class<*>.location: URL get() = protectionDomain.codeSource.location /** Convenience method to get the package name of a class literal. */ val KClass<*>.packageName: String get() = java.packageName -val Class<*>.packageName: String get() = `package`.name +val Class<*>.packageName: String get() = requireNotNull(`package`?.name) { "$this not defined inside a package" } inline val Class<*>.isAbstractClass: Boolean get() = Modifier.isAbstract(modifiers) diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index bbfef4be752..8786696354b 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -14,7 +14,6 @@ import net.corda.finance.issuedBy import net.corda.testing.core.* import net.corda.testing.internal.matchers.flow.willReturn import net.corda.testing.internal.matchers.flow.willThrow -import net.corda.testing.node.TestCordapp import net.corda.testing.node.internal.* import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatIllegalArgumentException @@ -77,7 +76,7 @@ class FinalityFlowTests : WithFinality { @Test fun `allow use of the old API if the CorDapp target version is 3`() { // We need Bob to load at least one old CorDapp so that its FinalityHandler is enabled - val bob = createBob(cordapps = listOf(cordappForPackages("com.template").withTargetVersion(3))) + val bob = createBob(cordapps = listOf(cordappWithPackages("com.template").copy(targetPlatformVersion = 3))) val stx = aliceNode.issuesCashTo(bob) val resultFuture = CordappResolver.withCordapp(targetPlatformVersion = 3) { @Suppress("DEPRECATION") @@ -87,7 +86,7 @@ class FinalityFlowTests : WithFinality { assertThat(bob.services.validatedTransactions.getTransaction(stx.id)).isNotNull() } - private fun createBob(cordapps: List = emptyList()): TestStartedNode { + private fun createBob(cordapps: List = emptyList()): TestStartedNode { return mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME, additionalCordapps = cordapps)) } diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 55e63564bd2..15885094902 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -34,6 +34,15 @@ Unreleased This allows Corda 4 signed CorDapps using signature constraints to consume existing hash constrained states generated by unsigned CorDapps in previous versions of Corda. +* You can now load different CorDapps for different nodes in the node-driver and mock-network. This previously wasn't possible with the + ``DriverParameters.extraCordappPackagesToScan`` and ``MockNetwork.cordappPackages`` parameters as all the nodes would get the same CorDapps. + See ``TestCordapp``, ``NodeParameters.additionalCordapps`` and ``MockNodeParameters.additionalCordapps``. + +* ``DriverParameters.extraCordappPackagesToScan`` and ``MockNetwork.cordappPackages`` have been deprecated as they do not support the new + CorDapp versioning and MANIFEST metadata support that has been added. They create artificial CorDapp jars which do not preserve these + settings and thus may produce incorrect results when testing. It is recommended ``DriverParameters.cordappsForAllNodes`` and + ``MockNetworkParameters.cordappsForAllNodes`` be used instead. + * Fixed a problem with IRS demo not being able to simulate future dates as expected (https://github.com/corda/corda/issues/3851). * Fixed a problem that was preventing `Cash.generateSpend` to be used more than once per transaction (https://github.com/corda/corda/issues/4110). @@ -136,9 +145,6 @@ Unreleased * "app", "rpc", "p2p" and "unknown" are no longer allowed as uploader values when importing attachments. These are used internally in security sensitive code. -* Introduced ``TestCorDapp`` and utilities to support asymmetric setups for nodes through ``DriverDSL``, ``MockNetwork`` - and ``MockServices``. - * Change type of the ``checkpoint_value`` column. Please check the upgrade-notes on how to update your database. * Removed buggy :serverNameTablePrefix: configuration. diff --git a/docs/source/example-code/src/integration-test/java/net/corda/docs/java/tutorial/test/JavaIntegrationTestingTutorial.java b/docs/source/example-code/src/integration-test/java/net/corda/docs/java/tutorial/test/JavaIntegrationTestingTutorial.java index aaeed50edfc..54a8d3aabeb 100644 --- a/docs/source/example-code/src/integration-test/java/net/corda/docs/java/tutorial/test/JavaIntegrationTestingTutorial.java +++ b/docs/source/example-code/src/integration-test/java/net/corda/docs/java/tutorial/test/JavaIntegrationTestingTutorial.java @@ -23,6 +23,7 @@ import java.util.List; import static java.util.Arrays.asList; +import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static net.corda.finance.Currencies.DOLLARS; import static net.corda.node.services.Permissions.invokeRpc; @@ -32,6 +33,7 @@ import static net.corda.testing.core.TestConstants.ALICE_NAME; import static net.corda.testing.core.TestConstants.BOB_NAME; import static net.corda.testing.driver.Driver.driver; +import static net.corda.testing.node.internal.TestCordappsUtilsKt.FINANCE_CORDAPP; import static org.junit.Assert.assertEquals; public class JavaIntegrationTestingTutorial { @@ -40,7 +42,7 @@ public void aliceBobCashExchangeExample() { // START 1 driver(new DriverParameters() .withStartNodesInProcess(true) - .withExtraCordappPackagesToScan(singletonList("net.corda.finance")), dsl -> { + .withCordappsForAllNodes(singleton(FINANCE_CORDAPP)), dsl -> { User aliceUser = new User("aliceUser", "testPassword1", new HashSet<>(asList( startFlow(CashIssueAndPaymentFlow.class), diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/kotlin/tutorial/test/KotlinIntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/kotlin/tutorial/test/KotlinIntegrationTestingTutorial.kt index 075d42ab3c9..1a5babcc495 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/kotlin/tutorial/test/KotlinIntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/kotlin/tutorial/test/KotlinIntegrationTestingTutorial.kt @@ -20,6 +20,7 @@ import net.corda.testing.core.* import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.driver import net.corda.testing.node.User +import net.corda.testing.node.internal.FINANCE_CORDAPP import org.junit.Test import rx.Observable import java.util.* @@ -29,7 +30,7 @@ class KotlinIntegrationTestingTutorial { @Test fun `alice bob cash exchange example`() { // START 1 - driver(DriverParameters(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance"))) { + driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = listOf(FINANCE_CORDAPP))) { val aliceUser = User("aliceUser", "testPassword1", permissions = setOf( startFlow(), invokeRpc("vaultTrackBy") diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index 862601ffb7c..145278bb161 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -176,8 +176,8 @@ has been adjusted in the same way as ``FinalityFlow`` above, to close problems w outside of other flow context. Old code will still work, but it is recommended to adjust your call sites so a session is passed into the ``SwapIdentitiesFlow``. -Step 5. Possibly, adjust unit test code ---------------------------------------- +Step 5. Possibly, adjust test code +---------------------------------- ``MockNodeParameters`` and functions creating it no longer use a lambda expecting a ``NodeConfiguration`` object. Use a ``MockNetworkConfigOverrides`` object instead. This is an API change we regret, but unfortunately in Corda 3 we accidentally exposed @@ -202,6 +202,9 @@ becomes:: initialIdentity = TestIdentity(CordaX500Name("TestIdentity", "", "GB")) ) +You may need to use the new ``TestCordapp`` API when testing with the node driver or mock network, especially if you decide to stick with the +old ``FinalityFlow`` API. The previous way of pulling in CorDapps into your tests does not honour CorDapp versioning. + Step 6. Security: refactor to avoid violating sealed packages ------------------------------------------------------------- diff --git a/finance/src/test/kotlin/net/corda/finance/internal/CashConfigDataFlowTest.kt b/finance/src/test/kotlin/net/corda/finance/internal/CashConfigDataFlowTest.kt index 669f1a2c9ac..29a84b9a4e8 100644 --- a/finance/src/test/kotlin/net/corda/finance/internal/CashConfigDataFlowTest.kt +++ b/finance/src/test/kotlin/net/corda/finance/internal/CashConfigDataFlowTest.kt @@ -7,7 +7,7 @@ import net.corda.finance.USD import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetworkParameters import net.corda.testing.node.MockNodeParameters -import net.corda.testing.node.internal.cordappForPackages +import net.corda.testing.node.internal.cordappWithPackages import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Test @@ -21,7 +21,7 @@ class CashConfigDataFlowTest { @Test fun `issuable currencies read in from cordapp config`() { val node = mockNet.createNode(MockNodeParameters(additionalCordapps = listOf( - cordappForPackages(javaClass.packageName).withConfig(mapOf("issuableCurrencies" to listOf("EUR", "USD"))) + cordappWithPackages(javaClass.packageName).copy(config = mapOf("issuableCurrencies" to listOf("EUR", "USD"))) ))) val config = node.startFlow(CashConfigDataFlow()).getOrThrow() assertThat(config.issuableCurrencies).containsExactly(EUR, USD) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt index 705d3e83c74..76a4c3292c4 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt @@ -26,8 +26,7 @@ import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.internal.rigorousMock -import net.corda.testing.node.internal.TestCordappDirectories -import net.corda.testing.node.internal.cordappsForPackages +import net.corda.testing.node.internal.cordappWithPackages import net.corda.testing.services.MockAttachmentStorage import org.assertj.core.api.Assertions.assertThat import org.junit.Assert.assertEquals @@ -112,7 +111,6 @@ class AttachmentsClassLoaderStaticContractTests { } private fun cordappLoaderForPackages(packages: Collection): CordappLoader { - val dirs = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) } - return JarScanningCordappLoader.fromDirectories(dirs) + return JarScanningCordappLoader.fromJarUrls(listOf(cordappWithPackages(*packages.toTypedArray()).jarFile.toUri().toURL())) } } diff --git a/node/src/integration-test/kotlin/net/corda/contracts/SignatureConstraintVersioningTests.kt b/node/src/integration-test/kotlin/net/corda/contracts/SignatureConstraintVersioningTests.kt index 6e2f4bae56c..297922c8a90 100644 --- a/node/src/integration-test/kotlin/net/corda/contracts/SignatureConstraintVersioningTests.kt +++ b/node/src/integration-test/kotlin/net/corda/contracts/SignatureConstraintVersioningTests.kt @@ -6,6 +6,8 @@ import net.corda.core.contracts.* import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.identity.Party +import net.corda.core.internal.deleteRecursively +import net.corda.core.internal.div import net.corda.core.internal.packageName import net.corda.core.messaging.startFlow import net.corda.core.transactions.LedgerTransaction @@ -23,7 +25,7 @@ import net.corda.testing.core.singleIdentity import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.node.User -import net.corda.testing.node.internal.cordappForPackages +import net.corda.testing.node.internal.cordappWithPackages import net.corda.testing.node.internal.internalDriver import org.junit.Assume.assumeFalse import org.junit.Test @@ -34,9 +36,9 @@ import kotlin.test.assertNotNull class SignatureConstraintVersioningTests { - private val base = cordappForPackages(MessageState::class.packageName, DummyMessageContract::class.packageName) - private val oldCordapp = base.withVersion("2") - private val newCordapp = base.withVersion("3") + private val base = cordappWithPackages(MessageState::class.packageName, DummyMessageContract::class.packageName).signed() + private val oldCordapp = base.copy(versionId = 2) + private val newCordapp = base.copy(versionId = 3) private val user = User("mark", "dadada", setOf(startFlow(), startFlow(), invokeRpc("vaultQuery"))) private val message = Message("Hello world!") private val transformetMessage = Message(message.value + "A") @@ -45,11 +47,13 @@ class SignatureConstraintVersioningTests { fun `can evolve from lower contract class version to higher one`() { assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")) // See NodeStatePersistenceTests.kt. - val stateAndRef: StateAndRef? = internalDriver(inMemoryDB = false, + val stateAndRef: StateAndRef? = internalDriver( + inMemoryDB = false, startNodesInProcess = isQuasarAgentSpecified(), - networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4), signCordapps = true) { - var nodeName = { - val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp), regenerateCordappsOnStart = true)).getOrThrow() + networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4) + ) { + val nodeName = { + val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp))).getOrThrow() val nodeName = nodeHandle.nodeInfo.singleIdentity().name CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use { it.proxy.startFlow(::CreateMessage, message, defaultNotaryIdentity).returnValue.getOrThrow() @@ -57,8 +61,9 @@ class SignatureConstraintVersioningTests { nodeHandle.stop() nodeName }() - var result = { - val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp), regenerateCordappsOnStart = true)).getOrThrow() + val result = { + (baseDirectory(nodeName) / "cordapps").deleteRecursively() + val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp))).getOrThrow() var result: StateAndRef? = CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use { val page = it.proxy.vaultQuery(MessageState::class.java) page.states.singleOrNull() @@ -87,9 +92,9 @@ class SignatureConstraintVersioningTests { val stateAndRef: StateAndRef? = internalDriver(inMemoryDB = false, startNodesInProcess = isQuasarAgentSpecified(), - networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4), signCordapps = true) { - var nodeName = { - val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp), regenerateCordappsOnStart = true), + networkParameters = testNetworkParameters(notaries = emptyList(), minimumPlatformVersion = 4)) { + val nodeName = { + val nodeHandle = startNode(NodeParameters(rpcUsers = listOf(user), additionalCordapps = listOf(newCordapp)), customOverrides = mapOf("h2Settings.address" to "localhost:$port")).getOrThrow() val nodeName = nodeHandle.nodeInfo.singleIdentity().name CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use { @@ -98,8 +103,9 @@ class SignatureConstraintVersioningTests { nodeHandle.stop() nodeName }() - var result = { - val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp), regenerateCordappsOnStart = true), + val result = { + (baseDirectory(nodeName) / "cordapps").deleteRecursively() + val nodeHandle = startNode(NodeParameters(providedName = nodeName, rpcUsers = listOf(user), additionalCordapps = listOf(oldCordapp)), customOverrides = mapOf("h2Settings.address" to "localhost:$port")).getOrThrow() //set the attachment with newer version (3) as untrusted one so the node can use only the older attachment with version 2 diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt b/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt index eb1219f62b5..bb4a078c4ba 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt @@ -4,6 +4,8 @@ import net.corda.core.contracts.HashAttachmentConstraint import net.corda.core.contracts.SignatureAttachmentConstraint import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.withoutIssuer +import net.corda.core.internal.deleteRecursively +import net.corda.core.internal.div import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.messaging.vaultQueryBy @@ -25,7 +27,8 @@ import net.corda.testing.driver.* import net.corda.testing.node.NotarySpec import net.corda.testing.node.User import net.corda.testing.node.internal.FINANCE_CORDAPP -import org.assertj.core.api.Assertions +import net.corda.testing.node.internal.cordappWithPackages +import org.assertj.core.api.Assertions.assertThat import org.junit.Test class CordappConstraintsTests { @@ -35,6 +38,7 @@ class CordappConstraintsTests { invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name), invokeRpc(CordaRPCOps::notaryIdentities), invokeRpc("vaultTrackByCriteria"))) + val UNSIGNED_FINANCE_CORDAPP = cordappWithPackages("net.corda.finance") } @Test @@ -44,9 +48,11 @@ class CordappConstraintsTests { inMemoryDB = false )) { - val alice = startNode(NodeParameters(additionalCordapps = listOf(FINANCE_CORDAPP.signJar()), + val alice = startNode(NodeParameters( + additionalCordapps = listOf(FINANCE_CORDAPP), providedName = ALICE_NAME, - rpcUsers = listOf(user))).getOrThrow() + rpcUsers = listOf(user) + )).getOrThrow() val expected = 500.DOLLARS val ref = OpaqueBytes.of(0x01) @@ -57,8 +63,8 @@ class CordappConstraintsTests { val states = alice.rpc.vaultQueryBy().states printVault(alice, states) - Assertions.assertThat(states).hasSize(1) - Assertions.assertThat(states[0].state.constraint is SignatureAttachmentConstraint) + assertThat(states).hasSize(1) + assertThat(states[0].state.constraint).isInstanceOf(SignatureAttachmentConstraint::class.java) } } @@ -70,9 +76,11 @@ class CordappConstraintsTests { )) { println("Starting the node using unsigned contract jar ...") - val alice = startNode(NodeParameters(providedName = ALICE_NAME, - additionalCordapps = listOf(FINANCE_CORDAPP), - rpcUsers = listOf(user))).getOrThrow() + val alice = startNode(NodeParameters( + providedName = ALICE_NAME, + additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP), + rpcUsers = listOf(user) + )).getOrThrow() val expected = 500.DOLLARS val ref = OpaqueBytes.of(0x01) @@ -82,7 +90,7 @@ class CordappConstraintsTests { // Query vault val states = alice.rpc.vaultQueryBy().states printVault(alice, states) - Assertions.assertThat(states).hasSize(1) + assertThat(states).hasSize(1) // Restart the node and re-query the vault println("Shutting down the node ...") @@ -90,14 +98,15 @@ class CordappConstraintsTests { alice.stop() println("Restarting the node using signed contract jar ...") - val restartedNode = startNode(NodeParameters(providedName = ALICE_NAME, - additionalCordapps = listOf(FINANCE_CORDAPP.signJar()), - regenerateCordappsOnStart = true + (baseDirectory(ALICE_NAME) / "cordapps").deleteRecursively() + val restartedNode = startNode(NodeParameters( + providedName = ALICE_NAME, + additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP.signed()) )).getOrThrow() val statesAfterRestart = restartedNode.rpc.vaultQueryBy().states printVault(restartedNode, statesAfterRestart) - Assertions.assertThat(statesAfterRestart).hasSize(1) + assertThat(statesAfterRestart).hasSize(1) val issueTx2 = restartedNode.rpc.startFlow(::CashIssueFlow, expected, ref, defaultNotaryIdentity).returnValue.getOrThrow() println("Issued 2nd transaction: $issueTx2") @@ -106,19 +115,19 @@ class CordappConstraintsTests { val allStates = restartedNode.rpc.vaultQueryBy().states printVault(restartedNode, allStates) - Assertions.assertThat(allStates).hasSize(2) - Assertions.assertThat(allStates[0].state.constraint is HashAttachmentConstraint) - Assertions.assertThat(allStates[1].state.constraint is SignatureAttachmentConstraint) + assertThat(allStates).hasSize(2) + assertThat(allStates[0].state.constraint).isInstanceOf(HashAttachmentConstraint::class.java) + assertThat(allStates[1].state.constraint).isInstanceOf(SignatureAttachmentConstraint::class.java) } } @Test fun `issue and consume cash using hash constraints`() { - driver(DriverParameters(cordappsForAllNodes = listOf(FINANCE_CORDAPP), - extraCordappPackagesToScan = listOf("net.corda.finance"), + driver(DriverParameters( + cordappsForAllNodes = listOf(UNSIGNED_FINANCE_CORDAPP), networkParameters = testNetworkParameters(minimumPlatformVersion = 4), - inMemoryDB = false)) { - + inMemoryDB = false + )) { val (alice, bob) = listOf( startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)), startNode(providedName = BOB_NAME, rpcUsers = listOf(user)) @@ -145,10 +154,10 @@ class CordappConstraintsTests { val aliceStates = aliceQuery.states printVault(alice, aliceQuery.states) - Assertions.assertThat(aliceStates).hasSize(1) - Assertions.assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) - Assertions.assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED) - Assertions.assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH) + assertThat(aliceStates).hasSize(1) + assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) + assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED) + assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH) // Check Bob Vault Updates vaultUpdatesBob.expectEvents { @@ -165,23 +174,23 @@ class CordappConstraintsTests { val bobStates = bobQuery.states printVault(bob, bobQuery.states) - Assertions.assertThat(bobStates).hasSize(1) - Assertions.assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) - Assertions.assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED) - Assertions.assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH) + assertThat(bobStates).hasSize(1) + assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) + assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED) + assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH) } } @Test fun `issue and consume cash using signature constraints`() { - driver(DriverParameters(cordappsForAllNodes = listOf(FINANCE_CORDAPP.signJar()), - extraCordappPackagesToScan = listOf("net.corda.finance"), + driver(DriverParameters( + cordappsForAllNodes = listOf(FINANCE_CORDAPP), networkParameters = testNetworkParameters(minimumPlatformVersion = 4), - inMemoryDB = false)) { - + inMemoryDB = false + )) { val (alice, bob) = listOf( - startNode(NodeParameters(providedName = ALICE_NAME, rpcUsers = listOf(user), additionalCordapps = listOf(FINANCE_CORDAPP.signJar()))), - startNode(NodeParameters(providedName = BOB_NAME, rpcUsers = listOf(user), additionalCordapps = listOf(FINANCE_CORDAPP.signJar()))) + startNode(NodeParameters(providedName = ALICE_NAME, rpcUsers = listOf(user))), + startNode(NodeParameters(providedName = BOB_NAME, rpcUsers = listOf(user))) ).map { it.getOrThrow() } // Issue Cash @@ -205,10 +214,10 @@ class CordappConstraintsTests { val aliceStates = aliceQuery.states printVault(alice, aliceStates) - Assertions.assertThat(aliceStates).hasSize(1) - Assertions.assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) - Assertions.assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED) - Assertions.assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE) + assertThat(aliceStates).hasSize(1) + assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) + assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED) + assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE) // Check Bob Vault Updates vaultUpdatesBob.expectEvents { @@ -225,10 +234,10 @@ class CordappConstraintsTests { val bobStates = bobQuery.states printVault(bob, bobStates) - Assertions.assertThat(bobStates).hasSize(1) - Assertions.assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) - Assertions.assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED) - Assertions.assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE) + assertThat(bobStates).hasSize(1) + assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) + assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED) + assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE) } } @@ -239,13 +248,15 @@ class CordappConstraintsTests { val keyStoreDir = SelfCleaningDir() val packageOwnerKey = keyStoreDir.path.generateKey() - driver(DriverParameters(cordappsForAllNodes = listOf(FINANCE_CORDAPP), + driver(DriverParameters( + cordappsForAllNodes = listOf(UNSIGNED_FINANCE_CORDAPP), notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = false)), - extraCordappPackagesToScan = listOf("net.corda.finance"), - networkParameters = testNetworkParameters(minimumPlatformVersion = 4, - packageOwnership = mapOf("net.corda.finance.contracts.asset" to packageOwnerKey)), - inMemoryDB = false)) { - + networkParameters = testNetworkParameters( + minimumPlatformVersion = 4, + packageOwnership = mapOf("net.corda.finance.contracts.asset" to packageOwnerKey) + ), + inMemoryDB = false + )) { val (alice, bob) = listOf( startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)), startNode(providedName = BOB_NAME, rpcUsers = listOf(user)) @@ -270,15 +281,17 @@ class CordappConstraintsTests { bob.stop() println("Restarting the node for $ALICE_NAME ...") - val restartedAlice = startNode(NodeParameters(providedName = ALICE_NAME, - additionalCordapps = listOf(FINANCE_CORDAPP.signJar(keyStoreDir.path)), - regenerateCordappsOnStart = true + (baseDirectory(ALICE_NAME) / "cordapps").deleteRecursively() + val restartedAlice = startNode(NodeParameters( + providedName = ALICE_NAME, + additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP.signed(keyStoreDir.path)) )).getOrThrow() println("Restarting the node for $BOB_NAME ...") - val restartedBob = startNode(NodeParameters(providedName = BOB_NAME, - additionalCordapps = listOf(FINANCE_CORDAPP.signJar(keyStoreDir.path)), - regenerateCordappsOnStart = true + (baseDirectory(BOB_NAME) / "cordapps").deleteRecursively() + val restartedBob = startNode(NodeParameters( + providedName = BOB_NAME, + additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP.signed(keyStoreDir.path)) )).getOrThrow() // Register for Bob vault updates @@ -295,10 +308,10 @@ class CordappConstraintsTests { val aliceStates = aliceQuery.states printVault(alice, aliceStates) - Assertions.assertThat(aliceStates).hasSize(1) - Assertions.assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) - Assertions.assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED) - Assertions.assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH) + assertThat(aliceStates).hasSize(1) + assertThat(aliceStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) + assertThat(aliceQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.CONSUMED) + assertThat(aliceQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.HASH) // Check Bob Vault Updates vaultUpdatesBob.expectEvents { @@ -316,10 +329,10 @@ class CordappConstraintsTests { val bobStates = bobQuery.states printVault(bob, bobStates) - Assertions.assertThat(bobStates).hasSize(1) - Assertions.assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) - Assertions.assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED) - Assertions.assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE) + assertThat(bobStates).hasSize(1) + assertThat(bobStates[0].state.data.amount.withoutIssuer()).isEqualTo(1000.DOLLARS) + assertThat(bobQuery.statesMetadata[0].status).isEqualTo(Vault.StateStatus.UNCONSUMED) + assertThat(bobQuery.statesMetadata[0].constraintInfo!!.type()).isEqualTo(Vault.ConstraintInfo.Type.SIGNATURE) // clean-up keyStoreDir.close() diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt index 3834f9efafd..0cbc778c4e3 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -22,7 +22,7 @@ import net.corda.testing.driver.internal.internalServices import net.corda.testing.internal.performance.div import net.corda.testing.node.NotarySpec import net.corda.testing.node.User -import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages +import net.corda.testing.node.internal.FINANCE_CORDAPP import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector import net.corda.testing.node.internal.performance.startReporter @@ -97,7 +97,7 @@ class NodePerformanceTests { internalDriver( notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(user))), startNodesInProcess = true, - cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance") + cordappsForAllNodes = listOf(FINANCE_CORDAPP) ) { val notary = defaultNotaryNode.getOrThrow() as InProcess val metricRegistry = startReporter(this.shutdownManager, notary.internalServices.monitoringService.metrics) diff --git a/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt b/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt index 9c9dbe06ebb..24523b5bada 100644 --- a/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt @@ -18,12 +18,14 @@ import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.driver import net.corda.testing.node.TestCordapp +import net.corda.testing.node.internal.CustomCordapp import net.corda.testing.node.internal.ListenProcessDeathException -import net.corda.testing.node.internal.TestCordappDirectories import net.corda.testing.node.internal.cordappForClasses import net.test.cordapp.v1.Record import net.test.cordapp.v1.SendMessageFlow +import org.assertj.core.api.Assertions.assertThat import org.junit.Test +import java.nio.file.Path import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.util.* import java.util.concurrent.TimeUnit @@ -46,8 +48,8 @@ class FlowCheckpointVersionNodeStartupCheckTest { @Test fun `restart node successfully with suspended flow`() { - return driver(parametersForRestartingNodes(listOf(defaultCordapp))) { - createSuspendedFlowInBob(cordapps = emptySet()) + return driver(parametersForRestartingNodes()) { + createSuspendedFlowInBob(setOf(defaultCordapp)) // Bob will resume the flow val alice = startNode(providedName = ALICE_NAME).getOrThrow() startNode(providedName = BOB_NAME).getOrThrow() @@ -66,19 +68,20 @@ class FlowCheckpointVersionNodeStartupCheckTest { @Test fun `restart node with incompatible version of suspended flow due to different jar name`() { driver(parametersForRestartingNodes()) { - val cordapp = defaultCordapp.withName("different-jar-name-test-${UUID.randomUUID()}") - // Create the CorDapp jar file manually first to get hold of the directory that will contain it so that we can - // rename the filename later. The cordappDir, which acts as pointer to the jar file, does not get renamed. - val cordappDir = TestCordappDirectories.getJarDirectory(cordapp) - val cordappJar = cordappDir.list().single { it.toString().endsWith(".jar") } + val uniqueName = "different-jar-name-test-${UUID.randomUUID()}" + val cordapp = defaultCordapp.copy(name = uniqueName) - createSuspendedFlowInBob(setOf(cordapp)) + val bobBaseDir = createSuspendedFlowInBob(setOf(cordapp)) + val cordappsDir = bobBaseDir / "cordapps" + val cordappJar = cordappsDir.list().single { it.toString().endsWith(".jar") } + // Make sure we're dealing with right jar + assertThat(cordappJar.fileName.toString()).contains(uniqueName) // Rename the jar file. - cordappJar.moveTo(cordappDir / "renamed-${cordappJar.fileName}") + cordappJar.moveTo(cordappsDir / "renamed-${cordappJar.fileName}") assertBobFailsToStartWithLogMessage( - setOf(cordapp), + emptyList(), CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message ) } @@ -87,25 +90,30 @@ class FlowCheckpointVersionNodeStartupCheckTest { @Test fun `restart node with incompatible version of suspended flow due to different jar hash`() { driver(parametersForRestartingNodes()) { - val originalCordapp = defaultCordapp.withName("different-jar-hash-test-${UUID.randomUUID()}") - val originalCordappJar = TestCordappDirectories.getJarDirectory(originalCordapp).list().single { it.toString().endsWith(".jar") } + val uniqueName = "different-jar-hash-test-${UUID.randomUUID()}" + val cordapp = defaultCordapp.copy(name = uniqueName) - createSuspendedFlowInBob(setOf(originalCordapp)) + val bobBaseDir = createSuspendedFlowInBob(setOf(cordapp)) - // The vendor is part of the MANIFEST so changing it is sufficient to change the jar hash - val modifiedCordapp = originalCordapp.withVendor("${originalCordapp.vendor}-modified") - val modifiedCordappJar = TestCordappDirectories.getJarDirectory(modifiedCordapp).list().single { it.toString().endsWith(".jar") } - modifiedCordappJar.moveTo(originalCordappJar, REPLACE_EXISTING) + val cordappsDir = bobBaseDir / "cordapps" + val cordappJar = cordappsDir.list().single { it.toString().endsWith(".jar") } + // Make sure we're dealing with right jar + assertThat(cordappJar.fileName.toString()).contains(uniqueName) + + // The name is part of the MANIFEST so changing it is sufficient to change the jar hash + val modifiedCordapp = cordapp.copy(name = "${cordapp.name}-modified") + val modifiedCordappJar = CustomCordapp.getJarFile(modifiedCordapp) + modifiedCordappJar.moveTo(cordappJar, REPLACE_EXISTING) assertBobFailsToStartWithLogMessage( - setOf(originalCordapp), + emptyList(), // The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException "that is incompatible with the current installed version of" ) } } - private fun DriverDSL.createSuspendedFlowInBob(cordapps: Set) { + private fun DriverDSL.createSuspendedFlowInBob(cordapps: Set): Path { val (alice, bob) = listOf(ALICE_NAME, BOB_NAME) .map { startNode(NodeParameters(providedName = it, additionalCordapps = cordapps)) } .transpose() @@ -115,6 +123,7 @@ class FlowCheckpointVersionNodeStartupCheckTest { // Wait until Bob progresses as far as possible because Alice node is offline flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single() bob.stop() + return bob.baseDirectory } private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection, logMessage: String) { @@ -122,8 +131,7 @@ class FlowCheckpointVersionNodeStartupCheckTest { startNode(NodeParameters( providedName = BOB_NAME, customOverrides = mapOf("devMode" to false), - additionalCordapps = cordapps, - regenerateCordappsOnStart = true + additionalCordapps = cordapps )).getOrThrow() } @@ -133,11 +141,11 @@ class FlowCheckpointVersionNodeStartupCheckTest { assertEquals(1, matchingLineCount) } - private fun parametersForRestartingNodes(cordappsForAllNodes: List = emptyList()): DriverParameters { + private fun parametersForRestartingNodes(): DriverParameters { return DriverParameters( startNodesInProcess = false, // Start nodes in separate processes to ensure CordappLoader is not shared between restarts inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows - cordappsForAllNodes = cordappsForAllNodes + cordappsForAllNodes = emptyList() ) } } diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index e679aa5a8bf..d65715fd974 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -20,7 +20,7 @@ import net.corda.testing.core.singleIdentity import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.node.NotarySpec -import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages +import net.corda.testing.node.internal.FINANCE_CORDAPP import net.corda.testing.node.internal.SharedCompatibilityZoneParams import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.network.NetworkMapServer @@ -88,7 +88,7 @@ class NodeRegistrationTest { portAllocation = portAllocation, compatibilityZone = compatibilityZone, notarySpecs = listOf(NotarySpec(notaryName)), - cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance"), + cordappsForAllNodes = listOf(FINANCE_CORDAPP), notaryCustomOverrides = mapOf("devMode" to false) ) { val (alice, genevieve) = listOf( diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt index d11b1676e7f..a2f4121b723 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt @@ -5,8 +5,7 @@ import net.corda.core.flows.* import net.corda.core.internal.packageName import net.corda.node.VersionInfo import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES -import net.corda.testing.node.internal.TestCordappDirectories -import net.corda.testing.node.internal.cordappForPackages +import net.corda.testing.node.internal.cordappWithPackages import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.nio.file.Paths @@ -68,8 +67,8 @@ class JarScanningCordappLoaderTest { @Test fun `flows are loaded by loader`() { - val dir = TestCordappDirectories.getJarDirectory(cordappForPackages(javaClass.packageName)) - val loader = JarScanningCordappLoader.fromDirectories(listOf(dir)) + val jarFile = cordappWithPackages(javaClass.packageName).jarFile + val loader = JarScanningCordappLoader.fromJarUrls(listOf(jarFile.toUri().toURL())) // One cordapp from this source tree. In gradle it will also pick up the node jar. assertThat(loader.cordapps).isNotEmpty diff --git a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt index b7fc729328e..626055aa7d7 100644 --- a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt @@ -46,7 +46,7 @@ class FinalityHandlerTest { legalName = BOB_NAME, // The node disables the FinalityHandler completely if there are no old CorDapps loaded, so we need to add // a token old CorDapp to keep the handler running. - additionalCordapps = setOf(cordappForPackages(javaClass.packageName).withTargetVersion(3)) + additionalCordapps = setOf(cordappWithPackages(javaClass.packageName).copy(targetPlatformVersion = 3)) )) val stx = alice.issueCashTo(bob) @@ -75,7 +75,7 @@ class FinalityHandlerTest { val bob = mockNet.createNode(InternalMockNodeParameters( legalName = BOB_NAME, // Make sure the target version is 4, and not the current platform version which may be greater - additionalCordapps = setOf(FINANCE_CORDAPP.withTargetVersion(4)) + additionalCordapps = setOf(cordappWithPackages("net.corda.finance").copy(targetPlatformVersion = 4)) )) val stx = alice.issueCashTo(bob) diff --git a/samples/bank-of-corda-demo/src/test/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializerTest.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializerTest.kt similarity index 81% rename from samples/bank-of-corda-demo/src/test/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializerTest.kt rename to serialization/src/test/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializerTest.kt index 058f56fe666..8812cd63d12 100644 --- a/samples/bank-of-corda-demo/src/test/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializerTest.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/custom/OptionalSerializerTest.kt @@ -3,7 +3,7 @@ package net.corda.serialization.internal.amqp.custom import net.corda.serialization.internal.amqp.SerializerFactory import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.nullValue -import org.junit.Assert +import org.junit.Assert.assertThat import org.junit.Test import org.mockito.Mockito import java.util.* @@ -13,27 +13,27 @@ class OptionalSerializerTest { fun `should convert optional with item to proxy`() { val opt = Optional.of("GenericTestString") val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt) - Assert.assertThat(proxy.item, `is`("GenericTestString")) + assertThat(proxy.item, `is`("GenericTestString")) } @Test fun `should convert optional without item to empty proxy`() { val opt = Optional.ofNullable(null) val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt) - Assert.assertThat(proxy.item, `is`(nullValue())) + assertThat(proxy.item, `is`(nullValue())) } @Test fun `should convert proxy without item to empty optional `() { val proxy = OptionalSerializer.OptionalProxy(null) val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy) - Assert.assertThat(opt.isPresent, `is`(false)) + assertThat(opt.isPresent, `is`(false)) } @Test fun `should convert proxy with item to empty optional `() { val proxy = OptionalSerializer.OptionalProxy("GenericTestString") val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy) - Assert.assertThat(opt.get(), `is`("GenericTestString")) + assertThat(opt.get(), `is`("GenericTestString")) } -} \ No newline at end of file +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 15b54202983..d8299d0cb0a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -6,6 +6,8 @@ import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.flows.FlowLogic import net.corda.core.identity.Party +import net.corda.core.internal.div +import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo @@ -19,7 +21,10 @@ import net.corda.testing.driver.internal.internalServices import net.corda.testing.node.NotarySpec import net.corda.testing.node.TestCordapp import net.corda.testing.node.User -import net.corda.testing.node.internal.* +import net.corda.testing.node.internal.DriverDSLImpl +import net.corda.testing.node.internal.genericDriver +import net.corda.testing.node.internal.getTimestampAsDirectoryName +import net.corda.testing.node.internal.newContext import rx.Observable import java.nio.file.Path import java.nio.file.Paths @@ -136,7 +141,8 @@ constructor( val startJmxHttpServer: Boolean = false, val jmxHttpServerPortAllocation: PortAllocation = incrementalPortAllocation(7005) ) { - @Deprecated("The default constructor does not turn on monitoring. Simply leave the jmxPolicy parameter unspecified if you wish to not have monitoring turned on.") + @Deprecated("The default constructor does not turn on monitoring. Simply leave the jmxPolicy parameter unspecified if you wish to not " + + "have monitoring turned on.") constructor() : this(false) /** Create a [JmxPolicy] that turns on monitoring using the given [PortAllocation]. */ @@ -178,14 +184,14 @@ fun driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr isDebug = defaultParameters.isDebug, startNodesInProcess = defaultParameters.startNodesInProcess, waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish, + extraCordappPackagesToScan = defaultParameters.extraCordappPackagesToScan, notarySpecs = defaultParameters.notarySpecs, jmxPolicy = defaultParameters.jmxPolicy, compatibilityZone = null, networkParameters = defaultParameters.networkParameters, notaryCustomOverrides = defaultParameters.notaryCustomOverrides, inMemoryDB = defaultParameters.inMemoryDB, - cordappsForAllNodes = defaultParameters.cordappsForAllNodes(), - signCordapps = false + cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes) ), coerce = { it }, dsl = dsl @@ -212,7 +218,8 @@ fun driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr * available from [DriverDSL.notaryHandles], and will be added automatically to the network parameters. * Defaults to a simple validating notary. * @property extraCordappPackagesToScan A [List] of additional cordapp packages to scan for any cordapp code, e.g. - * contract verification code, flows and services. The calling package is automatically added. + * contract verification code, flows and services. The calling package is automatically included in this list. If this is not desirable + * then use [cordappsForAllNodes] instead. * @property jmxPolicy Used to specify whether to expose JMX metrics via Jolokia HHTP/JSON. * @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be * empty as notaries are defined by [notarySpecs]. @@ -225,7 +232,7 @@ fun driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr @Suppress("unused") data class DriverParameters( val isDebug: Boolean = false, - val driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), + val driverDirectory: Path = Paths.get("build") / "node-driver" / getTimestampAsDirectoryName(), val portAllocation: PortAllocation = incrementalPortAllocation(10000), val debugPortAllocation: PortAllocation = incrementalPortAllocation(5005), val systemProperties: Map = emptyMap(), @@ -233,6 +240,8 @@ data class DriverParameters( val startNodesInProcess: Boolean = false, val waitForAllNodesToFinish: Boolean = false, val notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY_NAME)), + @Deprecated("extraCordappPackagesToScan does not preserve the original CorDapp's versioning and metadata, which may lead to " + + "misleading results in tests. Use cordappsForAllNodes instead.") val extraCordappPackagesToScan: List = emptyList(), @Suppress("DEPRECATION") val jmxPolicy: JmxPolicy = JmxPolicy(), val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()), @@ -242,7 +251,7 @@ data class DriverParameters( ) { constructor( isDebug: Boolean = false, - driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), + driverDirectory: Path = Paths.get("build") / "node-driver" / getTimestampAsDirectoryName(), portAllocation: PortAllocation = incrementalPortAllocation(10000), debugPortAllocation: PortAllocation = incrementalPortAllocation(5005), systemProperties: Map = emptyMap(), diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NodeParameters.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NodeParameters.kt index baeecec3988..ad509aede7b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NodeParameters.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NodeParameters.kt @@ -20,8 +20,6 @@ import net.corda.testing.node.User * @property maximumHeapSize The maximum JVM heap size to use for the node. Defaults to 512 MB. * @property additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes * managed by the [DriverDSL]. - * @property regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping - * and restarting the same node. */ @Suppress("unused") data class NodeParameters( @@ -32,7 +30,6 @@ data class NodeParameters( val startInSameProcess: Boolean? = null, val maximumHeapSize: String = "512m", val additionalCordapps: Collection = emptySet(), - val regenerateCordappsOnStart: Boolean = false, val flowOverrides: Map>, Class>> = emptyMap() ) { /** @@ -48,7 +45,6 @@ data class NodeParameters( fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess) fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize) fun withAdditionalCordapps(additionalCordapps: Set): NodeParameters = copy(additionalCordapps = additionalCordapps) - fun withRegenerateCordappsOnStart(regenerateCordappsOnStart: Boolean): NodeParameters = copy(regenerateCordappsOnStart = regenerateCordappsOnStart) fun withFlowOverrides(flowOverrides: Map>, Class>>): NodeParameters = copy(flowOverrides = flowOverrides) constructor( diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt index b3c0b099384..e12b07500b3 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt @@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowSession import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.internal.uncheckedCast import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub @@ -16,13 +17,15 @@ import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.config.NodeConfiguration import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME -import net.corda.testing.node.internal.* +import net.corda.testing.node.internal.InternalMockNetwork +import net.corda.testing.node.internal.InternalMockNodeParameters +import net.corda.testing.node.internal.TestStartedNode +import net.corda.testing.node.internal.newContext import rx.Observable import java.math.BigInteger import java.nio.file.Path import java.util.concurrent.Future - /** * Immutable builder for configuring a [StartedMockNode] or an [UnstartedMockNode] via [MockNetwork.createNode] and * [MockNetwork.createUnstartedNode]. Kotlin users can also use the named parameters overloads of those methods which @@ -77,6 +80,7 @@ data class MockNodeParameters( * @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient. * @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be * empty as notaries are defined by [notarySpecs]. + * @property cordappsForAllNodes [TestCordapp]s added to all nodes. */ @Suppress("unused") data class MockNetworkParameters( @@ -84,13 +88,32 @@ data class MockNetworkParameters( val threadPerNode: Boolean = false, val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), val notarySpecs: List = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)), - val networkParameters: NetworkParameters = testNetworkParameters() + val networkParameters: NetworkParameters = testNetworkParameters(), + val cordappsForAllNodes: Collection = emptyList() ) { + constructor(networkSendManuallyPumped: Boolean, + threadPerNode: Boolean, + servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy, + notarySpecs: List, + networkParameters: NetworkParameters + ) : this(networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters, emptyList()) + fun withNetworkParameters(networkParameters: NetworkParameters): MockNetworkParameters = copy(networkParameters = networkParameters) fun withNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean): MockNetworkParameters = copy(networkSendManuallyPumped = networkSendManuallyPumped) fun withThreadPerNode(threadPerNode: Boolean): MockNetworkParameters = copy(threadPerNode = threadPerNode) - fun withServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy): MockNetworkParameters = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy) + fun withServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy): MockNetworkParameters { + return copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy) + } fun withNotarySpecs(notarySpecs: List): MockNetworkParameters = copy(notarySpecs = notarySpecs) + fun withCordappsForAllNodes(cordappsForAllNodes: Collection): MockNetworkParameters = copy(cordappsForAllNodes = cordappsForAllNodes) + + fun copy(networkSendManuallyPumped: Boolean, + threadPerNode: Boolean, + servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy, + notarySpecs: List, + networkParameters: NetworkParameters): MockNetworkParameters { + return MockNetworkParameters(networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters, emptyList()) + } } /** @@ -282,33 +305,37 @@ inline fun > StartedMockNode.registerResponderFlow( * @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient. * @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be * empty as notaries are defined by [notarySpecs]. - * @property cordappsForAllNodes [TestCordapp]s that will be added to each node started by the [MockNetwork]. */ @Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") open class MockNetwork( + @Deprecated("cordappPackages does not preserve the original CorDapp's versioning and metadata, which may lead to " + + "misleading results in tests. Use MockNetworkParameters.cordappsForAllNodes instead.") val cordappPackages: List, val defaultParameters: MockNetworkParameters = MockNetworkParameters(), val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, val threadPerNode: Boolean = defaultParameters.threadPerNode, val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, val notarySpecs: List = defaultParameters.notarySpecs, - val networkParameters: NetworkParameters = defaultParameters.networkParameters, - val cordappsForAllNodes: Collection = cordappsForPackages(cordappPackages)) { - + val networkParameters: NetworkParameters = defaultParameters.networkParameters +) { + @Deprecated("cordappPackages does not preserve the original CorDapp's versioning and metadata, which may lead to " + + "misleading results in tests. Use MockNetworkParameters.cordappsForAllNodes instead.") @JvmOverloads constructor(cordappPackages: List, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters) - constructor( - cordappPackages: List, - defaultParameters: MockNetworkParameters = MockNetworkParameters(), - networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, - threadPerNode: Boolean = defaultParameters.threadPerNode, - servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, - notarySpecs: List = defaultParameters.notarySpecs, - networkParameters: NetworkParameters = defaultParameters.networkParameters - ) : this(emptyList(), defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters, cordappsForAllNodes = cordappsForPackages(cordappPackages)) - - private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, initialNetworkParameters = networkParameters, cordappsForAllNodes = cordappsForAllNodes) + @JvmOverloads + constructor(parameters: MockNetworkParameters = MockNetworkParameters()) : this(emptyList(), parameters) + + private val internalMockNetwork = InternalMockNetwork( + cordappPackages, + defaultParameters, + networkSendManuallyPumped, + threadPerNode, + servicePeerAllocationStrategy, + notarySpecs, + initialNetworkParameters = networkParameters, + cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes) + ) /** In a mock network, nodes have an incrementing integer ID. Real networks do not have this. Returns the next ID that will be used. */ val nextNodeId get(): Int = internalMockNetwork.nextNodeId @@ -346,58 +373,18 @@ open class MockNetwork( * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, * but can be overridden to cause nodes to have stable or colliding identity/service keys. * @param configOverrides Add/override the default configuration/behaviour of the node - * @param extraCordappPackages Extra CorDapp packages to add for this node. */ @JvmOverloads fun createNode(legalName: CordaX500Name? = null, forcedID: Int? = null, entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: MockNodeConfigOverrides? = null, - extraCordappPackages: List = emptyList()): StartedMockNode { - - return createNode(legalName, forcedID, entropyRoot, configOverrides, cordappsForPackages(extraCordappPackages)) - } - - /** - * Create a started node with the given parameters. - * - * @param legalName The node's legal name. - * @param forcedID A unique identifier for the node. - * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overridden to cause nodes to have stable or colliding identity/service keys. - * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. - * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork]. - */ - fun createNode(legalName: CordaX500Name? = null, - forcedID: Int? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: MockNodeConfigOverrides? = null, - additionalCordapps: Collection): StartedMockNode { - val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps) - return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters))) + configOverrides: MockNodeConfigOverrides? = null): StartedMockNode { + return createNode(MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides)) } /** Create an unstarted node with the given parameters. **/ - fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): UnstartedMockNode = UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters))) - - /** - * Create an unstarted node with the given parameters. - * - * @param legalName The node's legal name. - * @param forcedID A unique identifier for the node. - * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overridden to cause nodes to have stable or colliding identity/service keys. - * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. - * @param extraCordappPackages Extra CorDapp packages to add for this node. - */ - @JvmOverloads - fun createUnstartedNode(legalName: CordaX500Name? = null, - forcedID: Int? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: MockNodeConfigOverrides? = null, - extraCordappPackages: List = emptyList()): UnstartedMockNode { - - return createUnstartedNode(legalName, forcedID, entropyRoot, configOverrides, cordappsForPackages(extraCordappPackages)) + fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()): UnstartedMockNode { + return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters))) } /** @@ -408,15 +395,13 @@ open class MockNetwork( * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, * but can be overridden to cause nodes to have stable or colliding identity/service keys. * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. - * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork]. */ + @JvmOverloads fun createUnstartedNode(legalName: CordaX500Name? = null, forcedID: Int? = null, entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: MockNodeConfigOverrides? = null, - additionalCordapps: Collection): UnstartedMockNode { - val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps) - return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters))) + configOverrides: MockNodeConfigOverrides? = null): UnstartedMockNode { + return createUnstartedNode(MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides)) } /** Start all nodes that aren't already started. **/ diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 447401cd06e..ceefd1d9564 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -33,7 +33,6 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.contextTransaction import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.common.internal.addNotary import net.corda.testing.core.TestIdentity import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.MockCordappProvider @@ -76,10 +75,8 @@ open class MockServices private constructor( ) : ServiceHub { companion object { - private fun cordappLoaderForPackages(packages: Iterable, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader { - val cordappPaths = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) } - return JarScanningCordappLoader.fromDirectories(cordappPaths, versionInfo) + return JarScanningCordappLoader.fromJarUrls(cordappsForPackages(packages).map { it.jarFile.toUri().toURL() }, versionInfo) } /** diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt index f2372b06f4e..f55b66810ae 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt @@ -1,87 +1,41 @@ package net.corda.testing.node import net.corda.core.DoNotImplement -import net.corda.core.internal.PLATFORM_VERSION +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.NodeParameters import net.corda.testing.node.internal.TestCordappImpl -import net.corda.testing.node.internal.simplifyScanPackages -import java.nio.file.Path /** - * Represents information about a CorDapp. Used to generate CorDapp JARs in tests. + * Encapsulates a CorDapp that exists on the current classpath, which can be pulled in for testing. Use [TestCordapp.Factory.findCordapp] + * to locate an existing CorDapp. + * + * @see DriverParameters.cordappsForAllNodes + * @see NodeParameters.additionalCordapps + * @see MockNetworkParameters.cordappsForAllNodes + * @see MockNodeParameters.additionalCordapps */ @DoNotImplement interface TestCordapp { - /** Returns the name, defaults to "test-name" if not specified. */ - val name: String + /** The package used to find the CorDapp. This may not be the root package of the CorDapp. */ + val scanPackage: String - /** Returns the title, defaults to "test-title" if not specified. */ - val title: String - - /** Returns the version string, defaults to "1.0" if not specified. */ - val version: String - - /** Returns the vendor string, defaults to "test-vendor" if not specified. */ - val vendor: String - - /** Returns the target platform version, defaults to the current platform version if not specified. */ - val targetVersion: Int - - /** Returns the config for this CorDapp, defaults to empty if not specified. */ + /** Returns the config to be applied on this CorDapp, defaults to empty if not specified. */ val config: Map - /** Returns the set of package names scanned for this test CorDapp. */ - val packages: Set - - /** Returns whether the CorDapp should be jar signed. */ - val signJar: Boolean - - /** Return a copy of this [TestCordapp] but with the specified name. */ - fun withName(name: String): TestCordapp - - /** Return a copy of this [TestCordapp] but with the specified title. */ - fun withTitle(title: String): TestCordapp - - /** Return a copy of this [TestCordapp] but with the specified version string. */ - fun withVersion(version: String): TestCordapp - - /** Return a copy of this [TestCordapp] but with the specified vendor string. */ - fun withVendor(vendor: String): TestCordapp - - /** Return a copy of this [TestCordapp] but with the specified target platform version. */ - fun withTargetVersion(targetVersion: Int): TestCordapp - - /** Returns a copy of this [TestCordapp] but with the specified CorDapp config. */ - fun withConfig(config: Map): TestCordapp - - /** Returns a signed copy of this [TestCordapp]. - * Optionally can pass in the location of an existing java key store to use */ - fun signJar(keyStorePath: Path? = null): TestCordappImpl - class Factory { companion object { /** - * Create a [TestCordapp] object by scanning the given packages. The meta data on the CorDapp will be the - * default values, which can be changed with the wither methods. - */ - @JvmStatic - fun fromPackages(vararg packageNames: String): TestCordapp = fromPackages(packageNames.asList()) - - /** - * Create a [TestCordapp] object by scanning the given packages. The meta data on the CorDapp will be the - * default values, which can be changed with the wither methods. + * Scans the current classpath to find the CorDapp that contains the given package. All the CorDapp's metdata present in its + * MANIFEST are inherited. If more than one location containing the package is found then an exception is thrown. An exception + * is also thrown if no CorDapp is found. + * + * @param scanPackage The package name used to find the CorDapp. This does not need to be the root package. + * @param config config to be applied with the CorDapp. */ @JvmStatic - fun fromPackages(packageNames: Collection): TestCordapp { - return TestCordappImpl( - name = "test-name", - version = "1", - vendor = "test-vendor", - title = "test-title", - targetVersion = PLATFORM_VERSION, - config = emptyMap(), - packages = simplifyScanPackages(packageNames), - classes = emptySet() - ) + @JvmOverloads + fun findCordapp(scanPackage: String, config: Map = emptyMap()): TestCordapp { + return TestCordappImpl(scanPackage = scanPackage, config = config) } } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt new file mode 100644 index 00000000000..9af96128366 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt @@ -0,0 +1,139 @@ +package net.corda.testing.node.internal + +import io.github.classgraph.ClassGraph +import net.corda.core.internal.* +import net.corda.core.internal.cordapp.CordappImpl +import net.corda.core.internal.cordapp.set +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.debug +import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey +import net.corda.testing.core.internal.JarSignatureTestUtils.signJar +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.attribute.FileTime +import java.time.Instant +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.jar.Attributes +import java.util.jar.JarFile +import java.util.jar.JarOutputStream +import java.util.jar.Manifest +import java.util.zip.ZipEntry + +/** + * Represents a completely custom CorDapp comprising of resources taken from packages on the existing classpath, even including individual + * disparate classes. The CorDapp metadata that's present in the MANIFEST can also be tailored. + */ +data class CustomCordapp( + val packages: Set, + val name: String = "custom-cordapp", + val versionId: Int = 1, + val targetPlatformVersion: Int = PLATFORM_VERSION, + val classes: Set> = emptySet(), + val sign: Sign? = null, + override val config: Map = emptyMap() +) : TestCordappInternal { + init { + require(packages.isNotEmpty() || classes.isNotEmpty()) { "At least one package or class must be specified" } + } + + override val jarFile: Path get() = getJarFile(this) + + override val scanPackage: String get() = throw UnsupportedOperationException() + + override fun withoutMeta(): CustomCordapp = copy(sign = null, config = emptyMap()) + + fun signed(keyStorePath: Path? = null): CustomCordapp = copy(sign = Sign(keyStorePath)) + + @VisibleForTesting + internal fun packageAsJar(file: Path) { + val scanResult = ClassGraph() + .whitelistPackages(*packages.toTypedArray()) + .whitelistClasses(*classes.map { it.name }.toTypedArray()) + .scan() + + scanResult.use { + JarOutputStream(file.outputStream()).use { jos -> + jos.addEntry(testEntry(JarFile.MANIFEST_NAME)) { + createTestManifest(name, versionId, targetPlatformVersion).write(jos) + } + + // The same resource may be found in different locations (this will happen when running from gradle) so just + // pick the first one found. + scanResult.allResources.asMap().forEach { path, resourceList -> + jos.addEntry(testEntry(path), resourceList[0].open()) + } + } + } + } + + private fun signJar(jarFile: Path) { + if (sign != null) { + val testKeystore = "_teststore" + val alias = "Test" + val pwd = "secret!" + val keyStorePathToUse = if (sign.keyStorePath != null) { + sign.keyStorePath + } else { + defaultJarSignerDirectory.createDirectories() + if (!(defaultJarSignerDirectory / testKeystore).exists()) { + defaultJarSignerDirectory.generateKey(alias, pwd, "O=Test Company Ltd,OU=Test,L=London,C=GB") + } + defaultJarSignerDirectory + } + val pk = keyStorePathToUse.signJar(jarFile.toString(), alias, pwd) + logger.debug { "Signed Jar: $jarFile with public key $pk" } + } else { + logger.debug { "Unsigned Jar: $jarFile" } + } + } + + private fun createTestManifest(name: String, versionId: Int, targetPlatformVersion: Int): Manifest { + val manifest = Manifest() + + // Mandatory manifest attribute. If not present, all other entries are silently skipped. + manifest[Attributes.Name.MANIFEST_VERSION] = "1.0" + + manifest[CordappImpl.CORDAPP_CONTRACT_NAME] = name + manifest[CordappImpl.CORDAPP_CONTRACT_VERSION] = versionId.toString() + manifest[CordappImpl.CORDAPP_WORKFLOW_NAME] = name + manifest[CordappImpl.CORDAPP_WORKFLOW_VERSION] = versionId.toString() + manifest[CordappImpl.TARGET_PLATFORM_VERSION] = targetPlatformVersion.toString() + + return manifest + } + + private fun testEntry(name: String): ZipEntry { + return ZipEntry(name).setCreationTime(epochFileTime).setLastAccessTime(epochFileTime).setLastModifiedTime(epochFileTime) + } + + data class Sign(val keyStorePath: Path? = null) + + companion object { + private val logger = contextLogger() + private val epochFileTime = FileTime.from(Instant.EPOCH) + private val cordappsDirectory: Path + private val defaultJarSignerDirectory: Path + private val whitespace = "\\s".toRegex() + private val cache = ConcurrentHashMap() + + init { + val buildDir = Paths.get("build").toAbsolutePath() + val timeDirName = getTimestampAsDirectoryName() + cordappsDirectory = buildDir / "generated-custom-cordapps" / timeDirName + defaultJarSignerDirectory = buildDir / "jar-signer" / timeDirName + } + + fun getJarFile(cordapp: CustomCordapp): Path { + // The CorDapp config is external to the jar and so can be ignored here + return cache.computeIfAbsent(cordapp.copy(config = emptyMap())) { + val filename = it.run { "${name.replace(whitespace, "-")}_${versionId}_${targetPlatformVersion}_${UUID.randomUUID()}.jar" } + val jarFile = cordappsDirectory.createDirectories() / filename + it.packageAsJar(jarFile) + it.signJar(jarFile) + logger.debug { "$it packaged into $jarFile" } + jarFile + } + } + } +} 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 2decaddae45..9d200c2ae33 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 @@ -27,7 +27,6 @@ import net.corda.node.internal.NodeWithInfo import net.corda.node.internal.clientSslOptionsCompatibleWith import net.corda.node.services.Permissions import net.corda.node.services.config.* -import net.corda.node.services.config.NodeConfiguration.Companion.cordappDirectoriesKey import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NodeRegistrationHelper import net.corda.nodeapi.internal.DevIdentityGenerator @@ -49,8 +48,6 @@ import net.corda.testing.driver.internal.OutOfProcessImpl import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.node.ClusterSpec import net.corda.testing.node.NotarySpec -import net.corda.testing.node.TestCordapp -import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages import okhttp3.OkHttpClient import okhttp3.Request import rx.Subscription @@ -73,6 +70,7 @@ import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicInteger import kotlin.collections.ArrayList import kotlin.collections.HashMap +import kotlin.collections.HashSet import kotlin.concurrent.thread import net.corda.nodeapi.internal.config.User as InternalUser @@ -85,20 +83,21 @@ class DriverDSLImpl( val isDebug: Boolean, val startNodesInProcess: Boolean, val waitForAllNodesToFinish: Boolean, + val extraCordappPackagesToScan: List, val jmxPolicy: JmxPolicy, val notarySpecs: List, val compatibilityZone: CompatibilityZoneParams?, val networkParameters: NetworkParameters, val notaryCustomOverrides: Map, val inMemoryDB: Boolean, - val cordappsForAllNodes: Collection, - val signCordapps: Boolean + val cordappsForAllNodes: Collection? ) : InternalDriverDSL { private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! private var _shutdownManager: ShutdownManager? = null override val shutdownManager get() = _shutdownManager!! + private lateinit var extraCustomCordapps: Set // Map from a nodes legal name to an observable emitting the number of nodes in its network map. private val networkVisibilityController = NetworkVisibilityController() /** @@ -198,7 +197,7 @@ class DriverDSLImpl( return registrationFuture.flatMap { networkMapAvailability.flatMap { // But starting the node proper does require the network map - startRegisteredNode(name, it, parameters, p2pAddress, signCordapps) + startRegisteredNode(name, it, parameters, p2pAddress) } } } @@ -206,8 +205,7 @@ class DriverDSLImpl( private fun startRegisteredNode(name: CordaX500Name, localNetworkMap: LocalNetworkMap?, parameters: NodeParameters, - p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(), - signCordapps: Boolean = false): CordaFuture { + p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort()): CordaFuture { val rpcAddress = portAllocation.nextHostAndPort() val rpcAdminAddress = portAllocation.nextHostAndPort() val webAddress = portAllocation.nextHostAndPort() @@ -229,7 +227,7 @@ class DriverDSLImpl( } val flowOverrideConfig = FlowOverrideConfig(parameters.flowOverrides.map { FlowOverride(it.key.canonicalName, it.value.canonicalName) }) - + val overrides = configOf( NodeConfiguration::myLegalName.name to name.toString(), NodeConfiguration::p2pAddress.name to p2pAddress.toString(), @@ -245,7 +243,7 @@ class DriverDSLImpl( allowMissingConfig = true, configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true) )).checkAndOverrideForInMemoryDB() - return startNodeInternal(config, webAddress, localNetworkMap, parameters, signCordapps) + return startNodeInternal(config, webAddress, localNetworkMap, parameters) } private fun startNodeRegistration( @@ -333,6 +331,9 @@ class DriverDSLImpl( require(networkParameters.notaries.isEmpty()) { "Define notaries using notarySpecs" } _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _shutdownManager = ShutdownManager(executorService) + + extraCustomCordapps = cordappsForPackages(extraCordappPackagesToScan + getCallerPackage()) + val notaryInfosFuture = if (compatibilityZone == null) { // If no CZ is specified then the driver does the generation of the network parameters and the copying of the // node info files. @@ -369,6 +370,27 @@ class DriverDSLImpl( } } + /** + * Get the package of the caller to the driver so that it can be added to the list of packages the nodes will scan. + * This makes the driver automatically pick the CorDapp module that it's run from. + * + * This returns List rather than String? to make it easier to bolt onto extraCordappPackagesToScan. + */ + private fun getCallerPackage(): List { + if (cordappsForAllNodes != null) { + // We turn this feature off if cordappsForAllNodes is being used + return emptyList() + } + val stackTrace = Throwable().stackTrace + val index = stackTrace.indexOfLast { it.className == "net.corda.testing.driver.Driver" } + return if (index == -1) { + // In this case we're dealing with the the RPCDriver or one of it's cousins which are internal and we don't care about them + emptyList() + } else { + listOf(Class.forName(stackTrace[index + 1].className).packageName) + } + } + private fun startNotaryIdentityGeneration(): CordaFuture> { return executorService.fork { notarySpecs.map { spec -> @@ -531,13 +553,12 @@ class DriverDSLImpl( } } - private fun startNodeInternal(specifiedConfig: NodeConfig, + private fun startNodeInternal(config: NodeConfig, webAddress: NetworkHostAndPort, localNetworkMap: LocalNetworkMap?, - parameters: NodeParameters, - signCordapps: Boolean = false): CordaFuture { - val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName) - val baseDirectory = specifiedConfig.corda.baseDirectory.createDirectories() + parameters: NodeParameters): CordaFuture { + val visibilityHandle = networkVisibilityController.register(config.corda.myLegalName) + val baseDirectory = config.corda.baseDirectory.createDirectories() localNetworkMap?.networkParametersCopier?.install(baseDirectory) localNetworkMap?.nodeInfosCopier?.addConfig(baseDirectory) @@ -546,24 +567,13 @@ class DriverDSLImpl( visibilityHandle.close() } - val useHTTPS = specifiedConfig.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") } + val useHTTPS = config.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") } - val existingCorDappDirectories = if (parameters.regenerateCordappsOnStart) { - emptyList() - } else if (specifiedConfig.typesafe.hasPath(cordappDirectoriesKey)) { - specifiedConfig.typesafe.getStringList(cordappDirectoriesKey) - } else { - emptyList() - } - - // Instead of using cordappsForAllNodes we get only these that are missing from additionalCordapps - // This way we prevent errors when we want the same CordApp but with different config - val appOverrides = parameters.additionalCordapps.map { it.name to it.version }.toSet() - val baseCordapps = cordappsForAllNodes.filter { !appOverrides.contains(it.name to it.version) } - - val cordappDirectories = existingCorDappDirectories + (baseCordapps + parameters.additionalCordapps).map { TestCordappDirectories.getJarDirectory(it, signJar = signCordapps).toString() } - - val config = NodeConfig(specifiedConfig.typesafe.withValue(cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet()))) + TestCordappInternal.installCordapps( + baseDirectory, + parameters.additionalCordapps.mapTo(HashSet()) { it as TestCordappInternal }, + extraCustomCordapps + (cordappsForAllNodes ?: emptySet()) + ) if (parameters.startInSameProcess ?: startNodesInProcess) { val nodeAndThreadFuture = startInProcessNode(executorService, config) @@ -685,14 +695,6 @@ class DriverDSLImpl( private fun oneOf(array: Array) = array[Random().nextInt(array.size)] - fun cordappsInCurrentAndAdditionalPackages(packagesToScan: Collection = emptySet()): List { - return cordappsForPackages(getCallerPackage() + packagesToScan) - } - - fun cordappsInCurrentAndAdditionalPackages(firstPackage: String, vararg otherPackages: String): List { - return cordappsInCurrentAndAdditionalPackages(otherPackages.asList() + firstPackage) - } - private fun startInProcessNode( executorService: ScheduledExecutorService, config: NodeConfig @@ -818,22 +820,6 @@ class DriverDSLImpl( private operator fun Config.plus(property: Pair) = withValue(property.first, ConfigValueFactory.fromAnyRef(property.second)) - /** - * Get the package of the caller to the driver so that it can be added to the list of packages the nodes will scan. - * This makes the driver automatically pick the CorDapp module that it's run from. - * - * This returns List rather than String? to make it easier to bolt onto extraCordappPackagesToScan. - */ - private fun getCallerPackage(): List { - val stackTrace = Throwable().stackTrace - val index = stackTrace.indexOfLast { it.className == "net.corda.testing.driver.Driver" } - // In this case we're dealing with the the RPCDriver or one of it's cousins which are internal and we don't care about them - if (index == -1) return emptyList() - val callerPackage = Class.forName(stackTrace[index + 1].className).`package` - ?: throw IllegalStateException("Function instantiating driver must be defined in a package.") - return listOf(callerPackage.name) - } - /** * We have an alternative way of specifying classpath for spawned process: by using "-cp" option. So duplicating the setting of this * rather long string is un-necessary and can be harmful on Windows. @@ -997,14 +983,14 @@ fun genericDriver( isDebug = defaultParameters.isDebug, startNodesInProcess = defaultParameters.startNodesInProcess, waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish, + extraCordappPackagesToScan = defaultParameters.extraCordappPackagesToScan, jmxPolicy = defaultParameters.jmxPolicy, notarySpecs = defaultParameters.notarySpecs, compatibilityZone = null, networkParameters = defaultParameters.networkParameters, notaryCustomOverrides = defaultParameters.notaryCustomOverrides, inMemoryDB = defaultParameters.inMemoryDB, - cordappsForAllNodes = defaultParameters.cordappsForAllNodes(), - signCordapps = false + cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes) ) ) val shutdownHook = addShutdownHook(driverDsl::shutdown) @@ -1089,6 +1075,7 @@ fun internalDriver( systemProperties: Map = DriverParameters().systemProperties, useTestClock: Boolean = DriverParameters().useTestClock, startNodesInProcess: Boolean = DriverParameters().startNodesInProcess, + extraCordappPackagesToScan: List = DriverParameters().extraCordappPackagesToScan, waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish, notarySpecs: List = DriverParameters().notarySpecs, jmxPolicy: JmxPolicy = DriverParameters().jmxPolicy, @@ -1096,8 +1083,7 @@ fun internalDriver( compatibilityZone: CompatibilityZoneParams? = null, notaryCustomOverrides: Map = DriverParameters().notaryCustomOverrides, inMemoryDB: Boolean = DriverParameters().inMemoryDB, - cordappsForAllNodes: Collection = DriverParameters().cordappsForAllNodes(), - signCordapps: Boolean = false, + cordappsForAllNodes: Collection? = null, dsl: DriverDSLImpl.() -> A ): A { return genericDriver( @@ -1110,14 +1096,14 @@ fun internalDriver( isDebug = isDebug, startNodesInProcess = startNodesInProcess, waitForAllNodesToFinish = waitForAllNodesToFinish, + extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, jmxPolicy = jmxPolicy, compatibilityZone = compatibilityZone, networkParameters = networkParameters, notaryCustomOverrides = notaryCustomOverrides, inMemoryDB = inMemoryDB, - cordappsForAllNodes = cordappsForAllNodes, - signCordapps = signCordapps + cordappsForAllNodes = cordappsForAllNodes ), coerce = { it }, dsl = dsl @@ -1137,9 +1123,6 @@ private fun Config.toNodeOnly(): Config { return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this } -internal fun DriverParameters.cordappsForAllNodes(): Collection = cordappsForAllNodes - ?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan) - fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture { val customOverrides = if (!devMode) { val nodeDir = baseDirectory(providedName) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index dedcaf791c8..6cb9186099b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -92,7 +92,7 @@ data class InternalMockNodeParameters( val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val configOverrides: MockNodeConfigOverrides? = null, val version: VersionInfo = MOCK_VERSION_INFO, - val additionalCordapps: Collection? = null, + val additionalCordapps: Collection = emptyList(), val flowManager: MockNodeFlowManager = MockNodeFlowManager()) { constructor(mockNodeParameters: MockNodeParameters) : this( mockNodeParameters.forcedID, @@ -100,7 +100,7 @@ data class InternalMockNodeParameters( mockNodeParameters.entropyRoot, mockNodeParameters.configOverrides, MOCK_VERSION_INFO, - mockNodeParameters.additionalCordapps + uncheckedCast(mockNodeParameters.additionalCordapps) ) } @@ -141,15 +141,17 @@ interface TestStartedNode { fun > registerInitiatedFlow(initiatingFlowClass: Class>, initiatedFlowClass: Class, track: Boolean = false): Observable } -open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParameters(), +open class InternalMockNetwork(cordappPackages: List = emptyList(), + // TODO InternalMockNetwork does not need MockNetworkParameters + defaultParameters: MockNetworkParameters = MockNetworkParameters(), val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, val threadPerNode: Boolean = defaultParameters.threadPerNode, servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, val notarySpecs: List = defaultParameters.notarySpecs, - val testDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), + val testDirectory: Path = Paths.get("build") / "mock-network" / getTimestampAsDirectoryName(), initialNetworkParameters: NetworkParameters = testNetworkParameters(), val defaultFactory: (MockNodeArgs) -> MockNode = { args -> MockNode(args) }, - val cordappsForAllNodes: Collection = emptySet(), + cordappsForAllNodes: Collection = emptySet(), val autoVisibleNodes: Boolean = true) : AutoCloseable { var networkParameters: NetworkParameters = initialNetworkParameters @@ -174,6 +176,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe private val _nodes = mutableListOf() private val serializationEnv = checkNotNull(setDriverSerialization()) { "Using more than one mock network simultaneously is not supported." } private val sharedUserCount = AtomicInteger(0) + private val combinedCordappsForAllNodes = cordappsForPackages(cordappPackages) + cordappsForAllNodes /** A read only view of the current set of nodes. */ val nodes: List get() = _nodes @@ -466,12 +469,11 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe doReturn(parameters.legalName ?: CordaX500Name("Mock Company $id", "London", "GB")).whenever(it).myLegalName doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties doReturn(emptyList()).whenever(it).extraNetworkMapKeys + doReturn(listOf(baseDirectory / "cordapps")).whenever(it).cordappDirectories parameters.configOverrides?.applyMockNodeOverrides(it) } - val cordapps = (parameters.additionalCordapps ?: emptySet()) + cordappsForAllNodes - val cordappDirectories = cordapps.map { TestCordappDirectories.getJarDirectory(it) }.distinct() - doReturn(cordappDirectories).whenever(config).cordappDirectories + TestCordappInternal.installCordapps(baseDirectory, parameters.additionalCordapps.toSet(), combinedCordappsForAllNodes) val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version, flowManager = parameters.flowManager)) _nodes += node diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt index 21160e78fcd..e37370bc50c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalTestUtils.kt @@ -29,11 +29,14 @@ import net.corda.testing.node.testContext import org.slf4j.LoggerFactory import rx.Observable import rx.subjects.AsyncSubject +import java.io.InputStream import java.net.Socket import java.net.SocketException import java.time.Duration import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit +import java.util.jar.JarOutputStream +import java.util.zip.ZipEntry private val log = LoggerFactory.getLogger("net.corda.testing.internal.InternalTestUtils") @@ -152,3 +155,14 @@ private class DriverSerializationEnvironment : SerializationEnvironment by creat inVMExecutors.remove(this) } } + +/** Add a new entry using the entire remaining bytes of [input] for the entry content. [input] will be closed at the end. */ +fun JarOutputStream.addEntry(entry: ZipEntry, input: InputStream) { + addEntry(entry) { input.use { it.copyTo(this) } } +} + +inline fun JarOutputStream.addEntry(entry: ZipEntry, write: () -> Unit) { + putNextEntry(entry) + write() + closeEntry() +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt index f0b32fb9ad7..f6f527696d7 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt @@ -1,6 +1,5 @@ package net.corda.testing.node.internal -import com.typesafe.config.ConfigValueFactory import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.PLATFORM_VERSION @@ -121,16 +120,10 @@ constructor(private val cordappPackages: List = emptyList(), private val ) + configOverrides ) - val cordapps = cordappsForPackages(getCallerPackage(NodeBasedTest::class)?.let { cordappPackages + it } - ?: cordappPackages) + val customCordapps = cordappsForPackages(getCallerPackage(NodeBasedTest::class)?.let { cordappPackages + it } ?: cordappPackages) + TestCordappInternal.installCordapps(baseDirectory, emptySet(), customCordapps) - val existingCorDappDirectoriesOption = if (config.hasPath(NodeConfiguration.cordappDirectoriesKey)) config.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList() - - val cordappDirectories = existingCorDappDirectoriesOption + cordapps.map { TestCordappDirectories.getJarDirectory(it).toString() } - - val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet())) - - val parsedConfig = specificConfig.parseAsNodeConfiguration().value() + val parsedConfig = config.parseAsNodeConfiguration().value() defaultNetworkParameters.install(baseDirectory) val node = InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), flowManager = flowManager) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index 307f1a513c4..b2bdc84663c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -32,9 +32,7 @@ import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.internal.TestingNamedCacheFactory import net.corda.testing.internal.fromUserList import net.corda.testing.node.NotarySpec -import net.corda.testing.node.TestCordapp import net.corda.testing.node.User -import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.TransportConfiguration import org.apache.activemq.artemis.api.core.client.ActiveMQClient @@ -108,20 +106,21 @@ private val globalDebugPortAllocation = incrementalPortAllocation(5005) fun rpcDriver( isDebug: Boolean = false, - driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), + driverDirectory: Path = Paths.get("build") / "rpc-driver" / getTimestampAsDirectoryName(), portAllocation: PortAllocation = globalPortAllocation, debugPortAllocation: PortAllocation = globalDebugPortAllocation, systemProperties: Map = emptyMap(), useTestClock: Boolean = false, startNodesInProcess: Boolean = false, waitForNodesToFinish: Boolean = false, + extraCordappPackagesToScan: List = emptyList(), notarySpecs: List = emptyList(), externalTrace: Trace? = null, @Suppress("DEPRECATION") jmxPolicy: JmxPolicy = JmxPolicy(), networkParameters: NetworkParameters = testNetworkParameters(), notaryCustomOverrides: Map = emptyMap(), inMemoryDB: Boolean = true, - cordappsForAllNodes: Collection = cordappsInCurrentAndAdditionalPackages(), + cordappsForAllNodes: Collection? = null, dsl: RPCDriverDSL.() -> A ): A { return genericDriver( @@ -135,14 +134,14 @@ fun rpcDriver( isDebug = isDebug, startNodesInProcess = startNodesInProcess, waitForAllNodesToFinish = waitForNodesToFinish, + extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, jmxPolicy = jmxPolicy, compatibilityZone = null, networkParameters = networkParameters, notaryCustomOverrides = notaryCustomOverrides, inMemoryDB = inMemoryDB, - cordappsForAllNodes = cordappsForAllNodes, - signCordapps = false + cordappsForAllNodes = cordappsForAllNodes ), externalTrace ), coerce = { it }, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappDirectories.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappDirectories.kt deleted file mode 100644 index 6e3c4c28d73..00000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappDirectories.kt +++ /dev/null @@ -1,64 +0,0 @@ -package net.corda.testing.node.internal - -import com.typesafe.config.ConfigValueFactory -import net.corda.core.crypto.sha256 -import net.corda.core.internal.* -import net.corda.core.utilities.debug -import net.corda.core.utilities.loggerFor -import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey -import net.corda.testing.core.internal.JarSignatureTestUtils.signJar -import net.corda.testing.node.TestCordapp -import java.nio.file.Path -import java.nio.file.Paths -import java.util.* -import java.util.concurrent.ConcurrentHashMap - -object TestCordappDirectories { - private val logger = loggerFor() - - private val whitespace = "\\s".toRegex() - - private val testCordappsCache = ConcurrentHashMap() - - //TODO In future, we may wish to associate a signer attribute to TestCordapp interface itself, and trigger signing from that. - fun getJarDirectory(cordapp: TestCordapp, cordappsDirectory: Path = defaultCordappsDirectory, signJar: Boolean = false): Path { - cordapp as TestCordappImpl - return testCordappsCache.computeIfAbsent(cordapp) { - val configString = ConfigValueFactory.fromMap(cordapp.config).toConfig().root().render() - val filename = cordapp.run { - val uniqueScanString = if (packages.size == 1 && classes.isEmpty() && config.isEmpty()) { - packages.first() - } else { - "$packages$classes$configString".toByteArray().sha256().toString() - } - "${name}_${vendor}_${title}_${version}_${targetVersion}_$uniqueScanString".replace(whitespace, "-") - } - val cordappDir = cordappsDirectory / UUID.randomUUID().toString() - val configDir = (cordappDir / "config").createDirectories() - val jarFile = cordappDir / "$filename.jar" - cordapp.packageAsJar(jarFile) - if (signJar || cordapp.signJar) { - val testKeystore = "_teststore" - val alias = "Test" - val pwd = "secret!" - if (!(cordappsDirectory / testKeystore).exists() && (cordapp.keyStorePath == null)) { - cordappsDirectory.generateKey(alias, pwd, "O=Test Company Ltd,OU=Test,L=London,C=GB") - } - val keyStorePathToUse = cordapp.keyStorePath ?: cordappsDirectory - (keyStorePathToUse / testKeystore).copyTo(cordappDir / testKeystore) - val pk = cordappDir.signJar("$filename.jar", alias, pwd) - logger.debug { "Signed Jar: $cordappDir/$filename.jar with public key $pk" } - } else logger.debug { "Unsigned Jar: $cordappDir/$filename.jar" } - (configDir / "$filename.conf").writeText(configString) - logger.debug { "$cordapp packaged into $jarFile" } - cordappDir - } - } - - private val defaultCordappsDirectory: Path by lazy { - val cordappsDirectory = Paths.get("build").toAbsolutePath() / "generated-test-cordapps" / getTimestampAsDirectoryName() - logger.info("Initialising generated test CorDapps directory in $cordappsDirectory") - cordappsDirectory.deleteRecursively() - cordappsDirectory.createDirectories() - } -} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt index 171ac108d1c..68cee46fa6b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt @@ -1,35 +1,97 @@ package net.corda.testing.node.internal +import io.github.classgraph.ClassGraph +import net.corda.core.internal.* +import net.corda.core.utilities.contextLogger import net.corda.testing.node.TestCordapp +import org.apache.commons.lang.SystemUtils import java.nio.file.Path +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import kotlin.streams.toList -data class TestCordappImpl(override val name: String, - override val version: String, - override val vendor: String, - override val title: String, - override val targetVersion: Int, - override val config: Map, - override val packages: Set, - override val signJar: Boolean = false, - val keyStorePath: Path? = null, - val classes: Set> - ) : TestCordapp { +/** + * Implementation of the public [TestCordapp] API. + * + * As described in [TestCordapp.Factory.findCordapp], this represents a single CorDapp jar on the current classpath. The [scanPackage] may + * be for an external dependency to the project that's using this API, in which case that dependency jar is referenced as is. On the other hand, + * the [scanPackage] may reference a gradle CorDapp project on the local system. In this scenerio the project's "jar" task is executed to + * build the CorDapp jar. This allows us to inherit the CorDapp's MANIFEST information without having to do any extra processing. + */ +data class TestCordappImpl(override val scanPackage: String, override val config: Map) : TestCordappInternal { + override fun withoutMeta(): TestCordappImpl = copy(config = emptyMap()) - override fun withName(name: String): TestCordappImpl = copy(name = name) + override val jarFile: Path + get() { + val jars = TestCordappImpl.findJars(scanPackage) + when (jars.size) { + 0 -> throw IllegalArgumentException("Package $scanPackage does not exist") + 1 -> return jars.first() + else -> throw IllegalArgumentException("More than one jar found containing package $scanPackage: $jars") + } + } - override fun withVersion(version: String): TestCordappImpl = copy(version = version) + companion object { + private val packageToRootPaths = ConcurrentHashMap>() + private val projectRootToBuiltJar = ConcurrentHashMap() + private val log = contextLogger() - override fun withVendor(vendor: String): TestCordappImpl = copy(vendor = vendor) + fun findJars(scanPackage: String): Set { + val rootPaths = findRootPaths(scanPackage) + return if (rootPaths.all { it.toString().endsWith(".jar") }) { + // We don't need to do anything more if all the root paths are jars + rootPaths + } else { + // Otherwise we need to build those paths which are local projects and extract the built jar from them + rootPaths.mapTo(HashSet()) { if (it.toString().endsWith(".jar")) it else buildCordappJar(it) } + } + } - override fun withTitle(title: String): TestCordappImpl = copy(title = title) + private fun findRootPaths(scanPackage: String): Set { + return packageToRootPaths.computeIfAbsent(scanPackage) { + ClassGraph() + .whitelistPackages(scanPackage) + .scan() + .use { it.allResources } + .asSequence() + .map { it.classpathElementURL.toPath() } + .filterNot { it.toString().endsWith("-tests.jar") } + .map { if (it.toString().endsWith(".jar")) it else findProjectRoot(it) } + .toSet() + } + } - override fun withTargetVersion(targetVersion: Int): TestCordappImpl = copy(targetVersion = targetVersion) + private fun findProjectRoot(path: Path): Path { + var current = path + while (true) { + if ((current / "build.gradle").exists()) { + return current + } + current = current.parent + } + } - override fun withConfig(config: Map): TestCordappImpl = copy(config = config) + private fun buildCordappJar(projectRoot: Path): Path { + return projectRootToBuiltJar.computeIfAbsent(projectRoot) { + val gradlew = findGradlewDir(projectRoot) / (if (SystemUtils.IS_OS_WINDOWS) "gradlew.bat" else "gradlew") + val libs = projectRoot / "build" / "libs" + libs.deleteRecursively() + log.info("Generating CorDapp jar from local project in $projectRoot ...") + val exitCode = ProcessBuilder(gradlew.toString(), "jar").directory(projectRoot.toFile()).inheritIO().start().waitFor() + check(exitCode == 0) { "Unable to generate CorDapp jar from local project in $projectRoot ($exitCode)" } + val jars = libs.list { it.filter { it.toString().endsWith(".jar") }.toList() } + checkNotNull(jars.singleOrNull()) { "Expecting a single built jar in $libs, but instead got $jars" } + } + } - override fun signJar(keyStorePath: Path?): TestCordappImpl = copy(signJar = true, keyStorePath = keyStorePath) - - fun withClasses(vararg classes: Class<*>): TestCordappImpl { - return copy(classes = classes.filter { clazz -> packages.none { clazz.name.startsWith("$it.") } }.toSet()) + private fun findGradlewDir(path: Path): Path { + var current = path + while (true) { + if ((current / "gradlew").exists() && (current / "gradlew.bat").exists()) { + return current + } + current = current.parent + } + } } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt new file mode 100644 index 00000000000..16d5bbf22ca --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt @@ -0,0 +1,49 @@ +package net.corda.testing.node.internal + +import com.typesafe.config.ConfigValueFactory +import net.corda.core.internal.copyToDirectory +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div +import net.corda.core.internal.writeText +import net.corda.testing.node.TestCordapp +import java.nio.file.Path +import java.nio.file.StandardCopyOption.REPLACE_EXISTING + +/** + * Extends the public [TestCordapp] API with internal extensions for use within the testing framework and for internal testing of the platform. + * + * @property jarFile The jar file this CorDapp represents. Different CorDapps may point to the same file. + */ +interface TestCordappInternal : TestCordapp { + val jarFile: Path + + /** Return a copy of this TestCordappInternal but without any metadata, such as configs and signing information. */ + fun withoutMeta(): TestCordappInternal + + companion object { + fun installCordapps(baseDirectory: Path, nodeSpecificCordapps: Set, generalCordapps: Set) { + val nodeSpecificCordappsWithoutMeta = checkNoConflicts(nodeSpecificCordapps) + checkNoConflicts(generalCordapps) + + // Precedence is given to node-specific CorDapps + val allCordapps = nodeSpecificCordapps + generalCordapps.filter { it.withoutMeta() !in nodeSpecificCordappsWithoutMeta } + // Ignore any duplicate jar files + val jarToCordapp = allCordapps.associateBy { it.jarFile } + + val cordappsDir = baseDirectory / "cordapps" + val configDir = (cordappsDir / "config").createDirectories() + + jarToCordapp.forEach { jar, cordapp -> + jar.copyToDirectory(cordappsDir, REPLACE_EXISTING) + val configString = ConfigValueFactory.fromMap(cordapp.config).toConfig().root().render() + (configDir / "${jar.fileName.toString().removeSuffix(".jar")}.conf").writeText(configString) + } + } + + private fun checkNoConflicts(cordapps: Set): Set { + val cordappsWithoutMeta = cordapps.groupBy { it.withoutMeta() } + cordappsWithoutMeta.forEach { require(it.value.size == 1) { "Conflicting CorDapps specified: ${it.value}" } } + return cordappsWithoutMeta.keys + } + } +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt index f5eb5ebbf01..cde1c27a963 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt @@ -1,41 +1,40 @@ package net.corda.testing.node.internal -import io.github.classgraph.ClassGraph -import net.corda.core.internal.cordapp.* -import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME -import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION -import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_NAME -import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_VERSION -import net.corda.core.internal.cordapp.CordappImpl.Companion.TARGET_PLATFORM_VERSION -import net.corda.core.internal.outputStream import net.corda.testing.node.TestCordapp -import java.io.BufferedOutputStream -import java.nio.file.Path -import java.nio.file.attribute.FileTime -import java.time.Instant -import java.util.jar.Attributes -import java.util.jar.JarFile -import java.util.jar.JarOutputStream -import java.util.jar.Manifest -import java.util.zip.ZipEntry import kotlin.reflect.KClass +/** + * Reference to the finance CorDapp in this repo. The metadata is taken directly from finance/build.gradle, including the fact that the jar + * is signed. If you need an unsigned jar then use `cordappWithPackages("net.corda.finance")`. + */ +// TODO We can't use net.corda.finance as it's only containined by the finance:isolated module @JvmField -val FINANCE_CORDAPP: TestCordappImpl = cordappForPackages("net.corda.finance") +val FINANCE_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.internal") -/** Creates a [TestCordappImpl] for each package. */ -fun cordappsForPackages(vararg packageNames: String): List = cordappsForPackages(packageNames.asList()) +fun cordappsForPackages(vararg packageNames: String): Set = cordappsForPackages(packageNames.asList()) -fun cordappsForPackages(packageNames: Iterable): List { - return simplifyScanPackages(packageNames).map { cordappForPackages(it) } +fun cordappsForPackages(packageNames: Iterable): Set { + return simplifyScanPackages(packageNames).mapTo(HashSet()) { cordappWithPackages(it) } } -/** Creates a single [TestCordappImpl] containing all the given packges. */ -fun cordappForPackages(vararg packageNames: String): TestCordappImpl { - return TestCordapp.Factory.fromPackages(*packageNames) as TestCordappImpl -} +/** + * Create a *custom* CorDapp which contains all the classes and resoures located in the given packages. The CorDapp's metadata will be the + * default values as defined in the [CustomCordapp] c'tor. Use the `copy` to change them. This means the metadata will *not* be the one defined + * in the original CorDapp(s) that the given packages may represent. If this is not what you want then use [findCordapp] instead. + */ +fun cordappWithPackages(vararg packageNames: String): CustomCordapp = CustomCordapp(packages = simplifyScanPackages(packageNames.asList())) -fun cordappForClasses(vararg classes: Class<*>): TestCordappImpl = cordappForPackages().withClasses(*classes) +/** Create a *custom* CorDapp which contains just the given classes. */ +// TODO Rename to cordappWithClasses +fun cordappForClasses(vararg classes: Class<*>): CustomCordapp = CustomCordapp(packages = emptySet(), classes = classes.toSet()) + +/** + * Find the single CorDapp jar on the current classpath which contains the given package. This is a convenience method for + * [TestCordapp.Factory.findCordapp] but returns the internal [TestCordappImpl]. + */ +fun findCordapp(packageName: String, config: Map = emptyMap()): TestCordappImpl { + return TestCordapp.Factory.findCordapp(packageName, config) as TestCordappImpl +} fun getCallerClass(directCallerClass: KClass<*>): Class<*>? { val stackTrace = Throwable().stackTrace @@ -62,52 +61,3 @@ fun simplifyScanPackages(scanPackages: Iterable): Set { } } } - -fun TestCordappImpl.packageAsJar(file: Path) { - // Don't mention "classes" in the error message as that feature is only available internally - require(packages.isNotEmpty() || classes.isNotEmpty()) { "At least one package must be specified" } - - val scanResult = ClassGraph() - .whitelistPackages(*packages.toTypedArray()) - .whitelistClasses(*classes.map { it.name }.toTypedArray()) - .scan() - - scanResult.use { - val manifest = createTestManifest(name, title, version, vendor, targetVersion) - JarOutputStream(file.outputStream()).use { jos -> - val time = FileTime.from(Instant.EPOCH) - val manifestEntry = ZipEntry(JarFile.MANIFEST_NAME).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time) - jos.putNextEntry(manifestEntry) - manifest.write(BufferedOutputStream(jos)) - jos.closeEntry() - - // The same resource may be found in different locations (this will happen when running from gradle) so just - // pick the first one found. - scanResult.allResources.asMap().forEach { path, resourceList -> - val entry = ZipEntry(path).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time) - jos.putNextEntry(entry) - resourceList[0].open().use { it.copyTo(jos) } - jos.closeEntry() - } - } - } -} - -fun createTestManifest(name: String, title: String, version: String, vendor: String, targetVersion: Int): Manifest { - val manifest = Manifest() - - // Mandatory manifest attribute. If not present, all other entries are silently skipped. - manifest[Attributes.Name.MANIFEST_VERSION.toString()] = "1.0" - - manifest["Name"] = name - manifest[Attributes.Name.IMPLEMENTATION_TITLE] = title - manifest[Attributes.Name.IMPLEMENTATION_VERSION] = version - manifest[Attributes.Name.IMPLEMENTATION_VENDOR] = vendor - manifest[CORDAPP_CONTRACT_NAME] = name - manifest[CORDAPP_CONTRACT_VERSION] = version - manifest[CORDAPP_WORKFLOW_NAME] = name - manifest[CORDAPP_WORKFLOW_VERSION] = version - manifest[TARGET_PLATFORM_VERSION] = targetVersion.toString() - - return manifest -} diff --git a/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/CustomCordappTest.kt b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/CustomCordappTest.kt new file mode 100644 index 00000000000..1844ee45a0e --- /dev/null +++ b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/CustomCordappTest.kt @@ -0,0 +1,80 @@ +package net.corda.testing.node.internal + +import net.corda.core.internal.cordapp.CordappImpl +import net.corda.core.internal.cordapp.get +import net.corda.core.internal.inputStream +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import java.nio.file.Path +import java.util.jar.JarInputStream + +class CustomCordappTest { + @Rule + @JvmField + val tempFolder = TemporaryFolder() + + @Test + fun `packageAsJar writes out the CorDapp info into the manifest`() { + val cordapp = cordappWithPackages("net.corda.testing.node.internal").copy(targetPlatformVersion = 123, name = "TestCordappsUtilsTest") + val jarFile = packageAsJar(cordapp) + JarInputStream(jarFile.inputStream()).use { + assertThat(it.manifest[CordappImpl.TARGET_PLATFORM_VERSION]).isEqualTo("123") + assertThat(it.manifest[CordappImpl.CORDAPP_CONTRACT_NAME]).isEqualTo("TestCordappsUtilsTest") + assertThat(it.manifest[CordappImpl.CORDAPP_WORKFLOW_NAME]).isEqualTo("TestCordappsUtilsTest") + } + } + + @Test + fun `packageAsJar on leaf package`() { + val entries = packageAsJarThenReadBack(cordappWithPackages("net.corda.testing.node.internal")) + + assertThat(entries).contains( + "net/corda/testing/node/internal/TestCordappsUtilsTest.class", + "net/corda/testing/node/internal/resource.txt" // Make sure non-class resource files are also picked up + ).doesNotContain( + "net/corda/testing/node/MockNetworkTest.class" + ) + + // Make sure the MockNetworkTest class does actually exist to ensure the above is not a false-positive + assertThat(javaClass.classLoader.getResource("net/corda/testing/node/MockNetworkTest.class")).isNotNull() + } + + @Test + fun `packageAsJar on package with sub-packages`() { + val entries = packageAsJarThenReadBack(cordappWithPackages("net.corda.testing.node")) + + assertThat(entries).contains( + "net/corda/testing/node/internal/TestCordappsUtilsTest.class", + "net/corda/testing/node/internal/resource.txt", + "net/corda/testing/node/MockNetworkTest.class" + ) + } + + @Test + fun `packageAsJar on single class`() { + val entries = packageAsJarThenReadBack(cordappForClasses(InternalMockNetwork::class.java)) + + assertThat(entries).containsOnly("${InternalMockNetwork::class.java.name.replace('.', '/')}.class") + } + + private fun packageAsJar(cordapp: CustomCordapp): Path { + val jarFile = tempFolder.newFile().toPath() + cordapp.packageAsJar(jarFile) + return jarFile + } + + private fun packageAsJarThenReadBack(cordapp: CustomCordapp): List { + val jarFile = packageAsJar(cordapp) + val entries = ArrayList() + JarInputStream(jarFile.inputStream()).use { + while (true) { + val e = it.nextJarEntry ?: break + entries += e.name + it.closeEntry() + } + } + return entries + } +} diff --git a/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/TestCordappsUtilsTest.kt b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/TestCordappsUtilsTest.kt index efd0bfe981f..4aa06a0a411 100644 --- a/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/TestCordappsUtilsTest.kt +++ b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/TestCordappsUtilsTest.kt @@ -1,22 +1,9 @@ package net.corda.testing.node.internal -import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME -import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_NAME -import net.corda.core.internal.cordapp.CordappImpl.Companion.TARGET_PLATFORM_VERSION -import net.corda.core.internal.cordapp.get -import net.corda.core.internal.inputStream import org.assertj.core.api.Assertions.assertThat -import org.junit.Rule import org.junit.Test -import org.junit.rules.TemporaryFolder -import java.nio.file.Path -import java.util.jar.JarInputStream class TestCordappsUtilsTest { - @Rule - @JvmField - val tempFolder = TemporaryFolder() - @Test fun `test simplifyScanPackages`() { assertThat(simplifyScanPackages(emptyList())).isEmpty() @@ -28,70 +15,4 @@ class TestCordappsUtilsTest { assertThat(simplifyScanPackages(listOf("com.foobar", "com.foo.bar"))).containsExactlyInAnyOrder("com.foobar", "com.foo.bar") assertThat(simplifyScanPackages(listOf("com.foobar", "com.foo"))).containsExactlyInAnyOrder("com.foobar", "com.foo") } - - @Test - fun `packageAsJar writes out the CorDapp info into the manifest`() { - val cordapp = cordappForPackages("net.corda.testing.node.internal") - .withTargetVersion(123) - .withName("TestCordappsUtilsTest") - - val jarFile = packageAsJar(cordapp) - JarInputStream(jarFile.inputStream()).use { - assertThat(it.manifest[TARGET_PLATFORM_VERSION]).isEqualTo("123") - assertThat(it.manifest[CORDAPP_CONTRACT_NAME]).isEqualTo("TestCordappsUtilsTest") - assertThat(it.manifest[CORDAPP_WORKFLOW_NAME]).isEqualTo("TestCordappsUtilsTest") - } - } - - @Test - fun `packageAsJar on leaf package`() { - val entries = packageAsJarThenReadBack(cordappForPackages("net.corda.testing.node.internal")) - - assertThat(entries).contains( - "net/corda/testing/node/internal/TestCordappsUtilsTest.class", - "net/corda/testing/node/internal/resource.txt" // Make sure non-class resource files are also picked up - ).doesNotContain( - "net/corda/testing/node/MockNetworkTest.class" - ) - - // Make sure the MockNetworkTest class does actually exist to ensure the above is not a false-positive - assertThat(javaClass.classLoader.getResource("net/corda/testing/node/MockNetworkTest.class")).isNotNull() - } - - @Test - fun `packageAsJar on package with sub-packages`() { - val entries = packageAsJarThenReadBack(cordappForPackages("net.corda.testing.node")) - - assertThat(entries).contains( - "net/corda/testing/node/internal/TestCordappsUtilsTest.class", - "net/corda/testing/node/internal/resource.txt", - "net/corda/testing/node/MockNetworkTest.class" - ) - } - - @Test - fun `packageAsJar on single class`() { - val entries = packageAsJarThenReadBack(cordappForClasses(InternalMockNetwork::class.java)) - - assertThat(entries).containsOnly("${InternalMockNetwork::class.java.name.replace('.', '/')}.class") - } - - private fun packageAsJar(cordapp: TestCordappImpl): Path { - val jarFile = tempFolder.newFile().toPath() - cordapp.packageAsJar(jarFile) - return jarFile - } - - private fun packageAsJarThenReadBack(cordapp: TestCordappImpl): List { - val jarFile = packageAsJar(cordapp) - val entries = ArrayList() - JarInputStream(jarFile.inputStream()).use { - while (true) { - val e = it.nextJarEntry ?: break - entries += e.name - it.closeEntry() - } - } - return entries - } } diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index d24b3656ad8..0b7abd70165 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -83,12 +83,12 @@ class ExplorerSimulation(private val options: OptionSet) { val issuerGBP = startNode(NodeParameters( providedName = ukBankName, rpcUsers = listOf(manager), - additionalCordapps = listOf(FINANCE_CORDAPP.withConfig(mapOf("issuableCurrencies" to listOf("GBP")))) + additionalCordapps = listOf(FINANCE_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("GBP")))) )) val issuerUSD = startNode(NodeParameters( providedName = usaBankName, rpcUsers = listOf(manager), - additionalCordapps = listOf(FINANCE_CORDAPP.withConfig(mapOf("issuableCurrencies" to listOf("USD")))) + additionalCordapps = listOf(FINANCE_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("USD")))) )) notaryNode = defaultNotaryNode.get() From 4b068fdd88bf6026f987b5ab404131c28649706d Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 19 Dec 2018 14:59:27 +0000 Subject: [PATCH 2/4] Responding to review comments --- docs/source/upgrade-notes.rst | 2 +- .../main/kotlin/net/corda/testing/node/TestCordapp.kt | 11 +++++------ .../net/corda/testing/node/internal/CustomCordapp.kt | 4 +++- .../corda/testing/node/internal/TestCordappImpl.kt | 2 ++ .../corda/testing/node/internal/TestCordappsUtils.kt | 4 +--- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index 145278bb161..a7fb2de54ef 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -203,7 +203,7 @@ becomes:: ) You may need to use the new ``TestCordapp`` API when testing with the node driver or mock network, especially if you decide to stick with the -old ``FinalityFlow`` API. The previous way of pulling in CorDapps into your tests does not honour CorDapp versioning. +pre-Corda 4 ``FinalityFlow`` API. The previous way of pulling in CorDapps into your tests does not honour CorDapp versioning. Step 6. Security: refactor to avoid violating sealed packages ------------------------------------------------------------- diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt index f55b66810ae..c4f986e049c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt @@ -19,9 +19,12 @@ interface TestCordapp { /** The package used to find the CorDapp. This may not be the root package of the CorDapp. */ val scanPackage: String - /** Returns the config to be applied on this CorDapp, defaults to empty if not specified. */ + /** Returns the config for on this CorDapp, defaults to empty if not specified. */ val config: Map + /** Returns a copy of this [TestCordapp] but with the specified CorDapp config. */ + fun withConfig(config: Map): TestCordapp + class Factory { companion object { /** @@ -30,13 +33,9 @@ interface TestCordapp { * is also thrown if no CorDapp is found. * * @param scanPackage The package name used to find the CorDapp. This does not need to be the root package. - * @param config config to be applied with the CorDapp. */ @JvmStatic - @JvmOverloads - fun findCordapp(scanPackage: String, config: Map = emptyMap()): TestCordapp { - return TestCordappImpl(scanPackage = scanPackage, config = config) - } + fun findCordapp(scanPackage: String): TestCordapp = TestCordappImpl(scanPackage = scanPackage, config = emptyMap()) } } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt index 9af96128366..67cbb58f464 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt @@ -41,7 +41,9 @@ data class CustomCordapp( override val scanPackage: String get() = throw UnsupportedOperationException() - override fun withoutMeta(): CustomCordapp = copy(sign = null, config = emptyMap()) + override fun withConfig(config: Map): CustomCordapp = copy(config = config) + + override fun withoutMeta(): CustomCordapp = CustomCordapp(packages = packages, classes = classes) fun signed(keyStorePath: Path? = null): CustomCordapp = copy(sign = Sign(keyStorePath)) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt index 68cee46fa6b..4eb881bc59c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt @@ -19,6 +19,8 @@ import kotlin.streams.toList * build the CorDapp jar. This allows us to inherit the CorDapp's MANIFEST information without having to do any extra processing. */ data class TestCordappImpl(override val scanPackage: String, override val config: Map) : TestCordappInternal { + override fun withConfig(config: Map): TestCordappImpl = copy(config = config) + override fun withoutMeta(): TestCordappImpl = copy(config = emptyMap()) override val jarFile: Path diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt index cde1c27a963..1584beb6602 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt @@ -32,9 +32,7 @@ fun cordappForClasses(vararg classes: Class<*>): CustomCordapp = CustomCordapp(p * Find the single CorDapp jar on the current classpath which contains the given package. This is a convenience method for * [TestCordapp.Factory.findCordapp] but returns the internal [TestCordappImpl]. */ -fun findCordapp(packageName: String, config: Map = emptyMap()): TestCordappImpl { - return TestCordapp.Factory.findCordapp(packageName, config) as TestCordappImpl -} +fun findCordapp(scanPackage: String): TestCordappImpl = TestCordapp.Factory.findCordapp(scanPackage) as TestCordappImpl fun getCallerClass(directCallerClass: KClass<*>): Class<*>? { val stackTrace = Throwable().stackTrace From e3a9625c3d3078c51d01be57c2c1320541546e3e Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 19 Dec 2018 15:13:48 +0000 Subject: [PATCH 3/4] Review comments --- .../net/corda/node/CordappConstraintsTests.kt | 12 +++++++++--- .../corda/testing/node/internal/CustomCordapp.kt | 14 +++++++------- .../corda/testing/node/internal/TestCordappImpl.kt | 2 +- .../testing/node/internal/TestCordappInternal.kt | 6 +++--- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt b/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt index bb4a078c4ba..400aec463c2 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt @@ -26,7 +26,9 @@ import net.corda.testing.core.internal.SelfCleaningDir import net.corda.testing.driver.* import net.corda.testing.node.NotarySpec import net.corda.testing.node.User +import net.corda.testing.node.internal.CustomCordapp import net.corda.testing.node.internal.FINANCE_CORDAPP +import net.corda.testing.node.internal.TestCordappImpl import net.corda.testing.node.internal.cordappWithPackages import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -38,7 +40,11 @@ class CordappConstraintsTests { invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name), invokeRpc(CordaRPCOps::notaryIdentities), invokeRpc("vaultTrackByCriteria"))) - val UNSIGNED_FINANCE_CORDAPP = cordappWithPackages("net.corda.finance") + // This is referencing the real finance CorDapp, which includes the fact that it's signed + val SIGNED_FINANCE_CORDAPP: TestCordappImpl = FINANCE_CORDAPP + // On the other hand this is creating a separate *custom* CorDapp which contains the same contents as the finance module. It's + // unsigned which is the default of CustomCordapp. + val UNSIGNED_FINANCE_CORDAPP: CustomCordapp = cordappWithPackages("net.corda.finance") } @Test @@ -49,7 +55,7 @@ class CordappConstraintsTests { )) { val alice = startNode(NodeParameters( - additionalCordapps = listOf(FINANCE_CORDAPP), + additionalCordapps = listOf(SIGNED_FINANCE_CORDAPP), providedName = ALICE_NAME, rpcUsers = listOf(user) )).getOrThrow() @@ -184,7 +190,7 @@ class CordappConstraintsTests { @Test fun `issue and consume cash using signature constraints`() { driver(DriverParameters( - cordappsForAllNodes = listOf(FINANCE_CORDAPP), + cordappsForAllNodes = listOf(SIGNED_FINANCE_CORDAPP), networkParameters = testNetworkParameters(minimumPlatformVersion = 4), inMemoryDB = false )) { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt index 67cbb58f464..9832edbb604 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/CustomCordapp.kt @@ -30,7 +30,7 @@ data class CustomCordapp( val versionId: Int = 1, val targetPlatformVersion: Int = PLATFORM_VERSION, val classes: Set> = emptySet(), - val sign: Sign? = null, + val signingInfo: SigningInfo? = null, override val config: Map = emptyMap() ) : TestCordappInternal { init { @@ -43,9 +43,9 @@ data class CustomCordapp( override fun withConfig(config: Map): CustomCordapp = copy(config = config) - override fun withoutMeta(): CustomCordapp = CustomCordapp(packages = packages, classes = classes) + override fun withOnlyJarContents(): CustomCordapp = CustomCordapp(packages = packages, classes = classes) - fun signed(keyStorePath: Path? = null): CustomCordapp = copy(sign = Sign(keyStorePath)) + fun signed(keyStorePath: Path? = null): CustomCordapp = copy(signingInfo = SigningInfo(keyStorePath)) @VisibleForTesting internal fun packageAsJar(file: Path) { @@ -70,12 +70,12 @@ data class CustomCordapp( } private fun signJar(jarFile: Path) { - if (sign != null) { + if (signingInfo != null) { val testKeystore = "_teststore" val alias = "Test" val pwd = "secret!" - val keyStorePathToUse = if (sign.keyStorePath != null) { - sign.keyStorePath + val keyStorePathToUse = if (signingInfo.keyStorePath != null) { + signingInfo.keyStorePath } else { defaultJarSignerDirectory.createDirectories() if (!(defaultJarSignerDirectory / testKeystore).exists()) { @@ -109,7 +109,7 @@ data class CustomCordapp( return ZipEntry(name).setCreationTime(epochFileTime).setLastAccessTime(epochFileTime).setLastModifiedTime(epochFileTime) } - data class Sign(val keyStorePath: Path? = null) + data class SigningInfo(val keyStorePath: Path? = null) companion object { private val logger = contextLogger() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt index 4eb881bc59c..0f6e666e022 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt @@ -21,7 +21,7 @@ import kotlin.streams.toList data class TestCordappImpl(override val scanPackage: String, override val config: Map) : TestCordappInternal { override fun withConfig(config: Map): TestCordappImpl = copy(config = config) - override fun withoutMeta(): TestCordappImpl = copy(config = emptyMap()) + override fun withOnlyJarContents(): TestCordappImpl = copy(config = emptyMap()) override val jarFile: Path get() { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt index 16d5bbf22ca..13d7d94428e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappInternal.kt @@ -18,7 +18,7 @@ interface TestCordappInternal : TestCordapp { val jarFile: Path /** Return a copy of this TestCordappInternal but without any metadata, such as configs and signing information. */ - fun withoutMeta(): TestCordappInternal + fun withOnlyJarContents(): TestCordappInternal companion object { fun installCordapps(baseDirectory: Path, nodeSpecificCordapps: Set, generalCordapps: Set) { @@ -26,7 +26,7 @@ interface TestCordappInternal : TestCordapp { checkNoConflicts(generalCordapps) // Precedence is given to node-specific CorDapps - val allCordapps = nodeSpecificCordapps + generalCordapps.filter { it.withoutMeta() !in nodeSpecificCordappsWithoutMeta } + val allCordapps = nodeSpecificCordapps + generalCordapps.filter { it.withOnlyJarContents() !in nodeSpecificCordappsWithoutMeta } // Ignore any duplicate jar files val jarToCordapp = allCordapps.associateBy { it.jarFile } @@ -41,7 +41,7 @@ interface TestCordappInternal : TestCordapp { } private fun checkNoConflicts(cordapps: Set): Set { - val cordappsWithoutMeta = cordapps.groupBy { it.withoutMeta() } + val cordappsWithoutMeta = cordapps.groupBy { it.withOnlyJarContents() } cordappsWithoutMeta.forEach { require(it.value.size == 1) { "Conflicting CorDapps specified: ${it.value}" } } return cordappsWithoutMeta.keys } From e81f9ad565e6fff404d665e0d4976f6a88d55a3d Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Wed, 19 Dec 2018 20:22:49 +0000 Subject: [PATCH 4/4] Split FINANCE_CORDAPP const into two, matching the split to finance --- .idea/compiler.xml | 5 +++++ .../confidential/IdentitySyncFlowTests.kt | 4 ++-- .../test/JavaIntegrationTestingTutorial.java | 5 ++--- .../test/KotlinIntegrationTestingTutorial.kt | 4 ++-- .../net/corda/node/CordappConstraintsTests.kt | 15 +++---------- .../net/corda/node/NodePerformanceTests.kt | 4 ++-- .../registration/NodeRegistrationTest.kt | 4 ++-- .../node/services/FinalityHandlerTest.kt | 10 ++------- .../statemachine/FlowFrameworkTests.kt | 2 +- .../node/internal/TestCordappsUtils.kt | 22 +++++++++++++++---- .../net/corda/explorer/ExplorerSimulation.kt | 9 ++++---- 11 files changed, 44 insertions(+), 40 deletions(-) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ed8de752b59..4ecd10ebf54 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -39,6 +39,8 @@ + + @@ -260,6 +262,9 @@ + + + diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt index fa9879aeabc..5d49219c5e2 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -18,7 +18,7 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.CHARLIE_NAME import net.corda.testing.core.singleIdentity -import net.corda.testing.node.internal.FINANCE_CORDAPP +import net.corda.testing.node.internal.FINANCE_CORDAPPS import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.startFlow import org.junit.After @@ -35,7 +35,7 @@ class IdentitySyncFlowTests { fun before() { // We run this in parallel threads to help catch any race conditions that may exist. mockNet = InternalMockNetwork( - cordappsForAllNodes = listOf(FINANCE_CORDAPP), + cordappsForAllNodes = FINANCE_CORDAPPS, networkSendManuallyPumped = false, threadPerNode = true ) diff --git a/docs/source/example-code/src/integration-test/java/net/corda/docs/java/tutorial/test/JavaIntegrationTestingTutorial.java b/docs/source/example-code/src/integration-test/java/net/corda/docs/java/tutorial/test/JavaIntegrationTestingTutorial.java index 54a8d3aabeb..2446ccf6997 100644 --- a/docs/source/example-code/src/integration-test/java/net/corda/docs/java/tutorial/test/JavaIntegrationTestingTutorial.java +++ b/docs/source/example-code/src/integration-test/java/net/corda/docs/java/tutorial/test/JavaIntegrationTestingTutorial.java @@ -23,7 +23,6 @@ import java.util.List; import static java.util.Arrays.asList; -import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static net.corda.finance.Currencies.DOLLARS; import static net.corda.node.services.Permissions.invokeRpc; @@ -33,7 +32,7 @@ import static net.corda.testing.core.TestConstants.ALICE_NAME; import static net.corda.testing.core.TestConstants.BOB_NAME; import static net.corda.testing.driver.Driver.driver; -import static net.corda.testing.node.internal.TestCordappsUtilsKt.FINANCE_CORDAPP; +import static net.corda.testing.node.internal.TestCordappsUtilsKt.FINANCE_CORDAPPS; import static org.junit.Assert.assertEquals; public class JavaIntegrationTestingTutorial { @@ -42,7 +41,7 @@ public void aliceBobCashExchangeExample() { // START 1 driver(new DriverParameters() .withStartNodesInProcess(true) - .withCordappsForAllNodes(singleton(FINANCE_CORDAPP)), dsl -> { + .withCordappsForAllNodes(FINANCE_CORDAPPS), dsl -> { User aliceUser = new User("aliceUser", "testPassword1", new HashSet<>(asList( startFlow(CashIssueAndPaymentFlow.class), diff --git a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/kotlin/tutorial/test/KotlinIntegrationTestingTutorial.kt b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/kotlin/tutorial/test/KotlinIntegrationTestingTutorial.kt index 1a5babcc495..b18f6f402bd 100644 --- a/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/kotlin/tutorial/test/KotlinIntegrationTestingTutorial.kt +++ b/docs/source/example-code/src/integration-test/kotlin/net/corda/docs/kotlin/tutorial/test/KotlinIntegrationTestingTutorial.kt @@ -20,7 +20,7 @@ import net.corda.testing.core.* import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.driver import net.corda.testing.node.User -import net.corda.testing.node.internal.FINANCE_CORDAPP +import net.corda.testing.node.internal.FINANCE_CORDAPPS import org.junit.Test import rx.Observable import java.util.* @@ -30,7 +30,7 @@ class KotlinIntegrationTestingTutorial { @Test fun `alice bob cash exchange example`() { // START 1 - driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = listOf(FINANCE_CORDAPP))) { + driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = FINANCE_CORDAPPS)) { val aliceUser = User("aliceUser", "testPassword1", permissions = setOf( startFlow(), invokeRpc("vaultTrackBy") diff --git a/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt b/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt index 400aec463c2..59b72b7e92f 100644 --- a/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/CordappConstraintsTests.kt @@ -26,9 +26,6 @@ import net.corda.testing.core.internal.SelfCleaningDir import net.corda.testing.driver.* import net.corda.testing.node.NotarySpec import net.corda.testing.node.User -import net.corda.testing.node.internal.CustomCordapp -import net.corda.testing.node.internal.FINANCE_CORDAPP -import net.corda.testing.node.internal.TestCordappImpl import net.corda.testing.node.internal.cordappWithPackages import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -40,11 +37,8 @@ class CordappConstraintsTests { invokeRpc(CordaRPCOps::wellKnownPartyFromX500Name), invokeRpc(CordaRPCOps::notaryIdentities), invokeRpc("vaultTrackByCriteria"))) - // This is referencing the real finance CorDapp, which includes the fact that it's signed - val SIGNED_FINANCE_CORDAPP: TestCordappImpl = FINANCE_CORDAPP - // On the other hand this is creating a separate *custom* CorDapp which contains the same contents as the finance module. It's - // unsigned which is the default of CustomCordapp. - val UNSIGNED_FINANCE_CORDAPP: CustomCordapp = cordappWithPackages("net.corda.finance") + val UNSIGNED_FINANCE_CORDAPP = cordappWithPackages("net.corda.finance") + val SIGNED_FINANCE_CORDAPP = UNSIGNED_FINANCE_CORDAPP.signed() } @Test @@ -53,7 +47,6 @@ class CordappConstraintsTests { networkParameters = testNetworkParameters(minimumPlatformVersion = 4), inMemoryDB = false )) { - val alice = startNode(NodeParameters( additionalCordapps = listOf(SIGNED_FINANCE_CORDAPP), providedName = ALICE_NAME, @@ -80,7 +73,6 @@ class CordappConstraintsTests { networkParameters = testNetworkParameters(minimumPlatformVersion = 4), inMemoryDB = false )) { - println("Starting the node using unsigned contract jar ...") val alice = startNode(NodeParameters( providedName = ALICE_NAME, @@ -107,7 +99,7 @@ class CordappConstraintsTests { (baseDirectory(ALICE_NAME) / "cordapps").deleteRecursively() val restartedNode = startNode(NodeParameters( providedName = ALICE_NAME, - additionalCordapps = listOf(UNSIGNED_FINANCE_CORDAPP.signed()) + additionalCordapps = listOf(SIGNED_FINANCE_CORDAPP) )).getOrThrow() val statesAfterRestart = restartedNode.rpc.vaultQueryBy().states @@ -249,7 +241,6 @@ class CordappConstraintsTests { @Test fun `issue cash and transfer using hash to signature constraints migration`() { - // signing key setup val keyStoreDir = SelfCleaningDir() val packageOwnerKey = keyStoreDir.path.generateKey() diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt index 0cbc778c4e3..0c6ee6c0bf3 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -22,7 +22,7 @@ import net.corda.testing.driver.internal.internalServices import net.corda.testing.internal.performance.div import net.corda.testing.node.NotarySpec import net.corda.testing.node.User -import net.corda.testing.node.internal.FINANCE_CORDAPP +import net.corda.testing.node.internal.FINANCE_CORDAPPS import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector import net.corda.testing.node.internal.performance.startReporter @@ -97,7 +97,7 @@ class NodePerformanceTests { internalDriver( notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(user))), startNodesInProcess = true, - cordappsForAllNodes = listOf(FINANCE_CORDAPP) + cordappsForAllNodes = FINANCE_CORDAPPS ) { val notary = defaultNotaryNode.getOrThrow() as InProcess val metricRegistry = startReporter(this.shutdownManager, notary.internalServices.monitoringService.metrics) diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index d65715fd974..5849f74b401 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -20,7 +20,7 @@ import net.corda.testing.core.singleIdentity import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.node.NotarySpec -import net.corda.testing.node.internal.FINANCE_CORDAPP +import net.corda.testing.node.internal.FINANCE_CORDAPPS import net.corda.testing.node.internal.SharedCompatibilityZoneParams import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.network.NetworkMapServer @@ -88,7 +88,7 @@ class NodeRegistrationTest { portAllocation = portAllocation, compatibilityZone = compatibilityZone, notarySpecs = listOf(NotarySpec(notaryName)), - cordappsForAllNodes = listOf(FINANCE_CORDAPP), + cordappsForAllNodes = FINANCE_CORDAPPS, notaryCustomOverrides = mapOf("devMode" to false) ) { val (alice, genevieve) = listOf( diff --git a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt index 626055aa7d7..6bb4af859f2 100644 --- a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt @@ -37,10 +37,7 @@ class FinalityHandlerTest { fun `sent to flow hospital on error and attempted retry on node restart`() { // Setup a network where only Alice has the finance CorDapp and it sends a cash tx to Bob who doesn't have the // CorDapp. Bob's FinalityHandler will error when validating the tx. - val alice = mockNet.createNode(InternalMockNodeParameters( - legalName = ALICE_NAME, - additionalCordapps = setOf(FINANCE_CORDAPP) - )) + val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = FINANCE_CORDAPPS)) var bob = mockNet.createNode(InternalMockNodeParameters( legalName = BOB_NAME, @@ -67,10 +64,7 @@ class FinalityHandlerTest { @Test fun `disabled if there are no old CorDapps loaded`() { - val alice = mockNet.createNode(InternalMockNodeParameters( - legalName = ALICE_NAME, - additionalCordapps = setOf(FINANCE_CORDAPP) - )) + val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = FINANCE_CORDAPPS)) val bob = mockNet.createNode(InternalMockNodeParameters( legalName = BOB_NAME, diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index fb39bfe8738..57ab6419d8f 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -66,7 +66,7 @@ class FlowFrameworkTests { @Before fun setUpMockNet() { mockNet = InternalMockNetwork( - cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts") + FINANCE_CORDAPP, + cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts") + FINANCE_CORDAPPS, servicePeerAllocationStrategy = RoundRobin() ) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt index 1584beb6602..250c38fbaa2 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt @@ -4,12 +4,26 @@ import net.corda.testing.node.TestCordapp import kotlin.reflect.KClass /** - * Reference to the finance CorDapp in this repo. The metadata is taken directly from finance/build.gradle, including the fact that the jar - * is signed. If you need an unsigned jar then use `cordappWithPackages("net.corda.finance")`. + * Reference to the finance-contracts CorDapp in this repo. The metadata is taken directly from finance/contracts/build.gradle, including the + * fact that the jar is signed. If you need an unsigned jar then use `cordappWithPackages("net.corda.finance.contracts")`. + * + * You will probably need to use [FINANCE_CORDAPPS] instead to get access to the flows as well. */ -// TODO We can't use net.corda.finance as it's only containined by the finance:isolated module +// TODO We can't use net.corda.finance.contracts as finance-workflows contains the package net.corda.finance.contracts.asset.cash.selection. This should be renamed. @JvmField -val FINANCE_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.internal") +val FINANCE_CONTRACTS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.schemas") + +/** + * Reference to the finance-workflows CorDapp in this repo. The metadata is taken directly from finance/workflows/build.gradle, including the + * fact that the jar is signed. If you need an unsigned jar then use `cordappWithPackages("net.corda.finance.flows")`. + * + * You will probably need to use [FINANCE_CORDAPPS] instead to get access to the contract classes as well. + */ +@JvmField +val FINANCE_WORKFLOWS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.flows") + +@JvmField +val FINANCE_CORDAPPS: List = listOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP) fun cordappsForPackages(vararg packageNames: String): Set = cordappsForPackages(packageNames.asList()) diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index 0b7abd70165..76c4ab05236 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -31,7 +31,8 @@ import net.corda.testing.core.BOB_NAME import net.corda.testing.driver.* import net.corda.testing.driver.internal.incrementalPortAllocation import net.corda.testing.node.User -import net.corda.testing.node.internal.FINANCE_CORDAPP +import net.corda.testing.node.internal.FINANCE_CORDAPPS +import net.corda.testing.node.internal.FINANCE_WORKFLOWS_CORDAPP import java.time.Instant import java.util.* @@ -71,7 +72,7 @@ class ExplorerSimulation(private val options: OptionSet) { val portAllocation = incrementalPortAllocation(20000) driver(DriverParameters( portAllocation = portAllocation, - cordappsForAllNodes = listOf(FINANCE_CORDAPP), + cordappsForAllNodes = FINANCE_CORDAPPS, waitForAllNodesToFinish = true, jmxPolicy = JmxPolicy.defaultEnabled() )) { @@ -83,12 +84,12 @@ class ExplorerSimulation(private val options: OptionSet) { val issuerGBP = startNode(NodeParameters( providedName = ukBankName, rpcUsers = listOf(manager), - additionalCordapps = listOf(FINANCE_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("GBP")))) + additionalCordapps = listOf(FINANCE_WORKFLOWS_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("GBP")))) )) val issuerUSD = startNode(NodeParameters( providedName = usaBankName, rpcUsers = listOf(manager), - additionalCordapps = listOf(FINANCE_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("USD")))) + additionalCordapps = listOf(FINANCE_WORKFLOWS_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("USD")))) )) notaryNode = defaultNotaryNode.get()