diff --git a/.ci/api-current.txt b/.ci/api-current.txt index a1a4e27e36..cf5f0a1573 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -5825,6 +5825,8 @@ public interface net.corda.testing.driver.DriverDSL @NotNull public abstract net.corda.core.concurrent.CordaFuture startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String) @NotNull + public abstract net.corda.core.concurrent.CordaFuture startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String, java.util.Set, boolean) + @NotNull public abstract net.corda.core.concurrent.CordaFuture startWebserver(net.corda.testing.driver.NodeHandle) @NotNull public abstract net.corda.core.concurrent.CordaFuture startWebserver(net.corda.testing.driver.NodeHandle, String) @@ -5832,6 +5834,11 @@ public interface net.corda.testing.driver.DriverDSL public final class net.corda.testing.driver.DriverParameters extends java.lang.Object public () public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters) + public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map, boolean, boolean) + public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map, boolean, boolean, java.util.Set) + public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Set) + public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean) + public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean, java.util.Set) public final boolean component1() @NotNull public final java.util.List component10() @@ -6024,6 +6031,75 @@ public static final class net.corda.testing.driver.PortAllocation$Incremental ex public final java.util.concurrent.atomic.AtomicInteger getPortCounter() public int nextPort() ## +@DoNotImplement +public interface net.corda.testing.driver.TestCorDapp + @NotNull + public abstract java.util.Set> getClasses() + @NotNull + public abstract String getName() + @NotNull + public abstract java.util.Set getResources() + @NotNull + public abstract String getTitle() + @NotNull + public abstract String getVendor() + @NotNull + public abstract String getVersion() + @NotNull + public abstract java.nio.file.Path packageAsJarInDirectory(java.nio.file.Path) + public abstract void packageAsJarWithPath(java.nio.file.Path) +## +public static final class net.corda.testing.driver.TestCorDapp$Factory extends java.lang.Object + public () + @NotNull + public static final net.corda.testing.driver.TestCorDapp$Mutable create(String, String, String, String, java.util.Set>, kotlin.jvm.functions.Function2) + public static final net.corda.testing.driver.TestCorDapp$Factory$Companion Companion +## +public static final class net.corda.testing.driver.TestCorDapp$Factory$Companion extends java.lang.Object + @NotNull + public final net.corda.testing.driver.TestCorDapp$Mutable create(String, String, String, String, java.util.Set>, kotlin.jvm.functions.Function2) +## +@DoNotImplement +public static interface net.corda.testing.driver.TestCorDapp$Mutable extends net.corda.testing.driver.TestCorDapp + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable minus(Class) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackage(Package) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackage(String) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(Package, Package...) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(String, String...) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(java.util.Set) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable minusResource(String, java.net.URL) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable plus(Class) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackage(Package) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackage(String) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(Package, Package...) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(String, String...) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(java.util.Set) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable plusResource(String, java.net.URL) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable withClasses(java.util.Set>) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable withName(String) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable withTitle(String) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable withVendor(String) + @NotNull + public abstract net.corda.testing.driver.TestCorDapp$Mutable withVersion(String) +## public final class net.corda.testing.driver.VerifierType extends java.lang.Enum protected () public static net.corda.testing.driver.VerifierType valueOf(String) @@ -6167,6 +6243,8 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object @NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.List) @NotNull + public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Set) + @NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.testing.node.MockNodeParameters) @NotNull public final net.corda.testing.node.StartedMockNode createPartyNode(net.corda.core.identity.CordaX500Name) @@ -6183,6 +6261,8 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object @NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.List) @NotNull + public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Set) + @NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters) @NotNull public final java.util.List getCordappPackages() @@ -6263,6 +6343,7 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O public () public (Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1) public (Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.List) + public (Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Set) @Nullable public final Integer component1() @Nullable @@ -6275,6 +6356,8 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1) @NotNull public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.List) + @NotNull + public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Set) public boolean equals(Object) @NotNull public final kotlin.jvm.functions.Function1 getConfigOverrides() @@ -6297,14 +6380,14 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O ## public class net.corda.testing.node.MockServices extends java.lang.Object implements net.corda.core.node.ServiceHub public () - public (java.util.List) - public (java.util.List, net.corda.core.identity.CordaX500Name) - public (java.util.List, net.corda.core.identity.CordaX500Name, java.security.KeyPair, java.security.KeyPair...) - public (java.util.List, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService) - public (java.util.List, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService, java.security.KeyPair, java.security.KeyPair...) - public (java.util.List, net.corda.testing.core.TestIdentity, net.corda.core.node.services.IdentityService, net.corda.core.node.NetworkParameters, java.security.KeyPair...) - public (java.util.List, net.corda.testing.core.TestIdentity, net.corda.core.node.services.IdentityService, java.security.KeyPair...) - public (java.util.List, net.corda.testing.core.TestIdentity, java.security.KeyPair...) + public (Iterable) + public (Iterable, net.corda.core.identity.CordaX500Name) + public (Iterable, net.corda.core.identity.CordaX500Name, java.security.KeyPair, java.security.KeyPair...) + public (Iterable, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService) + public (Iterable, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService, java.security.KeyPair, java.security.KeyPair...) + public (Iterable, net.corda.testing.core.TestIdentity, net.corda.core.node.services.IdentityService, net.corda.core.node.NetworkParameters, java.security.KeyPair...) + public (Iterable, net.corda.testing.core.TestIdentity, net.corda.core.node.services.IdentityService, java.security.KeyPair...) + public (Iterable, net.corda.testing.core.TestIdentity, java.security.KeyPair...) public (net.corda.core.identity.CordaX500Name) public (net.corda.core.identity.CordaX500Name, java.security.KeyPair, java.security.KeyPair...) public (net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5aec9bd194..5fa84805af 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,9 +3,9 @@ # PR Checklist: -- [ ] Have you run the unit, integration and smoke tests as described here? https://docs.corda.net/head/testing.html -- [ ] If you added/changed public APIs, did you write/update the JavaDocs? -- [ ] If the changes are of interest to application developers, have you added them to the changelog, and potentially release notes? -- [ ] If you are contributing for the first time, please read the agreement in CONTRIBUTING.md now and add to this Pull Request that you agree to it. +- [ ] Have you run the unit, integration and smoke tests as described [here](https://docs.corda.net/head/testing.html)? +- [ ] If you added public APIs, did you write the JavaDocs? +- [ ] If the changes are of interest to application developers, have you added them to the [changelog](https://github.com/corda/corda/blob/master/docs/source/changelog.rst), and potentially the [release notes](https://github.com/corda/corda/blob/master/docs/source/release-notes.rst)? +- [ ] If you are contributing for the first time, please read the agreement in [CONTRIBUTING.md](https://github.com/corda/corda/blob/master/CONTRIBUTING.md) now and add a comment to this pull request stating that your PR is in accordance with the [Developer's Certificate of Origin](https://github.com/corda/corda/blob/master/CONTRIBUTING.md#developer-certificate-of-origin). Thanks for your code, it's appreciated! :) diff --git a/build.gradle b/build.gradle index 6524707b9f..3413392dd5 100644 --- a/build.gradle +++ b/build.gradle @@ -185,7 +185,7 @@ allprojects { jvmTarget = "1.8" javaParameters = true // Useful for reflection. freeCompilerArgs = ['-Xjvm-default=compatibility'] - allWarningsAsErrors = true + allWarningsAsErrors = project.hasProperty('compilation.allWarningsAsErrors') ? project.property('compilation.allWarningsAsErrors').toBoolean() : true } } @@ -201,7 +201,7 @@ allprojects { } tasks.withType(Test) { - failFast = true + failFast = project.hasProperty('tests.failFast') ? project.property('tests.failFast').toBoolean() : true // Prevent the project from creating temporary files outside of the build directory. systemProperty 'java.io.tmpdir', buildDir.absolutePath 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 c1bb1f7b9b..7a8a34549e 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -29,6 +29,7 @@ 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.InternalMockNetwork +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.startFlow import org.junit.After import org.junit.Before @@ -44,7 +45,7 @@ class IdentitySyncFlowTests { fun before() { // We run this in parallel threads to help catch any race conditions that may exist. mockNet = InternalMockNetwork( - cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"), + cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset", "net.corda.finance.schemas"), networkSendManuallyPumped = false, threadPerNode = true ) diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index 1dbacf7edf..08ee35f08d 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -137,7 +137,7 @@ class AttachmentTests : WithMockNet { // Makes a node that doesn't do sanity checking at load time. private fun makeBadNode(name: CordaX500Name) = mockNet.createNode( InternalMockNodeParameters(legalName = randomise(name)), - nodeFactory = { args -> + nodeFactory = { args, _ -> object : InternalMockNetwork.MockNode(args) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false } } diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index 7a6034cdf6..55d52554eb 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -30,6 +30,7 @@ import net.corda.testing.core.* import net.corda.testing.internal.rigorousMock import net.corda.testing.node.MockServices import net.corda.testing.node.internal.InternalMockNetwork +import net.corda.testing.node.internal.cordappsForPackages import org.junit.AfterClass import org.junit.Test @@ -37,7 +38,7 @@ class CollectSignaturesFlowTests : WithContracts { companion object { private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) private val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), miniCorp, rigorousMock()) - private val classMockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.core.flows")) + private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts", "net.corda.core.flows")) private const val MAGIC_NUMBER = 1337 diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowRPCTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowRPCTest.kt index a814375125..a6095f1162 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowRPCTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowRPCTest.kt @@ -30,10 +30,7 @@ import org.junit.Test class ContractUpgradeFlowRPCTest : WithContracts, WithFinality { companion object { - private val classMockNet = InternalMockNetwork(cordappPackages = listOf( - "net.corda.testing.contracts", - "net.corda.finance.contracts.asset", - "net.corda.core.flows")) + private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows")) @JvmStatic @AfterClass @@ -99,7 +96,7 @@ class ContractUpgradeFlowRPCTest : WithContracts, WithFinality { ).get() } - private fun RPCDriverDSL.createTestUser() = rpcTestUser.copy(permissions = setOf( + private fun createTestUser() = rpcTestUser.copy(permissions = setOf( startFlow(), startFlow>(), startFlow(), diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 72bc004a15..52cd6e8b88 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -35,6 +35,7 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity import net.corda.testing.node.internal.InternalMockNetwork +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.startFlow import org.junit.AfterClass import org.junit.Test @@ -42,10 +43,7 @@ import java.util.* class ContractUpgradeFlowTest : WithContracts, WithFinality { companion object { - private val classMockNet = InternalMockNetwork(cordappPackages = listOf( - "net.corda.testing.contracts", - "net.corda.finance.contracts.asset", - "net.corda.core.flows")) + private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows")) @JvmStatic @AfterClass 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 e90d05783b..6c56429518 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -24,13 +24,14 @@ import net.corda.finance.issuedBy import net.corda.node.internal.StartedNode import net.corda.testing.core.* import net.corda.testing.node.internal.InternalMockNetwork +import net.corda.testing.node.internal.cordappsForPackages import org.junit.AfterClass import org.junit.Test class FinalityFlowTests : WithFinality { companion object { private val CHARLIE = TestIdentity(CHARLIE_NAME, 90).party - private val classMockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset")) + private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset")) @JvmStatic @AfterClass diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index 4d64c3749e..0413abc452 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -171,7 +171,7 @@ class AttachmentSerializationTest { } private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String { - client = mockNet.restartNode(client) { args -> + client = mockNet.restartNode(client) { args, _ -> object : InternalMockNetwork.MockNode(args) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad } } diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index ddeb807677..8b19a55f83 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,6 +6,7 @@ release, see :doc:`upgrade-notes`. Unreleased ---------- +* Introduced ``TestCorDapp`` and utilities to support asymmetric setups for nodes through ``DriverDSL``, ``MockNetwork`` and ``MockServices``. * ``freeLocalHostAndPort``, ``freePort``, and ``getFreeLocalPorts`` from ``TestUtils`` have been deprecated as they don't provide any guarantee the returned port will be available which can result in flaky tests. Use ``PortAllocation.Incremental`` diff --git a/docs/source/cordapp-build-systems.rst b/docs/source/cordapp-build-systems.rst index 4bb47b7fb1..bdac392e33 100644 --- a/docs/source/cordapp-build-systems.rst +++ b/docs/source/cordapp-build-systems.rst @@ -158,9 +158,9 @@ Installing the CorDapp JAR .. note:: Before installing a CorDapp, you must create one or more nodes to install it on. For instructions, please see :doc:`generating-a-node`. -At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on -a node, the CorDapp JAR must be added to the ``/cordapps/`` folder, where ``node_dir`` is the folder in which -the node's JAR and configuration files are stored. +At start-up, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore, in order to install a CorDapp on +a node, the CorDapp JAR must be added to the ``/cordapps/`` folder (where ``node_dir`` is the folder in which +the node's JAR and configuration files are stored) and the node restarted. CorDapp configuration files --------------------------- diff --git a/docs/source/tutorial-cordapp.rst b/docs/source/tutorial-cordapp.rst index e87aadc607..76dfe7891c 100644 --- a/docs/source/tutorial-cordapp.rst +++ b/docs/source/tutorial-cordapp.rst @@ -317,7 +317,7 @@ Assuming all went well, you can view the newly-created IOU by accessing the vaul *Via web/example:* * PartyA: Navigate to http://localhost:10009/web/example and hit the "refresh" button -* PartyA: Navigate to http://localhost:10012/web/example and hit the "refresh" button +* PartyB: Navigate to http://localhost:10012/web/example and hit the "refresh" button The vault and web front-end of PartyC (at ``localhost:10015``) will not display any IOUs. This is because PartyC was not involved in this transaction. diff --git a/finance/src/test/kotlin/net/corda/finance/flows/CashSelectionTest.kt b/finance/src/test/kotlin/net/corda/finance/flows/CashSelectionTest.kt index 0f829fbee7..c771111cee 100644 --- a/finance/src/test/kotlin/net/corda/finance/flows/CashSelectionTest.kt +++ b/finance/src/test/kotlin/net/corda/finance/flows/CashSelectionTest.kt @@ -12,6 +12,7 @@ import net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection import net.corda.finance.contracts.getCashBalance import net.corda.finance.issuedBy import net.corda.testing.core.singleIdentity +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.startFlow import org.assertj.core.api.Assertions.assertThat @@ -19,7 +20,7 @@ import org.junit.After import org.junit.Test class CashSelectionTest { - private val mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.finance"), threadPerNode = true) + private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance"), threadPerNode = true) @After fun cleanUp() { 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 bb15c22a31..7353029572 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 @@ -21,18 +21,25 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl +import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity -import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.MockCordappConfigProvider +import net.corda.testing.internal.rigorousMock +import net.corda.testing.node.internal.cordappsForPackages +import net.corda.testing.node.internal.getTimestampAsDirectoryName +import net.corda.testing.node.internal.packageInDirectory import net.corda.testing.services.MockAttachmentStorage -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Rule import org.junit.Test +import java.nio.file.Path +import java.nio.file.Paths class AttachmentsClassLoaderStaticContractTests { private companion object { @@ -70,7 +77,7 @@ class AttachmentsClassLoaderStaticContractTests { } private val serviceHub = rigorousMock().also { - doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage(), testNetworkParameters().whitelistedContractImplementations)).whenever(it).cordappProvider + doReturn(CordappProviderImpl(cordappLoaderForPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage(), testNetworkParameters().whitelistedContractImplementations)).whenever(it).cordappProvider doReturn(testNetworkParameters()).whenever(it).networkParameters } @@ -92,4 +99,18 @@ class AttachmentsClassLoaderStaticContractTests { assertNotNull(contract) } + + private fun cordappLoaderForPackages(packages: Iterable): CordappLoader { + + val cordapps = cordappsForPackages(packages) + return testDirectory().let { directory -> + cordapps.packageInDirectory(directory) + JarScanningCordappLoader.fromDirectories(listOf(directory)) + } + } + + private fun testDirectory(): Path { + + return Paths.get("build", getTimestampAsDirectoryName()) + } } \ No newline at end of file diff --git a/node/build.gradle b/node/build.gradle index 390cba8d18..af48ebdfd1 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -144,6 +144,7 @@ dependencies { testCompile project(':test-utils') testCompile project(':client:jfx') testCompile project(':finance') + testCompile project(':finance:isolated') // sample test schemas testCompile project(path: ':finance', configuration: 'testArtifacts') diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt new file mode 100644 index 0000000000..3834f9efaf --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -0,0 +1,117 @@ +package net.corda.node + +import co.paralleluniverse.fibers.Suspendable +import com.google.common.base.Stopwatch +import net.corda.client.rpc.CordaRPCClient +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.StartableByRPC +import net.corda.core.internal.concurrent.transpose +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.minutes +import net.corda.finance.DOLLARS +import net.corda.finance.flows.CashIssueFlow +import net.corda.finance.flows.CashPaymentFlow +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.InProcess +import net.corda.testing.driver.driver +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.internalDriver +import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector +import net.corda.testing.node.internal.performance.startReporter +import net.corda.testing.node.internal.performance.startTightLoopInjector +import org.junit.Ignore +import org.junit.Test +import java.util.* +import java.util.concurrent.TimeUnit +import kotlin.streams.toList + +@Ignore("Run these locally") +class NodePerformanceTests { + @StartableByRPC + class EmptyFlow : FlowLogic() { + @Suspendable + override fun call() { + } + } + + private data class FlowMeasurementResult( + val flowPerSecond: Double, + val averageMs: Double + ) + + @Test + fun `empty flow per second`() { + driver(DriverParameters(startNodesInProcess = true)) { + val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow())))).get() + + CordaRPCClient(a.rpcAddress).use("A", "A") { connection -> + val timings = Collections.synchronizedList(ArrayList()) + val N = 10000 + val overallTiming = Stopwatch.createStarted().apply { + startTightLoopInjector( + parallelism = 8, + numberOfInjections = N, + queueBound = 50 + ) { + val timing = Stopwatch.createStarted().apply { + connection.proxy.startFlow(::EmptyFlow).returnValue.get() + }.stop().elapsed(TimeUnit.MICROSECONDS) + timings.add(timing) + } + }.stop().elapsed(TimeUnit.MICROSECONDS) + println( + FlowMeasurementResult( + flowPerSecond = N / (overallTiming * 0.000001), + averageMs = timings.average() * 0.001 + ) + ) + } + } + } + + @Test + fun `empty flow rate`() { + internalDriver(startNodesInProcess = true) { + val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow())))).get() + a as InProcess + val metricRegistry = startReporter(this.shutdownManager, a.internalServices.monitoringService.metrics) + CordaRPCClient(a.rpcAddress).use("A", "A") { connection -> + startPublishingFixedRateInjector(metricRegistry, 8, 5.minutes, 2000L / TimeUnit.SECONDS) { + connection.proxy.startFlow(::EmptyFlow).returnValue.get() + } + } + } + } + + @Test + fun `self pay rate`() { + val user = User("A", "A", setOf(startFlow(), startFlow())) + internalDriver( + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(user))), + startNodesInProcess = true, + cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance") + ) { + val notary = defaultNotaryNode.getOrThrow() as InProcess + val metricRegistry = startReporter(this.shutdownManager, notary.internalServices.monitoringService.metrics) + CordaRPCClient(notary.rpcAddress).use("A", "A") { connection -> + println("ISSUING") + val doneFutures = (1..100).toList().parallelStream().map { + connection.proxy.startFlow(::CashIssueFlow, 1.DOLLARS, OpaqueBytes.of(0), defaultNotaryIdentity).returnValue + }.toList() + doneFutures.transpose().get() + println("STARTING PAYMENT") + startPublishingFixedRateInjector(metricRegistry, 8, 5.minutes, 100L / TimeUnit.SECONDS) { + connection.proxy.startFlow(::CashPaymentFlow, 1.DOLLARS, defaultNotaryIdentity).returnValue.get() + } + } + } + } +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt b/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt new file mode 100644 index 0000000000..7034174181 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt @@ -0,0 +1,84 @@ +package net.corda.node.flows + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.internal.concurrent.transpose +import net.corda.core.internal.packageName +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.testing.core.singleIdentity +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.TestCorDapp +import net.corda.testing.driver.driver +import org.junit.Test +import kotlin.test.assertEquals + +class AsymmetricCorDappsTests { + + @StartableByRPC + @InitiatingFlow + class Ping(private val pongParty: Party, val times: Int) : FlowLogic() { + @Suspendable + override fun call() { + val pongSession = initiateFlow(pongParty) + pongSession.sendAndReceive(times) + for (i in 1..times) { + val j = pongSession.sendAndReceive(i).unwrap { it } + assertEquals(i, j) + } + } + } + + @InitiatedBy(Ping::class) + class Pong(private val pingSession: FlowSession) : FlowLogic() { + @Suspendable + override fun call() { + val times = pingSession.sendAndReceive(Unit).unwrap { it } + for (i in 1..times) { + val j = pingSession.sendAndReceive(i).unwrap { it } + assertEquals(i, j) + } + } + } + + @Test + fun noSharedCorDappsWithAsymmetricSpecificClasses() { + + driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = emptySet())) { + + val nodeA = startNode(additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java)))).getOrThrow() + val nodeB = startNode(additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java, Pong::class.java)))).getOrThrow() + nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow() + } + } + + @Test + fun sharedCorDappsWithAsymmetricSpecificClasses() { + + val resourceName = "cordapp.properties" + val cordappPropertiesResource = this::class.java.getResource(resourceName) + val sharedCordapp = TestCorDapp.Factory.create("shared", "1.0", classes = setOf(Ping::class.java)).plusResource("${AsymmetricCorDappsTests::class.java.packageName}.$resourceName", cordappPropertiesResource) + val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java)) + driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = setOf(sharedCordapp))) { + + val (nodeA, nodeB) = listOf(startNode(), startNode(additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow() + nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow() + } + } + + @Test + fun sharedCorDappsWithAsymmetricSpecificClassesInProcess() { + + val resourceName = "cordapp.properties" + val cordappPropertiesResource = this::class.java.getResource(resourceName) + val sharedCordapp = TestCorDapp.Factory.create("shared", "1.0", classes = setOf(Ping::class.java)).plusResource("${AsymmetricCorDappsTests::class.java.packageName}.$resourceName", cordappPropertiesResource) + val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java)) + driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = setOf(sharedCordapp))) { + + val (nodeA, nodeB) = listOf(startNode(), startNode(additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow() + nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow() + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..e4640b4ab2 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt @@ -0,0 +1,152 @@ +package net.corda.node.flows + +import net.corda.client.rpc.CordaRPCClient +import net.corda.core.internal.div +import net.corda.core.internal.list +import net.corda.core.internal.readLines +import net.corda.core.messaging.startTrackedFlow +import net.corda.core.utilities.getOrThrow +import net.corda.node.internal.CheckpointIncompatibleException +import net.corda.node.internal.NodeStartup +import net.corda.node.services.Permissions.Companion.invokeRpc +import net.corda.node.services.Permissions.Companion.startFlow +import net.corda.testMessage.Message +import net.corda.testMessage.MessageState +import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.BOB_NAME +import net.corda.testing.core.singleIdentity +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.TestCorDapp +import net.corda.testing.driver.driver +import net.corda.testing.node.User +import net.corda.testing.node.internal.ListenProcessDeathException +import net.test.cordapp.v1.SendMessageFlow +import org.junit.Test +import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull + +class FlowCheckpointVersionNodeStartupCheckTest { + companion object { + val message = Message("Hello world!") + val classes = setOf(net.corda.testMessage.MessageState::class.java, + net.corda.testMessage.MessageContract::class.java, + net.test.cordapp.v1.SendMessageFlow::class.java, + net.test.cordapp.v1.Record::class.java) + val user = User("mark", "dadada", setOf(startFlow(), invokeRpc("vaultQuery"), invokeRpc("vaultTrack"))) + } + + @Test + fun `restart node successfully with suspended flow`() { + + val cordapps = setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)) + + return driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, cordappsForAllNodes = cordapps)) { + { + val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME).getOrThrow() + val bob = startNode(rpcUsers = listOf(user), providedName = BOB_NAME).getOrThrow() + alice.stop() + CordaRPCClient(bob.rpcAddress).start(user.username, user.password).use { + val flowTracker = it.proxy.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress + //wait until Bob progresses as far as possible because alice node is off + flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single() + } + bob.stop() + }() + val result = { + //Bob will resume the flow + val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow() + startNode(providedName = BOB_NAME, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to false)).getOrThrow() + CordaRPCClient(alice.rpcAddress).start(user.username, user.password).use { + val page = it.proxy.vaultTrack(MessageState::class.java) + if (page.snapshot.states.isNotEmpty()) { + page.snapshot.states.first() + } else { + val r = page.updates.timeout(5, TimeUnit.SECONDS).take(1).toBlocking().single() + if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first() + } + } + }() + assertNotNull(result) + assertEquals(message, result.state.data.message) + } + } + + private fun assertNodeRestartFailure( + cordapps: Set?, + cordappsVersionAtStartup: Set, + cordappsVersionAtRestart: Set, + reuseAdditionalCordappsAtRestart: Boolean, + assertNodeLogs: String + ) { + + return driver(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 flow in Bob's node + cordappsForAllNodes = cordapps) + ) { + val bobLogFolder = { + val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, additionalCordapps = cordappsVersionAtStartup).getOrThrow() + val bob = startNode(rpcUsers = listOf(user), providedName = BOB_NAME, additionalCordapps = cordappsVersionAtStartup).getOrThrow() + alice.stop() + CordaRPCClient(bob.rpcAddress).start(user.username, user.password).use { + val flowTracker = it.proxy.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress + // wait until Bob progresses as far as possible because Alice node is offline + flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single() + } + val logFolder = bob.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME + // SendMessageFlow suspends in Bob node + bob.stop() + logFolder + }() + + startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false), + additionalCordapps = cordappsVersionAtRestart, regenerateCordappsOnStart = !reuseAdditionalCordappsAtRestart).getOrThrow() + + assertFailsWith(ListenProcessDeathException::class) { + startNode(providedName = BOB_NAME, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to false), + additionalCordapps = cordappsVersionAtRestart, regenerateCordappsOnStart = !reuseAdditionalCordappsAtRestart).getOrThrow() + } + + val logFile = bobLogFolder.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() } + val numberOfNodesThatLogged = logFile.readLines { it.filter { assertNodeLogs in it }.count() } + assertEquals(1, numberOfNodesThatLogged) + } + } + + @Test + fun `restart nodes with incompatible version of suspended flow due to different jar name`() { + + assertNodeRestartFailure( + emptySet(), + setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)), + setOf(TestCorDapp.Factory.create("testJar2", "1.0", classes = classes)), + false, + CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message) + } + + @Test + fun `restart nodes with incompatible version of suspended flow`() { + + assertNodeRestartFailure( + emptySet(), + setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)), + setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes + net.test.cordapp.v1.SendMessageFlow::class.java)), + false, + // the part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException + "that is incompatible with the current installed version of") + } + + @Test + fun `restart nodes with incompatible version of suspended flow due to different timestamps only`() { + + assertNodeRestartFailure( + emptySet(), + setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)), + setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)), + false, + // the part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException + "that is incompatible with the current installed version of") + } +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 67367d12c8..bc0665638a 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -58,7 +58,7 @@ class AttachmentLoadingTests : IntegrationTest() { @JvmField val testSerialization = SerializationEnvironmentRule() private val attachments = MockAttachmentStorage() - private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments, testNetworkParameters().whitelistedContractImplementations) + private val provider = CordappProviderImpl(JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments, testNetworkParameters().whitelistedContractImplementations) private val cordapp get() = provider.cordapps.first() private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!! private val appContext get() = provider.getAppContext(cordapp) @@ -86,13 +86,6 @@ class AttachmentLoadingTests : IntegrationTest() { startNode(providedName = bankBName) ).transpose().getOrThrow() } - - private fun DriverDSL.installIsolatedCordappTo(nodeName: CordaX500Name) { - // Copy the app jar to the first node. The second won't have it. - val path = (baseDirectory(nodeName) / "cordapps").createDirectories() / "isolated.jar" - logger.info("Installing isolated jar to $path") - isolatedJAR.openStream().use { it.copyTo(path) } - } } private val services = object : ServicesForResolution { @@ -125,9 +118,9 @@ class AttachmentLoadingTests : IntegrationTest() { @Test fun `test that attachments retrieved over the network are not used for code`() { withoutTestSerialization { - driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) { - installIsolatedCordappTo(bankAName) - val (bankA, bankB) = createTwoNodes() + driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList(), cordappsForAllNodes = emptySet())) { + val bankA = startNode(providedName = bankAName, additionalCordapps = cordappsForPackages("net.corda.finance.contracts.isolated")).getOrThrow() + val bankB = startNode(providedName = bankBName, additionalCordapps = cordappsForPackages("net.corda.finance.contracts.isolated")).getOrThrow() assertFailsWith("Party C=CH,L=Zurich,O=BankB rejected session request: Don't know net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") { bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow() } @@ -135,16 +128,4 @@ class AttachmentLoadingTests : IntegrationTest() { Unit } } - - @Test - fun `tests that if the attachment is loaded on both sides already that a flow can run`() { - withoutTestSerialization { - driver { - installIsolatedCordappTo(bankAName) - installIsolatedCordappTo(bankBName) - val (bankA, bankB) = createTwoNodes() - bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow() - } - } - } -} +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index a774f03c2a..92d6494f37 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -16,11 +16,7 @@ import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow -import net.corda.core.crypto.CompositeKey -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.TransactionSignature -import net.corda.core.crypto.isFulfilledBy -import net.corda.core.crypto.sha256 +import net.corda.core.crypto.* import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryException import net.corda.core.flows.NotaryFlow @@ -49,6 +45,7 @@ import net.corda.testing.core.singleIdentity import net.corda.testing.node.TestClock import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork.MockNode import net.corda.testing.node.internal.InternalMockNodeParameters @@ -63,18 +60,8 @@ import java.nio.file.Paths import java.time.Duration import java.time.Instant import java.util.concurrent.ExecutionException -import kotlin.collections.List import kotlin.collections.component1 import kotlin.collections.component2 -import kotlin.collections.distinct -import kotlin.collections.forEach -import kotlin.collections.last -import kotlin.collections.listOf -import kotlin.collections.map -import kotlin.collections.mapIndexedNotNull -import kotlin.collections.plus -import kotlin.collections.single -import kotlin.collections.zip import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertTrue @@ -94,7 +81,7 @@ class BFTNotaryServiceTests { @JvmStatic fun before() { IntegrationTest.globalSetUp() //Enterprise only - remote db setup - mockNet = InternalMockNetwork(listOf("net.corda.testing.contracts")) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts")) val clusterSize = minClusterSize(1) val started = startBftClusterAndNode(clusterSize, mockNet) notary = started.first diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTSMaRtTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTSMaRtTests.kt index 5fed0bea75..bbafa50575 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTSMaRtTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTSMaRtTests.kt @@ -14,6 +14,7 @@ import net.corda.testing.core.dummyCommand import net.corda.testing.core.singleIdentity import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork.MockNode import net.corda.testing.node.internal.startFlow @@ -34,7 +35,7 @@ class BFTSMaRtTests : IntegrationTest() { @Before fun before() { - mockNet = InternalMockNetwork(listOf("net.corda.testing.contracts")) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts")) } @After 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 4f08356c64..71b6331dcc 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 @@ -32,6 +32,7 @@ import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.node.NotarySpec +import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages import net.corda.testing.node.internal.SharedCompatibilityZoneParams import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.network.NetworkMapServer @@ -100,7 +101,7 @@ class NodeRegistrationTest : IntegrationTest() { compatibilityZone = compatibilityZone, initialiseSerialization = false, notarySpecs = listOf(NotarySpec(notaryName)), - extraCordappPackagesToScan = listOf("net.corda.finance"), + cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance"), notaryCustomOverrides = mapOf("devMode" to false) ) { val (alice, genevieve) = listOf( diff --git a/node/src/integration-test/kotlin/net/test/cordapp/v1/FlowCheckpointCordapp.kt b/node/src/integration-test/kotlin/net/test/cordapp/v1/FlowCheckpointCordapp.kt new file mode 100644 index 0000000000..75ca920b44 --- /dev/null +++ b/node/src/integration-test/kotlin/net/test/cordapp/v1/FlowCheckpointCordapp.kt @@ -0,0 +1,68 @@ +package net.test.cordapp.v1 + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.Command +import net.corda.core.contracts.StateAndContract +import net.corda.core.flows.* +import net.corda.core.identity.Party +import net.corda.core.node.StatesToRecord +import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.ProgressTracker +import net.corda.testMessage.MESSAGE_CONTRACT_PROGRAM_ID +import net.corda.testMessage.Message +import net.corda.testMessage.MessageContract +import net.corda.testMessage.MessageState + +@StartableByRPC +@InitiatingFlow +class SendMessageFlow(private val message: Message, private val notary: Party, private val reciepent: Party? = null) : FlowLogic() { + companion object { + object GENERATING_TRANSACTION : ProgressTracker.Step("Generating transaction based on the message.") + object VERIFYING_TRANSACTION : ProgressTracker.Step("Verifying contract constraints.") + object SIGNING_TRANSACTION : ProgressTracker.Step("Signing transaction with our private key.") + object FINALISING_TRANSACTION : ProgressTracker.Step("Obtaining notary signature and recording transaction.") { + override fun childProgressTracker() = FinalityFlow.tracker() + } + + fun tracker() = ProgressTracker(GENERATING_TRANSACTION, VERIFYING_TRANSACTION, SIGNING_TRANSACTION, FINALISING_TRANSACTION) + } + + override val progressTracker = tracker() + + @Suspendable + override fun call(): SignedTransaction { + progressTracker.currentStep = GENERATING_TRANSACTION + + val messageState = MessageState(message = message, by = ourIdentity) + val txCommand = Command(MessageContract.Commands.Send(), messageState.participants.map { it.owningKey }) + val txBuilder = TransactionBuilder(notary).withItems(StateAndContract(messageState, MESSAGE_CONTRACT_PROGRAM_ID), txCommand) + + progressTracker.currentStep = VERIFYING_TRANSACTION + txBuilder.toWireTransaction(serviceHub).toLedgerTransaction(serviceHub).verify() + + progressTracker.currentStep = SIGNING_TRANSACTION + val signedTx = serviceHub.signInitialTransaction(txBuilder) + + progressTracker.currentStep = FINALISING_TRANSACTION + + if (reciepent != null) { + val session = initiateFlow(reciepent) + subFlow(SendTransactionFlow(session, signedTx)) + return subFlow(FinalityFlow(signedTx, setOf(reciepent), FINALISING_TRANSACTION.childProgressTracker())) + } else { + return subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker())) + } + } +} + + +@InitiatedBy(SendMessageFlow::class) +class Record(private val session: FlowSession) : FlowLogic() { + + @Suspendable + override fun call() { + val tx = subFlow(ReceiveTransactionFlow(session, statesToRecord = StatesToRecord.ALL_VISIBLE)) + serviceHub.addSignature(tx) + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/cordapp/CordappLoader.kt new file mode 100644 index 0000000000..8f472c8922 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/cordapp/CordappLoader.kt @@ -0,0 +1,32 @@ +package net.corda.node.cordapp + +import net.corda.core.cordapp.Cordapp +import net.corda.core.flows.FlowLogic +import net.corda.core.schemas.MappedSchema + +/** + * Handles loading [Cordapp]s. + */ +interface CordappLoader { + + /** + * Returns all [Cordapp]s found. + */ + val cordapps: List + + /** + * Returns a [ClassLoader] containing all types from all [Cordapp]s. + */ + val appClassLoader: ClassLoader + + /** + * Returns a map between flow class and owning [Cordapp]. + * The mappings are unique, and the node will not start otherwise. + */ + val flowCordappMap: Map>, Cordapp> + + /** + * Returns all [MappedSchema] found inside the [Cordapp]s. + */ + val cordappSchemas: Set +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index d5d504a618..f50c4dfbd8 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -67,10 +67,11 @@ import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.minutes import net.corda.node.CordaClock import net.corda.node.VersionInfo +import net.corda.node.cordapp.CordappLoader import net.corda.node.internal.CheckpointVerifier.verifyCheckpointsCompatible import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.internal.cordapp.CordappConfigFileProvider -import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.CordappProviderInternal import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy diff --git a/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt b/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt index 7044e40dc6..7204f04b5a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt @@ -22,7 +22,6 @@ import net.corda.core.internal.Emoji import net.corda.core.internal.concurrent.thenMatch import net.corda.core.utilities.loggerFor import net.corda.node.VersionInfo -import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.RelayConfiguration import net.corda.node.services.statemachine.MultiThreadedStateMachineExecutor @@ -38,9 +37,8 @@ import java.util.concurrent.TimeUnit open class EnterpriseNode(configuration: NodeConfiguration, versionInfo: VersionInfo, - initialiseSerialization: Boolean = true, - cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo) -) : Node(configuration, versionInfo, initialiseSerialization, cordappLoader) { + initialiseSerialization: Boolean = true +) : Node(configuration, versionInfo, initialiseSerialization) { companion object { private val logger by lazy { loggerFor() } diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 0ab9a6bfa7..0c88f04c59 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -35,8 +35,9 @@ import net.corda.node.SimpleClock import net.corda.node.VersionInfo import net.corda.node.internal.artemis.ArtemisBroker import net.corda.node.internal.artemis.BrokerAddresses -import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.core.internal.errors.AddressBindingException +import net.corda.node.cordapp.CordappLoader import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser import net.corda.node.serialization.amqp.AMQPServerSerializationScheme @@ -124,15 +125,11 @@ open class Node(configuration: NodeConfiguration, } private val sameVmNodeCounter = AtomicInteger() - const val scanPackagesSystemProperty = "net.corda.node.cordapp.scan.packages" - const val scanPackagesSeparator = "," @JvmStatic protected fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader { - return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages -> - CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator), versionInfo) - } ?: CordappLoader.createDefault(configuration.baseDirectory, versionInfo) + return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories, versionInfo) } // TODO: make this configurable. const val MAX_RPC_MESSAGE_SIZE = 10485760 diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt index 6f2163ff85..ce1f3b04a8 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt @@ -23,6 +23,7 @@ import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.loggerFor +import net.corda.node.cordapp.CordappLoader import java.net.URL import java.util.concurrent.ConcurrentHashMap diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt similarity index 51% rename from node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt rename to node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt index d09bbecd07..772dd2c1ae 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt @@ -1,16 +1,5 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - package net.corda.node.internal.cordapp -import com.github.benmanes.caffeine.cache.Caffeine import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult import net.corda.core.cordapp.Cordapp @@ -18,50 +7,25 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.flows.* import net.corda.core.internal.* -import net.corda.core.flows.ContractUpgradeFlow -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.SchedulableFlow -import net.corda.core.flows.StartableByRPC -import net.corda.core.flows.StartableByService -import net.corda.core.internal.VisibleForTesting -import net.corda.core.internal.copyTo import net.corda.core.internal.cordapp.CordappImpl -import net.corda.core.internal.createDirectories -import net.corda.core.internal.div -import net.corda.core.internal.exists -import net.corda.core.internal.isRegularFile -import net.corda.core.internal.list -import net.corda.core.internal.objectOrNewInstance -import net.corda.core.internal.outputStream -import net.corda.core.internal.toPath -import net.corda.core.internal.toTypedArray -import net.corda.core.internal.walk import net.corda.core.node.services.CordaService +import net.corda.node.VersionInfo import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import net.corda.core.utilities.contextLogger -import net.corda.node.VersionInfo +import net.corda.node.cordapp.CordappLoader import net.corda.node.internal.classloading.requireAnnotation -import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.coreContractClasses import net.corda.serialization.internal.DefaultWhitelist import org.apache.commons.collections4.map.LRUMap import java.lang.reflect.Modifier -import java.net.JarURLConnection import java.net.URL import java.net.URLClassLoader 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.JarInputStream -import java.util.jar.JarOutputStream -import java.util.zip.ZipEntry import kotlin.reflect.KClass import kotlin.streams.toList @@ -70,20 +34,11 @@ import kotlin.streams.toList * * @property cordappJarPaths The classpath of cordapp JARs */ -class CordappLoader private constructor(private val cordappJarPaths: List, versionInfo: VersionInfo) { - val cordapps: List by lazy { loadCordapps() + coreCordapp } - val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader) +class JarScanningCordappLoader private constructor(private val cordappJarPaths: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN) : CordappLoaderTemplate() { - // Create a map of the CorDapps that provide a Flow. If a flow is not in this map it is a Core flow. - // It also checks that there is only one CorDapp containing that flow class - val flowCordappMap: Map>, Cordapp> by lazy { - cordapps.flatMap { corDapp -> corDapp.allFlows.map { flow -> flow to corDapp } } - .groupBy { it.first } - .mapValues { - if(it.value.size > 1) { throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow ${it.value.first().first.name}: [ ${it.value.joinToString { it.second.name }} ].") } - it.value.single().second - } - } + override val cordapps: List by lazy { loadCordapps() + coreCordapp } + + override val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader) init { if (cordappJarPaths.isEmpty()) { @@ -93,7 +48,47 @@ class CordappLoader private constructor(private val cordappJarPaths: List get() = cordapps.flatMap { it.customSchemas }.toSet() + companion object { + private val logger = contextLogger() + + /** + * Creates a CordappLoader from multiple directories. + * + * @param corDappDirectories Directories used to scan for CorDapp JARs. + */ + fun fromDirectories(corDappDirectories: Iterable, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader { + + logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}") + return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }, versionInfo) + } + + /** + * Creates a CordappLoader loader out of a list of JAR URLs. + * + * @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection. + */ + fun fromJarUrls(scanJars: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN) = JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo) + + private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName) + + private fun jarUrlsInDirectory(directory: Path): List { + + return if (!directory.exists()) { + emptyList() + } else { + directory.list { paths -> + // `toFile()` can't be used here... + paths.filter { it.toString().endsWith(".jar") }.map { it.toUri().toURL() }.toList() + } + } + } + + /** A list of the core RPC flows present in Corda */ + private val coreRPCFlows = listOf( + ContractUpgradeFlow.Initiate::class.java, + ContractUpgradeFlow.Authorise::class.java, + ContractUpgradeFlow.Deauthorise::class.java) + } /** A Cordapp representing the core package which is not scanned automatically. */ @VisibleForTesting @@ -107,165 +102,36 @@ class CordappLoader private constructor(private val cordappJarPaths: List, CordappLoader>() - private val generatedCordapps = ConcurrentHashMap() - - private fun simplifyScanPackages(scanPackages: List): List { - return scanPackages.sorted().fold(emptyList()) { listSoFar, packageName -> - when { - listSoFar.isEmpty() -> listOf(packageName) - packageName.startsWith(listSoFar.last()) -> listSoFar // Squash ["com.foo", "com.foo.bar"] into just ["com.foo"] - else -> listSoFar + packageName - } - } - } - - /** - * Create a dev mode CordappLoader for test environments that creates and loads cordapps from the classpath - * and cordapps directory. This is intended mostly for use by the driver. - * - * @param testPackages See [createWithTestPackages] - */ - @VisibleForTesting - fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader { - if (!configuration.devMode) { - logger.warn("Package scanning should only occur in dev mode!") - } - val urls = getNodeCordappURLs(configuration.baseDirectory) + simplifyScanPackages(testPackages).flatMap(this::getPackageURLs) - return cordappLoadersCache.asMap().computeIfAbsent(urls) { values -> CordappLoader(values, versionInfo) } - } - - /** - * Create a dev mode CordappLoader for test environments that creates and loads cordapps from the classpath. - * This is intended for use in unit and integration tests. - * - * @param testPackages List of package names that contain CorDapp classes that can be automatically turned into - * CorDapps. - */ - @VisibleForTesting - fun createWithTestPackages(testPackages: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader { - val urls = simplifyScanPackages(testPackages).flatMap(this::getPackageURLs) - return cordappLoadersCache.asMap().computeIfAbsent(urls) { values -> CordappLoader(values, versionInfo) } - } - - /** - * Creates a dev mode CordappLoader intended only to be used in test environments - * - * @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection - */ - @VisibleForTesting - fun createDevMode(scanJars: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN) = CordappLoader(scanJars.map { RestrictedURL(it, null) }, versionInfo) - - private fun getPackageURLs(scanPackage: String): List { - val resource = scanPackage.replace('.', '/') - return this::class.java.classLoader.getResources(resource) - .asSequence() - // This is to only scan classes from test folders. - .filter { url -> - !url.toString().contains("main/$resource") || listOf("net.corda.core", "net.corda.node", "net.corda.finance").none { scanPackage.startsWith(it) } - } - .map { url -> - if (url.protocol == "jar") { - // When running tests from gradle this may be a corda module jar, so restrict to scanPackage: - RestrictedURL((url.openConnection() as JarURLConnection).jarFileURL, scanPackage) - } else { - // No need to restrict as createDevCordappJar has already done that: - RestrictedURL(createDevCordappJar(scanPackage, url, resource).toUri().toURL(), null) - } - } - .toList() - } - - /** Takes a package of classes and creates a JAR from them - only use in tests. */ - private fun createDevCordappJar(scanPackage: String, url: URL, resource: String): Path { - return generatedCordapps.computeIfAbsent(url) { - // TODO Using the driver in out-of-process mode causes each node to have their own copy of the same dev CorDapps - val cordappDir = (Paths.get("build") / "tmp" / "generated-test-cordapps").createDirectories() - val uuid = UUID.randomUUID() - val cordappJar = cordappDir / "$scanPackage-$uuid.jar" - logger.info("Generating a test-only CorDapp of classes discovered for package $scanPackage in $url: $cordappJar") - val manifest = createTestManifest(resource, scanPackage, uuid) - val scanDir = url.toPath() - JarOutputStream(cordappJar.outputStream(), manifest).use { jos -> - scanDir.walk { - it.forEach { - val entryPath = "$resource/${scanDir.relativize(it).toString().replace('\\', '/')}" - val time = FileTime.from(Instant.EPOCH) - val entry = ZipEntry(entryPath).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time) - jos.putNextEntry(entry) - if (it.isRegularFile()) { - it.copyTo(jos) - } - jos.closeEntry() - } - } - } - cordappJar - } - } - - private fun getNodeCordappURLs(baseDir: Path): List { - val cordappsDir = baseDir / CORDAPPS_DIR_NAME - return if (!cordappsDir.exists()) { - emptyList() - } else { - cordappsDir.list { - it.filter { it.toString().endsWith(".jar") }.map { RestrictedURL(it.toUri().toURL(), null) }.toList() - } - } - } - - /** A list of the core RPC flows present in Corda */ - private val coreRPCFlows = listOf( - ContractUpgradeFlow.Initiate::class.java, - ContractUpgradeFlow.Authorise::class.java, - ContractUpgradeFlow.Deauthorise::class.java) + private fun loadCordapps(): List { + return cordappJarPaths.map { scanCordapp(it).toCordapp(it) } } - private fun loadCordapps(): List { - return cordappJarPaths.map { - val url = it.url - val name = url.toPath().fileName.toString().removeSuffix(".jar") - val info = url.openStream().let(::JarInputStream).use { it.manifest }.toCordappInfo(name) - val scanResult = scanCordapp(it) - CordappImpl(contractClassNames = findContractClassNames(scanResult), - initiatedFlows = findInitiatedFlows(scanResult), - rpcFlows = findRPCFlows(scanResult), - serviceFlows = findServiceFlows(scanResult), - schedulableFlows = findSchedulableFlows(scanResult), - services = findServices(scanResult), - serializationWhitelists = findPlugins(it), - serializationCustomSerializers = findSerializers(scanResult), - customSchemas = findCustomSchemas(scanResult), - allFlows = findAllFlows(scanResult), - jarPath = it.url, - info = info, - jarHash = getJarHash(it.url), - name = name - ) - } + private fun RestrictedScanResult.toCordapp(url: RestrictedURL): Cordapp { + + val name = url.url.toPath().fileName.toString().removeSuffix(".jar") + val info = url.url.openStream().let(::JarInputStream).use { it.manifest }.toCordappInfo(name) + return CordappImpl( + findContractClassNames(this), + findInitiatedFlows(this), + findRPCFlows(this), + findServiceFlows(this), + findSchedulableFlows(this), + findServices(this), + findPlugins(url), + findSerializers(this), + findCustomSchemas(this), + findAllFlows(this), + url.url, + info, + getJarHash(url.url), + name + ) } private fun getJarHash(url: URL): SecureHash.SHA256 = url.openStream().readFully().sha256() @@ -328,11 +194,12 @@ class CordappLoader private constructor(private val cordappJarPaths: List(1000) + private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult { logger.info("Scanning CorDapp in ${cordappJarPath.url}") - return cachedScanResult.computeIfAbsent(cordappJarPath, { + return cachedScanResult.computeIfAbsent(cordappJarPath) { RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix) - }) + } } private class FlowTypeHierarchyComparator(val initiatingFlow: Class>) : Comparator>> { @@ -406,3 +273,26 @@ class CordappLoader private constructor(private val cordappJarPaths: List>, Cordapp> by lazy { + cordapps.flatMap { corDapp -> corDapp.allFlows.map { flow -> flow to corDapp } } + .groupBy { it.first } + .mapValues { + if(it.value.size > 1) { throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow ${it.value.first().first.name}: [ ${it.value.joinToString { it.second.name }} ].") } + it.value.single().second + } + } + + override val cordappSchemas: Set by lazy { + + cordapps.flatMap { it.customSchemas }.toSet() + } + + override val appClassLoader: ClassLoader by lazy { + + URLClassLoader(cordapps.stream().map { it.jarPath }.toTypedArray(), javaClass.classLoader) + } +} + diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt index abcf0b1622..00cb087867 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt @@ -16,10 +16,9 @@ import java.util.* import java.util.jar.Attributes import java.util.jar.Manifest -internal fun createTestManifest(name: String, title: String, jarUUID: UUID): Manifest { +fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest { + val manifest = Manifest() - val version = "test-$jarUUID" - val vendor = "R3" // Mandatory manifest attribute. If not present, all other entries are silently skipped. manifest.mainAttributes[Attributes.Name.MANIFEST_VERSION] = "1.0" @@ -37,11 +36,18 @@ internal fun createTestManifest(name: String, title: String, jarUUID: UUID): Man return manifest } -internal operator fun Manifest.set(key: String, value: String) { +internal fun createTestManifest(name: String, title: String, jarUUID: UUID): Manifest { + + return createTestManifest(name, title, "test-$jarUUID", "R3") +} + +operator fun Manifest.set(key: String, value: String) { + mainAttributes.putValue(key, value) } internal fun Manifest?.toCordappInfo(defaultShortName: String): Cordapp.Info { + var unknown = CordappImpl.Info.UNKNOWN (this?.mainAttributes?.getValue("Name") ?: defaultShortName).let { shortName -> unknown = unknown.copy(shortName = shortName) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 47bd4708cc..4370254c5b 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -15,6 +15,7 @@ import com.typesafe.config.ConfigException import net.corda.core.context.AuthServiceId import net.corda.core.identity.CordaX500Name import net.corda.core.internal.TimedFlow +import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds @@ -39,6 +40,7 @@ val Int.MB: Long get() = this * 1024L * 1024L private val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1) private val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1) +private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps" interface NodeConfiguration : NodeSSLConfiguration { val myLegalName: CordaX500Name @@ -83,6 +85,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val effectiveH2Settings: NodeH2Settings? val flowMonitorPeriodMillis: Duration get() = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS val flowMonitorSuspensionLoggingThresholdMillis: Duration get() = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS + val cordappDirectories: List get() = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT) fun validate(): List @@ -97,6 +100,8 @@ interface NodeConfiguration : NodeSSLConfiguration { val defaultAttachmentContentCacheSize: Long = 10.MB const val defaultAttachmentCacheBound = 1024L + + const val cordappDirectoriesKey = "cordappDirectories" } } @@ -262,7 +267,8 @@ data class NodeConfigurationImpl( // do not use or remove (used by Capsule) private val jarDirs: List = emptyList(), override val flowMonitorPeriodMillis: Duration = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS, - override val flowMonitorSuspensionLoggingThresholdMillis: Duration = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS + override val flowMonitorSuspensionLoggingThresholdMillis: Duration = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS, + override val cordappDirectories: List = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT) ) : NodeConfiguration { companion object { private val logger = loggerFor() diff --git a/node/src/main/resources/net/corda/node/flows/cordapp.properties b/node/src/main/resources/net/corda/node/flows/cordapp.properties new file mode 100644 index 0000000000..4b10332984 --- /dev/null +++ b/node/src/main/resources/net/corda/node/flows/cordapp.properties @@ -0,0 +1 @@ +key=value \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index ff39b3d088..a1728b6e7c 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -49,6 +49,7 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.expect import net.corda.testing.core.expectEvents import net.corda.testing.core.sequence +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork.MockNode import net.corda.testing.node.internal.InternalMockNodeParameters @@ -91,7 +92,7 @@ class CordaRPCOpsImplTest { @Before fun setup() { - mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas")) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset", "net.corda.finance.schemas")) aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME)) rpc = aliceNode.rpcOps CURRENT_RPC_CONTEXT.set(RpcAuthContext(InvocationContext.rpc(testActor()), buildSubject("TEST_USER", emptySet()))) diff --git a/node/src/test/kotlin/net/corda/node/internal/NodeUnloadHandlerTests.kt b/node/src/test/kotlin/net/corda/node/internal/NodeUnloadHandlerTests.kt index 16aa8e423f..c54d15f5f2 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NodeUnloadHandlerTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NodeUnloadHandlerTests.kt @@ -15,6 +15,7 @@ import net.corda.core.node.services.CordaService import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.contextLogger import net.corda.testing.node.internal.InternalMockNetwork +import net.corda.testing.node.internal.cordappsForPackages import org.junit.After import org.junit.Assert.assertTrue import org.junit.Test @@ -27,7 +28,7 @@ class NodeUnloadHandlerTests { val shutdownLatch = CountDownLatch(1) } - private val mockNet = InternalMockNetwork(cordappPackages = listOf(javaClass.packageName), notarySpecs = emptyList()) + private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(javaClass.packageName), notarySpecs = emptyList()) @After fun cleanUp() { diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt index 7c160593f3..a4c70f89d8 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt @@ -85,7 +85,7 @@ class CordappProviderImplTests { fun `test cordapp configuration`() { val configProvider = MockCordappConfigProvider() configProvider.cordappConfigs[isolatedCordappName] = validConfig - val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) + val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR)) val provider = CordappProviderImpl(loader, configProvider, attachmentStore, whitelistedContractImplementations) val expected = provider.getAppContext(provider.cordapps.first()).config @@ -94,7 +94,7 @@ class CordappProviderImplTests { } private fun newCordappProvider(vararg urls: URL): CordappProviderImpl { - val loader = CordappLoader.createDevMode(urls.toList()) + val loader = JarScanningCordappLoader.fromJarUrls(urls.toList()) return CordappProviderImpl(loader, stubConfigProvider, attachmentStore, whitelistedContractImplementations) } } diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt similarity index 71% rename from node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt rename to node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt index 6fb5b739e1..892a1de8ed 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt @@ -12,8 +12,13 @@ package net.corda.node.internal.cordapp import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.* +import net.corda.node.cordapp.CordappLoader +import net.corda.testing.node.internal.cordappsForPackages +import net.corda.testing.node.internal.getTimestampAsDirectoryName +import net.corda.testing.node.internal.packageInDirectory import org.assertj.core.api.Assertions.assertThat import org.junit.Test +import java.nio.file.Path import java.nio.file.Paths @InitiatingFlow @@ -40,7 +45,7 @@ class DummyRPCFlow : FlowLogic() { override fun call() = Unit } -class CordappLoaderTest { +class JarScanningCordappLoaderTest { private companion object { const val testScanPackage = "net.corda.node.internal.cordapp" const val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract" @@ -50,14 +55,14 @@ class CordappLoaderTest { @Test fun `test that classes that aren't in cordapps aren't loaded`() { // Basedir will not be a corda node directory so the dummy flow shouldn't be recognised as a part of a cordapp - val loader = CordappLoader.createDefault(Paths.get(".")) + val loader = JarScanningCordappLoader.fromDirectories(listOf(Paths.get("."))) assertThat(loader.cordapps).containsOnly(loader.coreCordapp) } @Test fun `isolated JAR contains a CorDapp with a contract and plugin`() { - val isolatedJAR = CordappLoaderTest::class.java.getResource("isolated.jar")!! - val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) + val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("isolated.jar")!! + val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR)) val actual = loader.cordapps.toTypedArray() assertThat(actual).hasSize(2) @@ -75,13 +80,11 @@ class CordappLoaderTest { @Test fun `flows are loaded by loader`() { - val loader = CordappLoader.createWithTestPackages(listOf(testScanPackage)) + val loader = cordappLoaderForPackages(listOf(testScanPackage)) val actual = loader.cordapps.toTypedArray() - // One core cordapp, one cordapp from this source tree, and two others due to identically named locations - // in resources and the non-test part of node. This is okay due to this being test code. In production this - // cannot happen. In gradle it will also pick up the node jar. - assertThat(actual.size == 4 || actual.size == 5).isTrue() + // One core cordapp, one cordapp from this source tree. In gradle it will also pick up the node jar. + assertThat(actual.size == 2 || actual.size == 3).isTrue() val actualCordapp = actual.single { !it.initiatedFlows.isEmpty() } assertThat(actualCordapp.initiatedFlows).first().hasSameClassAs(DummyFlow::class.java) @@ -91,14 +94,14 @@ class CordappLoaderTest { @Test fun `duplicate packages are ignored`() { - val loader = CordappLoader.createWithTestPackages(listOf(testScanPackage, testScanPackage)) + val loader = cordappLoaderForPackages(listOf(testScanPackage, testScanPackage)) val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows } assertThat(cordapps).hasSize(1) } @Test fun `sub-packages are ignored`() { - val loader = CordappLoader.createWithTestPackages(listOf("net.corda", testScanPackage)) + val loader = cordappLoaderForPackages(listOf("net.corda", testScanPackage)) val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows } assertThat(cordapps).hasSize(1) } @@ -107,10 +110,24 @@ class CordappLoaderTest { // being used internally. Later iterations will use a classloader per cordapp and this test can be retired. @Test fun `cordapp classloader can load cordapp classes`() { - val isolatedJAR = CordappLoaderTest::class.java.getResource("isolated.jar")!! - val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) + val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("isolated.jar")!! + val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR)) loader.appClassLoader.loadClass(isolatedContractId) loader.appClassLoader.loadClass(isolatedFlowName) } + + private fun cordappLoaderForPackages(packages: Iterable): CordappLoader { + + val cordapps = cordappsForPackages(packages) + return testDirectory().let { directory -> + cordapps.packageInDirectory(directory) + JarScanningCordappLoader.fromDirectories(listOf(directory)) + } + } + + private fun testDirectory(): Path { + + return Paths.get("build", getTimestampAsDirectoryName()) + } } diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index a2d41c3e64..d19a0287b5 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -60,6 +60,7 @@ import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.vault.VaultFiller import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockServices +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNodeParameters import net.corda.testing.node.internal.pumpReceive @@ -89,7 +90,7 @@ import kotlin.test.assertTrue @RunWith(Parameterized::class) class TwoPartyTradeFlowTests(private val anonymous: Boolean) { companion object { - private val cordappPackages = listOf("net.corda.finance.contracts", "net.corda.finance.schemas") + private val cordappPackages = setOf("net.corda.finance.contracts") @JvmStatic @Parameterized.Parameters(name = "Anonymous = {0}") fun data(): Collection = listOf(true, false) @@ -117,7 +118,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { // We run this in parallel threads to help catch any race conditions that may exist. The other tests // we run in the unit test thread exclusively to speed things up, ensure deterministic results and // allow interruption half way through. - mockNet = InternalMockNetwork(cordappPackages = cordappPackages, threadPerNode = true) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages), threadPerNode = true) val ledgerIdentityService = rigorousMock() MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode @@ -169,7 +170,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test(expected = InsufficientBalanceException::class) fun `trade cash for commercial paper fails using soft locking`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages, threadPerNode = true) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages), threadPerNode = true) val ledgerIdentityService = rigorousMock() MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode @@ -227,7 +228,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test fun `shutdown and restore`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages)) val ledgerIdentityService = rigorousMock() MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode @@ -326,11 +327,20 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { // of gets and puts. private fun makeNodeWithTracking(name: CordaX500Name): StartedNode { // Create a node in the mock network ... - return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args -> - object : InternalMockNetwork.MockNode(args) { - // That constructs a recording tx storage - override fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage { - return RecordingTransactionStorage(database, super.makeTransactionStorage(database, transactionCacheSizeBytes)) + return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args, cordappLoader -> + if (cordappLoader != null) { + object : InternalMockNetwork.MockNode(args, cordappLoader) { + // That constructs a recording tx storage + override fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage { + return RecordingTransactionStorage(database, super.makeTransactionStorage(database, transactionCacheSizeBytes)) + } + } + } else { + object : InternalMockNetwork.MockNode(args) { + // That constructs a recording tx storage + override fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage { + return RecordingTransactionStorage(database, super.makeTransactionStorage(database, transactionCacheSizeBytes)) + } } } }) @@ -338,7 +348,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test fun `check dependencies of sale asset are resolved`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages)) val notaryNode = mockNet.defaultNotaryNode val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) @@ -442,7 +452,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test fun `track works`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages)) val notaryNode = mockNet.defaultNotaryNode val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) @@ -520,7 +530,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test fun `dependency with error on buyer side`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages)) val ledgerIdentityService = rigorousMock() MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { runWithError(ledgerIdentityService, true, false, "at least one cash input") @@ -529,7 +539,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test fun `dependency with error on seller side`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages)) val ledgerIdentityService = rigorousMock() MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { runWithError(ledgerIdentityService, false, true, "Issuances have a time-window") diff --git a/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt b/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt index 24eea7a1cf..df18046284 100644 --- a/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt +++ b/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt @@ -11,11 +11,7 @@ package net.corda.node.modes.draining import co.paralleluniverse.fibers.Suspendable -import net.corda.core.contracts.LinearState -import net.corda.core.contracts.SchedulableState -import net.corda.core.contracts.ScheduledActivity -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.contracts.* import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogicRefFactory @@ -30,6 +26,7 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.dummyCommand import net.corda.testing.core.singleIdentity +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNodeParameters import net.corda.testing.node.internal.startFlow @@ -61,7 +58,7 @@ class ScheduledFlowsDrainingModeTest { @Before fun setup() { - mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"), threadPerNode = true) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), threadPerNode = true) aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME)) bobNode = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME)) notary = mockNet.defaultNotaryIdentity 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 fc07181b00..2b04dda50e 100644 --- a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt @@ -16,6 +16,7 @@ import net.corda.node.services.statemachine.StaffedFlowHospital.MedicalRecord.Ke import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity +import net.corda.testing.driver.TestCorDapp import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNodeParameters import net.corda.testing.node.internal.startFlow @@ -37,10 +38,8 @@ class FinalityHandlerTest { // CorDapp. Bob's FinalityHandler will error when validating the tx. mockNet = InternalMockNetwork() - val alice = mockNet.createNode(InternalMockNodeParameters( - legalName = ALICE_NAME, - extraCordappPackages = listOf("net.corda.finance.contracts.asset") - )) + val assertCordapp = TestCorDapp.Factory.create("net.corda.finance.contracts.asset", "1.0").plusPackage("net.corda.finance.contracts.asset") + val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = setOf(assertCordapp))) var bob = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME)) diff --git a/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt b/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt index 92f35bb37c..3329cc7259 100644 --- a/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt @@ -14,6 +14,7 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.contracts.asset.Cash import net.corda.finance.issuedBy +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.startFlow import org.assertj.core.api.Assertions.assertThat @@ -24,7 +25,7 @@ import java.util.concurrent.CountDownLatch class ServiceHubConcurrentUsageTest { - private val mockNet = InternalMockNetwork(listOf(Cash::class.packageName)) + private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(Cash::class.packageName)) @After fun stopNodes() { diff --git a/node/src/test/kotlin/net/corda/node/services/TimedFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/TimedFlowTests.kt index d0949c40ad..6383d041de 100644 --- a/node/src/test/kotlin/net/corda/node/services/TimedFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/TimedFlowTests.kt @@ -48,6 +48,7 @@ import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockNetworkParameters import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNodeParameters +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.startFlow import org.junit.Before import org.junit.ClassRule @@ -108,8 +109,8 @@ class TimedFlowTestRule(val clusterSize: Int) : ExternalResource() { override fun before() { mockNet = InternalMockNetwork( - listOf("net.corda.testing.contracts", "net.corda.node.services"), - MockNetworkParameters().withServicePeerAllocationStrategy(InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin()), + cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts", "net.corda.node.services"), + defaultParameters = MockNetworkParameters().withServicePeerAllocationStrategy(InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin()), threadPerNode = true ) val started = startClusterAndNode(mockNet) diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index 3a58b5f348..03a28c1941 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -33,6 +33,7 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.dummyCommand import net.corda.testing.core.singleIdentity +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork.MockNode import net.corda.testing.node.internal.InternalMockNodeParameters @@ -111,7 +112,7 @@ class ScheduledFlowTests { @Before fun setup() { - mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"), threadPerNode = true) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), threadPerNode = true) aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME)) bobNode = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME)) notary = mockNet.defaultNotaryIdentity diff --git a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt index cfdef8319d..321037b118 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/NodeSchemaServiceTest.kt @@ -25,6 +25,7 @@ import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.driver import net.corda.testing.driver.internal.InProcessImpl import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import org.hibernate.annotations.Cascade import org.hibernate.annotations.CascadeType @@ -41,7 +42,7 @@ class NodeSchemaServiceTest { */ @Test fun `registering custom schemas for testing with MockNode`() { - val mockNet = InternalMockNetwork(cordappPackages = listOf(DummyLinearStateSchemaV1::class.packageName)) + val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(DummyLinearStateSchemaV1::class.packageName)) val mockNode = mockNet.createNode() val schemaService = mockNode.services.schemaService assertTrue(schemaService.schemaOptions.containsKey(DummyLinearStateSchemaV1)) @@ -74,8 +75,6 @@ class NodeSchemaServiceTest { /** * Note: this test verifies auto-scanning to register identified [MappedSchema] schemas. - * By default, Driver uses the caller package for auto-scanning: - * System.setProperty("net.corda.node.cordapp.scan.packages", callerPackage) */ @Test fun `auto scanning of custom schemas for testing with Driver`() { 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 9806f83078..2dfc002ac1 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 @@ -78,7 +78,7 @@ class FlowFrameworkTests { @JvmStatic fun beforeClass() { mockNet = InternalMockNetwork( - cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"), + cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts", "net.corda.testing.contracts"), servicePeerAllocationStrategy = RoundRobin() ) @@ -491,7 +491,7 @@ class FlowFrameworkTripartyTests { @JvmStatic fun beforeClass() { mockNet = InternalMockNetwork( - cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"), + cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts", "net.corda.testing.contracts"), servicePeerAllocationStrategy = RoundRobin() ) @@ -655,7 +655,7 @@ class FlowFrameworkPersistenceTests { @Before fun start() { mockNet = InternalMockNetwork( - cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"), + cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts", "net.corda.testing.contracts"), servicePeerAllocationStrategy = RoundRobin() ) aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME)) diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/IdempotentFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/IdempotentFlowTests.kt index 459a67b1a2..56a35d8409 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/IdempotentFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/IdempotentFlowTests.kt @@ -14,6 +14,7 @@ import net.corda.core.utilities.seconds import net.corda.node.internal.StartedNode import net.corda.node.services.config.FlowTimeoutConfiguration import net.corda.node.services.config.NodeConfiguration +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNodeParameters import net.corda.testing.node.internal.startFlow @@ -37,7 +38,7 @@ class IdempotentFlowTests { @Before fun start() { - mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = listOf(this.javaClass.packageName)) + mockNet = InternalMockNetwork(threadPerNode = true, cordappsForAllNodes = cordappsForPackages(this.javaClass.packageName)) nodeA = mockNet.createNode(InternalMockNodeParameters( legalName = CordaX500Name("Alice", "AliceCorp", "GB"), configOverrides = { diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt index 0bf110ba43..fa6ad10f88 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt @@ -16,6 +16,7 @@ import net.corda.node.services.FinalityHandler import net.corda.node.services.messaging.Message import net.corda.node.services.persistence.DBTransactionStorage import net.corda.nodeapi.internal.persistence.contextTransaction +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork.MockNode import net.corda.testing.node.internal.MessagingServiceSpy @@ -42,7 +43,7 @@ class RetryFlowMockTest { @Before fun start() { - mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = listOf(this.javaClass.packageName)) + mockNet = InternalMockNetwork(threadPerNode = true, cordappsForAllNodes = cordappsForPackages(this.javaClass.packageName)) nodeA = mockNet.createNode() nodeB = mockNet.createNode() mockNet.startNodes() diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index 5cc9c5b43f..4607194afa 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -25,6 +25,7 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.singleIdentity import net.corda.testing.node.MockNetworkNotarySpec +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNodeParameters import net.corda.testing.node.internal.startFlow @@ -43,7 +44,7 @@ class NotaryServiceTests { @Before fun setup() { mockNet = InternalMockNetwork( - cordappPackages = listOf("net.corda.testing.contracts"), + cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME, validating = false)) ) aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME)) diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 6966c23eac..ed28b8232f 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -46,6 +46,7 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.dummyCommand import net.corda.testing.core.singleIdentity import net.corda.testing.node.TestClock +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InMemoryMessage import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNodeParameters @@ -72,7 +73,7 @@ class ValidatingNotaryServiceTests { @Before fun setup() { - mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts")) aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME)) notaryNode = mockNet.defaultNotaryNode notary = mockNet.defaultNotaryIdentity diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index a87144157b..7ae52df6d2 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -39,6 +39,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.testing.core.singleIdentity import net.corda.testing.internal.rigorousMock +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.startFlow import org.junit.After @@ -90,7 +91,7 @@ class VaultSoftLockManagerTest { private val mockVault = rigorousMock().also { doNothing().whenever(it).softLockRelease(any(), anyOrNull()) } - private val mockNet = InternalMockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = { args -> + private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(ContractImpl::class.packageName), defaultFactory = { args, _ -> object : InternalMockNetwork.MockNode(args) { override fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, hibernateConfig: HibernateConfiguration, database: CordaPersistence): VaultServiceInternal { val node = this diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt index 6973ffc6b3..19e4c5e7e5 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt @@ -21,6 +21,7 @@ import net.corda.core.identity.Party import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.packageName +import net.corda.core.node.services.IdentityService import net.corda.core.node.services.Vault import net.corda.core.node.services.VaultService import net.corda.core.node.services.queryBy diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlowTests.kt index 57e617b752..9a477982a7 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentFlowTests.kt @@ -23,6 +23,7 @@ import net.corda.node.internal.StartedNode import net.corda.testing.core.* import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.internal.InternalMockNetwork +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.startFlow import org.junit.After import org.junit.Before @@ -40,7 +41,7 @@ class CashIssueAndPaymentFlowTests { @Before fun start() { mockNet = InternalMockNetwork(servicePeerAllocationStrategy = RoundRobin(), - cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas")) + cordappsForAllNodes = cordappsForPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas")) bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) aliceNode = mockNet.createPartyNode(ALICE_NAME) bankOfCorda = bankOfCordaNode.info.singleIdentity() diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentNoSelectionFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentNoSelectionFlowTest.kt index e691dccc95..6e72f63ecd 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentNoSelectionFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashIssueAndPaymentNoSelectionFlowTest.kt @@ -22,9 +22,8 @@ import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.testing.core.* import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.StartedMockNode import net.corda.testing.node.internal.InternalMockNetwork +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.startFlow import org.junit.After import org.junit.Before @@ -51,7 +50,7 @@ class CashIssueAndPayNoSelectionTests(private val anonymous: Boolean) { @Before fun start() { mockNet = InternalMockNetwork(servicePeerAllocationStrategy = RoundRobin(), - cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas")) + cordappsForAllNodes = cordappsForPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas")) bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) aliceNode = mockNet.createPartyNode(ALICE_NAME) bankOfCorda = bankOfCordaNode.info.singleIdentity() diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt index 0e915fa817..11aa819842 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/CashPaymentFlowTests.kt @@ -23,6 +23,7 @@ import net.corda.node.internal.StartedNode import net.corda.testing.core.* import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.internal.InternalMockNetwork +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.node.internal.startFlow import org.junit.After import org.junit.Before @@ -42,7 +43,7 @@ class CashPaymentFlowTests { fun start() { mockNet = InternalMockNetwork( servicePeerAllocationStrategy = RoundRobin(), - cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas")) + cordappsForAllNodes = cordappsForPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas")) bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) aliceNode = mockNet.createPartyNode(ALICE_NAME) bankOfCorda = bankOfCordaNode.info.singleIdentity() diff --git a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt index 1441fe49a6..5af88e64b0 100644 --- a/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt +++ b/perftestcordapp/src/test/kotlin/com/r3/corda/enterprise/perftestcordapp/flows/TwoPartyTradeFlowTest.kt @@ -64,10 +64,7 @@ import net.corda.testing.internal.LogHelper import net.corda.testing.internal.TEST_TX_TIME import net.corda.testing.internal.rigorousMock import net.corda.testing.node.* -import net.corda.testing.node.internal.InternalMockNetwork -import net.corda.testing.node.internal.InternalMockNodeParameters -import net.corda.testing.node.internal.pumpReceive -import net.corda.testing.node.internal.startFlow +import net.corda.testing.node.internal.* import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -99,8 +96,8 @@ internal fun CheckpointStorage.checkpoints(): List> @RunWith(Parameterized::class) class TwoPartyTradeFlowTests(private val anonymous: Boolean) { companion object { - private val cordappPackages = listOf( - "com.r3.corda.enterprise.perftestcordapp.contracts", "com.r3.corda.enterprise.perftestcordapp.schemas") + private val cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts", "com.r3.corda.enterprise.perftestcordapp.schemas") + private val cordappsForAllNodes = cordappsForPackages(cordappPackages) @JvmStatic @Parameterized.Parameters(name = "Anonymous = {0}") fun data(): Collection = listOf(true, false) @@ -128,7 +125,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { // We run this in parallel threads to help catch any race conditions that may exist. The other tests // we run in the unit test thread exclusively to speed things up, ensure deterministic results and // allow interruption half way through. - mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(threadPerNode = true, cordappsForAllNodes = cordappsForAllNodes) val ledgerIdentityService = rigorousMock() MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode @@ -181,7 +178,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test(expected = InsufficientBalanceException::class) fun `trade cash for commercial paper fails using soft locking`() { - mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(threadPerNode = true, cordappsForAllNodes = cordappsForAllNodes) val ledgerIdentityService = rigorousMock() MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode @@ -239,7 +236,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test fun `shutdown and restore`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForAllNodes) val ledgerIdentityService = rigorousMock() MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { val notaryNode = mockNet.defaultNotaryNode @@ -340,7 +337,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { private fun makeNodeWithTracking( name: CordaX500Name): StartedNode { // Create a node in the mock network ... - return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args -> + return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args, _ -> object : InternalMockNetwork.MockNode(args) { // That constructs a recording tx storage override fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage { @@ -352,7 +349,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test fun `check dependencies of sale asset are resolved`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForAllNodes) val notaryNode = mockNet.defaultNotaryNode val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) @@ -456,7 +453,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test fun `track works`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForAllNodes) val notaryNode = mockNet.defaultNotaryNode val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) @@ -534,7 +531,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test fun `dependency with error on buyer side`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForAllNodes) val ledgerIdentityService = rigorousMock() MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { runWithError(ledgerIdentityService, true, false, "at least one cash input") @@ -543,7 +540,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { @Test fun `dependency with error on seller side`() { - mockNet = InternalMockNetwork(cordappPackages = cordappPackages) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForAllNodes) val ledgerIdentityService = rigorousMock() MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) { runWithError(ledgerIdentityService, false, true, "Issuances have a time-window") diff --git a/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt index a28a52ae78..47599e71f9 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt @@ -23,7 +23,7 @@ import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.* import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.OpaqueBytes -import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.nodeapi.DummyContractBackdoor import net.corda.testing.common.internal.testNetworkParameters @@ -67,7 +67,7 @@ class AttachmentsClassLoaderTests { val testSerialization = SerializationEnvironmentRule() private val attachments = MockAttachmentStorage() private val networkParameters = testNetworkParameters() - private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments, networkParameters.whitelistedContractImplementations) + private val cordappProvider = CordappProviderImpl(JarScanningCordappLoader.fromJarUrls(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments, networkParameters.whitelistedContractImplementations) private val cordapp get() = cordappProvider.cordapps.first() private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!! private val appContext get() = cordappProvider.getAppContext(cordapp) diff --git a/settings.gradle b/settings.gradle index 67574b7e89..d99e6d56f0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -86,3 +86,15 @@ include 'cordform-common' include 'hsm-tool' include 'launcher' include 'node:dist' + +buildCache { + local { + enabled = false + } + remote(HttpBuildCache) { +// url = 'http://localhost:5071/cache/' + // CI server: gradle-build-cache + url = 'http://40.114.193.246:80/cache/' + push = true + } +} 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 0266b0196d..174c6ac911 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 @@ -30,10 +30,7 @@ import net.corda.testing.driver.PortAllocation.Incremental import net.corda.testing.driver.internal.internalServices import net.corda.testing.node.NotarySpec import net.corda.testing.node.User -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 net.corda.testing.node.internal.* import rx.Observable import java.nio.file.Path import java.nio.file.Paths @@ -146,6 +143,9 @@ abstract class PortAllocation { * @property startInSameProcess Determines if the node should be started inside the same process the Driver is running * in. If null the Driver-level value will be used. * @property maximumHeapSize The maximum JVM heap size to use for the node. + * @property logLevel Logging level threshold. + * @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( @@ -155,8 +155,57 @@ data class NodeParameters( val customOverrides: Map = emptyMap(), val startInSameProcess: Boolean? = null, val maximumHeapSize: String = "512m", - val logLevel: String? = null + val logLevel: String? = null, + val additionalCordapps: Set = emptySet(), + val regenerateCordappsOnStart: Boolean = false ) { + /** + * Helper builder for configuring a [Node] from Java. + * + * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something + * random. Note that this must be unique as the driver uses it as a primary key! + * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to a single user with + * all permissions. + * @param verifierType The type of transaction verifier to use. See: [VerifierType] + * @param customOverrides A map of custom node configuration overrides. + * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running + * in. If null the Driver-level value will be used. + * @param maximumHeapSize The maximum JVM heap size to use for the node. + * @param logLevel Logging level threshold. + */ + constructor( + providedName: CordaX500Name?, + rpcUsers: List, + verifierType: VerifierType, + customOverrides: Map, + startInSameProcess: Boolean?, + maximumHeapSize: String, + logLevel: String? = null + ) : this( + providedName, + rpcUsers, + verifierType, + customOverrides, + startInSameProcess, + maximumHeapSize, + logLevel, + additionalCordapps = emptySet(), + regenerateCordappsOnStart = false + ) + + /** + * Helper builder for configuring a [Node] from Java. + * + * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something + * random. Note that this must be unique as the driver uses it as a primary key! + * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to a single user with + * all permissions. + * @param verifierType The type of transaction verifier to use. See: [VerifierType] + * @param customOverrides A map of custom node configuration overrides. + * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running + * in. If null the Driver-level value will be used. + * @param maximumHeapSize The maximum JVM heap size to use for the node. + */ constructor( providedName: CordaX500Name?, rpcUsers: List, @@ -171,7 +220,44 @@ data class NodeParameters( customOverrides, startInSameProcess, maximumHeapSize, - null) + null, + additionalCordapps = emptySet(), + regenerateCordappsOnStart = false) + + /** + * Helper builder for configuring a [Node] from Java. + * + * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something + * random. Note that this must be unique as the driver uses it as a primary key! + * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to a single user with + * all permissions. + * @param verifierType The type of transaction verifier to use. See: [VerifierType] + * @param customOverrides A map of custom node configuration overrides. + * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running + * in. If null the Driver-level value will be used. + * @param maximumHeapSize The maximum JVM heap size to use for the node. + * @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. + * @param regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. + */ + constructor( + providedName: CordaX500Name?, + rpcUsers: List, + verifierType: VerifierType, + customOverrides: Map, + startInSameProcess: Boolean?, + maximumHeapSize: String, + additionalCordapps: Set = emptySet(), + regenerateCordappsOnStart: Boolean = false + ) : this( + providedName, + rpcUsers, + verifierType, + customOverrides, + startInSameProcess, + maximumHeapSize, + null, + additionalCordapps, + regenerateCordappsOnStart) fun copy( providedName: CordaX500Name?, @@ -189,6 +275,25 @@ data class NodeParameters( maximumHeapSize, null) + fun copy( + providedName: CordaX500Name?, + rpcUsers: List, + verifierType: VerifierType, + customOverrides: Map, + startInSameProcess: Boolean?, + maximumHeapSize: String, + logLevel: String? + ) = this.copy( + providedName, + rpcUsers, + verifierType, + customOverrides, + startInSameProcess, + maximumHeapSize, + logLevel, + additionalCordapps = additionalCordapps, + regenerateCordappsOnStart = regenerateCordappsOnStart) + fun withProvidedName(providedName: CordaX500Name?): NodeParameters = copy(providedName = providedName) fun withRpcUsers(rpcUsers: List): NodeParameters = copy(rpcUsers = rpcUsers) fun withVerifierType(verifierType: VerifierType): NodeParameters = copy(verifierType = verifierType) @@ -196,6 +301,8 @@ data class NodeParameters( fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess) fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize) fun withLogLevel(logLevel: String?): NodeParameters = copy(logLevel = logLevel) + fun withAdditionalCordapps(additionalCordapps: Set): NodeParameters = copy(additionalCordapps = additionalCordapps) + fun withDeleteExistingCordappsDirectory(regenerateCordappsOnStart: Boolean): NodeParameters = copy(regenerateCordappsOnStart = regenerateCordappsOnStart) } /** @@ -240,12 +347,12 @@ fun driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr startNodesInProcess = defaultParameters.startNodesInProcess, waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish, notarySpecs = defaultParameters.notarySpecs, - extraCordappPackagesToScan = defaultParameters.extraCordappPackagesToScan, jmxPolicy = defaultParameters.jmxPolicy, compatibilityZone = null, networkParameters = defaultParameters.networkParameters, notaryCustomOverrides = defaultParameters.notaryCustomOverrides, - inMemoryDB = defaultParameters.inMemoryDB + inMemoryDB = defaultParameters.inMemoryDB, + cordappsForAllNodes = defaultParameters.cordappsForAllNodes() ), coerce = { it }, dsl = dsl, @@ -282,6 +389,7 @@ fun driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr * @property inMemoryDB Whether to use in-memory H2 for new nodes rather then on-disk (the node starts quicker, however * the data is not persisted between node restarts). Has no effect if node is configured * in any way to use database other than H2. + * @property cordappsForAllNodes [TestCorDapp]s that will be added to each node started by the [DriverDSL]. */ @Suppress("unused") data class DriverParameters( @@ -299,8 +407,44 @@ data class DriverParameters( val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()), val notaryCustomOverrides: Map = emptyMap(), val initialiseSerialization: Boolean = true, - val inMemoryDB: Boolean = true + val inMemoryDB: Boolean = true, + val cordappsForAllNodes: Set? = null ) { + constructor( + isDebug: Boolean = false, + driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), + portAllocation: PortAllocation = PortAllocation.Incremental(10000), + debugPortAllocation: PortAllocation = PortAllocation.Incremental(5005), + systemProperties: Map = emptyMap(), + useTestClock: Boolean = false, + startNodesInProcess: Boolean = false, + waitForAllNodesToFinish: Boolean = false, + notarySpecs: List = listOf(NotarySpec(DUMMY_NOTARY_NAME)), + extraCordappPackagesToScan: List = emptyList(), + jmxPolicy: JmxPolicy = JmxPolicy(), + networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()), + notaryCustomOverrides: Map = emptyMap(), + initialiseSerialization: Boolean = true, + inMemoryDB: Boolean = true + ) : this( + isDebug, + driverDirectory, + portAllocation, + debugPortAllocation, + systemProperties, + useTestClock, + startNodesInProcess, + waitForAllNodesToFinish, + notarySpecs, + extraCordappPackagesToScan, + jmxPolicy, + networkParameters, + notaryCustomOverrides, + initialiseSerialization, + inMemoryDB, + cordappsForAllNodes = null + ) + constructor( isDebug: Boolean, driverDirectory: Path, @@ -329,7 +473,41 @@ data class DriverParameters( networkParameters, emptyMap(), true, - true + true, + cordappsForAllNodes = null + ) + + constructor( + isDebug: Boolean, + driverDirectory: Path, + portAllocation: PortAllocation, + debugPortAllocation: PortAllocation, + systemProperties: Map, + useTestClock: Boolean, + startNodesInProcess: Boolean, + waitForAllNodesToFinish: Boolean, + notarySpecs: List, + extraCordappPackagesToScan: List, + jmxPolicy: JmxPolicy, + networkParameters: NetworkParameters, + cordappsForAllNodes: Set? = null + ) : this( + isDebug, + driverDirectory, + portAllocation, + debugPortAllocation, + systemProperties, + useTestClock, + startNodesInProcess, + waitForAllNodesToFinish, + notarySpecs, + extraCordappPackagesToScan, + jmxPolicy, + networkParameters, + emptyMap(), + true, + true, + cordappsForAllNodes ) constructor( @@ -362,7 +540,43 @@ data class DriverParameters( networkParameters, emptyMap(), initialiseSerialization, - inMemoryDB + inMemoryDB, + cordappsForAllNodes = null + ) + + constructor( + isDebug: Boolean, + driverDirectory: Path, + portAllocation: PortAllocation, + debugPortAllocation: PortAllocation, + systemProperties: Map, + useTestClock: Boolean, + startNodesInProcess: Boolean, + waitForAllNodesToFinish: Boolean, + notarySpecs: List, + extraCordappPackagesToScan: List, + jmxPolicy: JmxPolicy, + networkParameters: NetworkParameters, + initialiseSerialization: Boolean, + inMemoryDB: Boolean, + cordappsForAllNodes: Set? = null + ) : this( + isDebug, + driverDirectory, + portAllocation, + debugPortAllocation, + systemProperties, + useTestClock, + startNodesInProcess, + waitForAllNodesToFinish, + notarySpecs, + extraCordappPackagesToScan, + jmxPolicy, + networkParameters, + emptyMap(), + initialiseSerialization, + inMemoryDB, + cordappsForAllNodes ) fun withIsDebug(isDebug: Boolean): DriverParameters = copy(isDebug = isDebug) @@ -380,6 +594,7 @@ data class DriverParameters( fun withNetworkParameters(networkParameters: NetworkParameters): DriverParameters = copy(networkParameters = networkParameters) fun withNotaryCustomOverrides(notaryCustomOverrides: Map): DriverParameters = copy(notaryCustomOverrides = notaryCustomOverrides) fun withInMemoryDB(inMemoryDB: Boolean): DriverParameters = copy(inMemoryDB = inMemoryDB) + fun withCordappsForAllNodes(cordappsForAllNodes: Set?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes) fun copy( isDebug: Boolean, @@ -441,4 +656,69 @@ data class DriverParameters( notaryCustomOverrides = emptyMap(), initialiseSerialization = initialiseSerialization ) + + fun copy( + isDebug: Boolean, + driverDirectory: Path, + portAllocation: PortAllocation, + debugPortAllocation: PortAllocation, + systemProperties: Map, + useTestClock: Boolean, + startNodesInProcess: Boolean, + waitForAllNodesToFinish: Boolean, + notarySpecs: List, + extraCordappPackagesToScan: List, + jmxPolicy: JmxPolicy, + networkParameters: NetworkParameters, + cordappsForAllNodes: Set? + ) = this.copy( + isDebug = isDebug, + driverDirectory = driverDirectory, + portAllocation = portAllocation, + debugPortAllocation = debugPortAllocation, + systemProperties = systemProperties, + useTestClock = useTestClock, + startNodesInProcess = startNodesInProcess, + waitForAllNodesToFinish = waitForAllNodesToFinish, + notarySpecs = notarySpecs, + extraCordappPackagesToScan = extraCordappPackagesToScan, + jmxPolicy = jmxPolicy, + networkParameters = networkParameters, + notaryCustomOverrides = emptyMap(), + initialiseSerialization = true, + cordappsForAllNodes = cordappsForAllNodes + ) + + fun copy( + isDebug: Boolean, + driverDirectory: Path, + portAllocation: PortAllocation, + debugPortAllocation: PortAllocation, + systemProperties: Map, + useTestClock: Boolean, + startNodesInProcess: Boolean, + waitForAllNodesToFinish: Boolean, + notarySpecs: List, + extraCordappPackagesToScan: List, + jmxPolicy: JmxPolicy, + networkParameters: NetworkParameters, + initialiseSerialization: Boolean, + cordappsForAllNodes: Set? + ) = this.copy( + isDebug = isDebug, + driverDirectory = driverDirectory, + portAllocation = portAllocation, + debugPortAllocation = debugPortAllocation, + systemProperties = systemProperties, + useTestClock = useTestClock, + startNodesInProcess = startNodesInProcess, + waitForAllNodesToFinish = waitForAllNodesToFinish, + notarySpecs = notarySpecs, + extraCordappPackagesToScan = extraCordappPackagesToScan, + jmxPolicy = jmxPolicy, + networkParameters = networkParameters, + notaryCustomOverrides = emptyMap(), + initialiseSerialization = initialiseSerialization, + cordappsForAllNodes = cordappsForAllNodes + ) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt index 7e4553b422..d1632b0ea4 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt @@ -90,6 +90,38 @@ interface DriverDSL { maximumHeapSize: String = defaultParameters.maximumHeapSize ): CordaFuture + /** + * Start a node. + * + * @param defaultParameters The default parameters for the node. Allows the node to be configured in builder style + * when called from Java code. + * @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something + * random. Note that this must be unique as the driver uses it as a primary key! + * @param rpcUsers List of users who are authorised to use the RPC system. Defaults to empty list. + * @param verifierType The type of transaction verifier to use. See: [VerifierType]. + * @param customOverrides A map of custom node configuration overrides. + * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running + * in. If null the Driver-level value will be used. + * @param maximumHeapSize The maximum JVM heap size to use for the node as a [String]. By default a number is interpreted + * as being in bytes. Append the letter 'k' or 'K' to the value to indicate Kilobytes, 'm' or 'M' to indicate + * megabytes, and 'g' or 'G' to indicate gigabytes. The default value is "512m" = 512 megabytes. + * @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. + * @param regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. + * @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available and + * it sees all previously started nodes, including the notaries. + */ + fun startNode( + defaultParameters: NodeParameters = NodeParameters(), + providedName: CordaX500Name? = defaultParameters.providedName, + rpcUsers: List = defaultParameters.rpcUsers, + verifierType: VerifierType = defaultParameters.verifierType, + customOverrides: Map = defaultParameters.customOverrides, + startInSameProcess: Boolean? = defaultParameters.startInSameProcess, + maximumHeapSize: String = defaultParameters.maximumHeapSize, + additionalCordapps: Set = defaultParameters.additionalCordapps, + regenerateCordappsOnStart: Boolean = defaultParameters.regenerateCordappsOnStart + ): CordaFuture + /** * Helper function for starting a [Node] with custom parameters from Java. * diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/TestCorDapp.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/TestCorDapp.kt new file mode 100644 index 0000000000..8dd209c34f --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/TestCorDapp.kt @@ -0,0 +1,85 @@ +package net.corda.testing.driver + +import net.corda.core.DoNotImplement +import net.corda.testing.node.internal.MutableTestCorDapp +import java.net.URL +import java.nio.file.Path + +/** + * Represents information about a CorDapp. Used to generate CorDapp JARs in tests. + */ +@DoNotImplement +interface TestCorDapp { + + val name: String + val title: String + val version: String + val vendor: String + + val classes: Set> + + val resources: Set + + fun packageAsJarInDirectory(parentDirectory: Path): Path + + fun packageAsJarWithPath(jarFilePath: Path) + + /** + * Responsible of creating [TestCorDapp]s. + */ + class Factory { + companion object { + + /** + * Returns a builder-style [TestCorDapp] to easily generate different [TestCorDapp]s that have something in common. + */ + @JvmStatic + fun create(name: String, version: String, vendor: String = "R3", title: String = name, classes: Set> = emptySet(), willResourceBeAddedBeToCorDapp: (fullyQualifiedName: String, url: URL) -> Boolean = MutableTestCorDapp.Companion::filterTestCorDappClass): TestCorDapp.Mutable { + + return MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedBeToCorDapp) + } + } + } + + @DoNotImplement + interface Mutable : TestCorDapp { + + fun withName(name: String): TestCorDapp.Mutable + + fun withTitle(title: String): TestCorDapp.Mutable + + fun withVersion(version: String): TestCorDapp.Mutable + + fun withVendor(vendor: String): TestCorDapp.Mutable + + fun withClasses(classes: Set>): TestCorDapp.Mutable + + fun plusPackages(pckgs: Set): TestCorDapp.Mutable + + fun minusPackages(pckgs: Set): TestCorDapp.Mutable + + fun plusPackage(pckg: String): TestCorDapp.Mutable = plusPackages(setOf(pckg)) + + fun minusPackage(pckg: String): TestCorDapp.Mutable = minusPackages(setOf(pckg)) + + fun plusPackage(pckg: Package): TestCorDapp.Mutable = plusPackages(pckg.name) + + fun minusPackage(pckg: Package): TestCorDapp.Mutable = minusPackages(pckg.name) + + operator fun plus(clazz: Class<*>): TestCorDapp.Mutable = withClasses(classes + clazz) + + operator fun minus(clazz: Class<*>): TestCorDapp.Mutable = withClasses(classes - clazz) + + fun plusPackages(pckg: String, vararg pckgs: String): TestCorDapp.Mutable = plusPackages(setOf(pckg, *pckgs)) + + fun plusPackages(pckg: Package, vararg pckgs: Package): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs).map { it.name }.toSet()) + + fun minusPackages(pckg: String, vararg pckgs: String): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs)) + + fun minusPackages(pckg: Package, vararg pckgs: Package): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs).map { it.name }.toSet()) + + fun plusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable + + fun minusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable + } +} \ No newline at end of file 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 9861c8b59a..eee0225bc3 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 @@ -24,10 +24,8 @@ import net.corda.node.internal.StartedNode 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.InternalMockMessagingService -import net.corda.testing.node.internal.InternalMockNetwork -import net.corda.testing.node.internal.InternalMockNodeParameters -import net.corda.testing.node.internal.newContext +import net.corda.testing.driver.TestCorDapp +import net.corda.testing.node.internal.* import rx.Observable import java.math.BigInteger import java.nio.file.Path @@ -43,22 +41,36 @@ import java.nio.file.Path * @property 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. * @property configOverrides Add/override behaviour of the [NodeConfiguration] mock object. - * @property extraCordappPackages Extra CorDapp packages to include for this node. + * @property additionalCordapps [TestCorDapp]s that will be added to this node in addition to the ones shared by all nodes, which get specified at [MockNetwork] level. */ @Suppress("unused") -data class MockNodeParameters @JvmOverloads constructor( +data class MockNodeParameters constructor( val forcedID: Int? = null, val legalName: CordaX500Name? = null, val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val configOverrides: (NodeConfiguration) -> Any? = {}, - val extraCordappPackages: List = emptyList()) { + val additionalCordapps: Set) { + + @JvmOverloads + constructor( + forcedID: Int? = null, + legalName: CordaX500Name? = null, + entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), + configOverrides: (NodeConfiguration) -> Any? = {}, + extraCordappPackages: List = emptyList() + ) : this(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps = cordappsForPackages(extraCordappPackages)) + fun withForcedID(forcedID: Int?): MockNodeParameters = copy(forcedID = forcedID) fun withLegalName(legalName: CordaX500Name?): MockNodeParameters = copy(legalName = legalName) fun withEntropyRoot(entropyRoot: BigInteger): MockNodeParameters = copy(entropyRoot = entropyRoot) fun withConfigOverrides(configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters = copy(configOverrides = configOverrides) - fun withExtraCordappPackages(extraCordappPackages: List): MockNodeParameters = copy(extraCordappPackages = extraCordappPackages) + fun withExtraCordappPackages(extraCordappPackages: List): MockNodeParameters = copy(forcedID = forcedID, legalName = legalName, entropyRoot = entropyRoot, configOverrides = configOverrides, extraCordappPackages = extraCordappPackages) + fun withAdditionalCordapps(additionalCordapps: Set): MockNodeParameters = copy(additionalCordapps = additionalCordapps) fun copy(forcedID: Int?, legalName: CordaX500Name?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters { - return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides) + return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps = emptySet()) + } + fun copy(forcedID: Int?, legalName: CordaX500Name?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?, extraCordappPackages: List = emptyList()): MockNodeParameters { + return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, extraCordappPackages) } } @@ -215,6 +227,7 @@ class StartedMockNode private constructor(private val node: StartedNode = defaultParameters.notarySpecs, - val networkParameters: NetworkParameters = defaultParameters.networkParameters) { + val networkParameters: NetworkParameters = defaultParameters.networkParameters, + val cordappsForAllNodes: Set = cordappsForPackages(cordappPackages)) { + @JvmOverloads constructor(cordappPackages: List, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters) - private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(cordappPackages, defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters) + 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, networkParameters = networkParameters, cordappsForAllNodes = 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 @@ -272,7 +297,26 @@ open class MockNetwork( entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), configOverrides: (NodeConfiguration) -> Any? = {}, extraCordappPackages: List = emptyList()): StartedMockNode { - val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, extraCordappPackages) + + 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: (NodeConfiguration) -> Any? = {}, + additionalCordapps: Set): StartedMockNode { + val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps) return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters))) } @@ -295,7 +339,26 @@ open class MockNetwork( entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), configOverrides: (NodeConfiguration) -> Any? = {}, extraCordappPackages: List = emptyList()): UnstartedMockNode { - val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, extraCordappPackages) + + return createUnstartedNode(legalName, forcedID, entropyRoot, configOverrides, cordappsForPackages(extraCordappPackages)) + } + + /** + * 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 additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork]. + */ + fun createUnstartedNode(legalName: CordaX500Name? = null, + forcedID: Int? = null, + entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), + configOverrides: (NodeConfiguration) -> Any? = {}, + additionalCordapps: Set = emptySet()): UnstartedMockNode { + val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps) return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters))) } 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 c8416bb862..816a30cdc8 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 @@ -28,9 +28,10 @@ import net.corda.core.node.services.* import net.corda.core.serialization.SerializeAsToken import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort +import net.corda.node.cordapp.CordappLoader import net.corda.node.internal.ServicesForResolutionImpl import net.corda.node.internal.configureDatabase -import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.services.api.* import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.schema.HibernateObserver @@ -72,7 +73,15 @@ open class MockServices private constructor( private val initialIdentity: TestIdentity, private val moreKeys: Array ) : ServiceHub { + companion object { + + private fun cordappLoaderForPackages(packages: Iterable): CordappLoader { + + val cordappPaths = TestCordappDirectories.forPackages(packages) + return JarScanningCordappLoader.fromDirectories(cordappPaths) + } + /** * Make properties appropriate for creating a DataSource for unit tests. * Defaults configuration of in-memory H2 instance. If 'databaseProvider' system property is set then creates @@ -102,7 +111,7 @@ open class MockServices private constructor( initialIdentity: TestIdentity, networkParameters: NetworkParameters = testNetworkParameters(), vararg moreKeys: KeyPair): Pair { - val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages) + val cordappLoader = cordappLoaderForPackages(cordappPackages) val dataSourceProps = makeInternalTestDataSourceProperties(initialIdentity.name.organisation, SecureHash.randomSHA256().toString()) val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(initialIdentity.name.organisation), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService) @@ -124,14 +133,6 @@ open class MockServices private constructor( return Pair(database, mockService) } - @JvmStatic - private fun getCallerPackage(): String { - // TODO: In Java 9 there's a new stack walker API that is better than this. - // The magic number '3' here is to chop off this method, an invisible bridge method generated by the - // compiler and then the c'tor itself. - return Throwable().stackTrace[3].className.split('.').dropLast(1).joinToString(".") - } - // Because Kotlin is dumb and makes not publicly visible objects public, thus changing the public API. private val mockStateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage() } @@ -155,39 +156,38 @@ open class MockServices private constructor( * (you can get one from [makeTestIdentityService]) and represents the given identity. */ @JvmOverloads - constructor(cordappPackages: List, + constructor(cordappPackages: Iterable, initialIdentity: TestIdentity, identityService: IdentityService = makeTestIdentityService(), vararg moreKeys: KeyPair) : - this(CordappLoader.createWithTestPackages(cordappPackages), identityService, testNetworkParameters(), initialIdentity, moreKeys) + this(cordappLoaderForPackages(cordappPackages), identityService, testNetworkParameters(), initialIdentity, moreKeys) - constructor(cordappPackages: List, + constructor(cordappPackages: Iterable, initialIdentity: TestIdentity, identityService: IdentityService, networkParameters: NetworkParameters, vararg moreKeys: KeyPair) : - this(CordappLoader.createWithTestPackages(cordappPackages), identityService, networkParameters, initialIdentity, moreKeys) + this(cordappLoaderForPackages(cordappPackages), identityService, networkParameters, initialIdentity, moreKeys) /** * Create a mock [ServiceHub] that looks for app code in the given package names, uses the provided identity service * (you can get one from [makeTestIdentityService]) and represents the given identity. */ @JvmOverloads - constructor(cordappPackages: List, initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService(), key: KeyPair, vararg moreKeys: KeyPair) : + constructor(cordappPackages: Iterable, initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService(), key: KeyPair, vararg moreKeys: KeyPair) : this(cordappPackages, TestIdentity(initialIdentityName, key), identityService, *moreKeys) - /** * Create a mock [ServiceHub] that can't load CorDapp code, which uses the provided identity service * (you can get one from [makeTestIdentityService]) and which represents the given identity. */ @JvmOverloads - constructor(cordappPackages: List, initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService()) : + constructor(cordappPackages: Iterable, initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService()) : this(cordappPackages, TestIdentity(initialIdentityName), identityService) /** * Create a mock [ServiceHub] that can't load CorDapp code, and which uses a default service identity. */ - constructor(cordappPackages: List) : this(cordappPackages, CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService()) + constructor(cordappPackages: Iterable) : this(cordappPackages, CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService()) /** * Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service @@ -195,7 +195,7 @@ open class MockServices private constructor( */ @JvmOverloads constructor(initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService(), key: KeyPair, vararg moreKeys: KeyPair) - : this(listOf(getCallerPackage()), TestIdentity(initialIdentityName, key), identityService, *moreKeys) + : this(listOf(getCallerPackage(MockServices::class)!!), TestIdentity(initialIdentityName, key), identityService, *moreKeys) /** * Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service @@ -203,7 +203,7 @@ open class MockServices private constructor( */ @JvmOverloads constructor(initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService()) - : this(listOf(getCallerPackage()), TestIdentity(initialIdentityName), identityService) + : this(listOf(getCallerPackage(MockServices::class)!!), TestIdentity(initialIdentityName), identityService) /** * A helper constructor that requires at least one test identity to be registered, and which takes the package of @@ -212,7 +212,7 @@ open class MockServices private constructor( * it is aware of. */ constructor(firstIdentity: TestIdentity, vararg moreIdentities: TestIdentity) : this( - listOf(getCallerPackage()), + listOf(getCallerPackage(MockServices::class)!!), firstIdentity, makeTestIdentityService(*listOf(firstIdentity, *moreIdentities).map { it.identity }.toTypedArray()), firstIdentity.keyPair @@ -222,7 +222,7 @@ open class MockServices private constructor( * Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses a default service * identity. */ - constructor() : this(listOf(getCallerPackage()), CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService()) + constructor() : this(listOf(getCallerPackage(MockServices::class)!!), CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService()) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { txs.forEach { @@ -241,7 +241,9 @@ open class MockServices private constructor( return NodeInfo(listOf(NetworkHostAndPort("mock.node.services", 10000)), listOf(initialIdentity.identity), 1, serial = 1L) } override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2) + private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments, networkParameters.whitelistedContractImplementations) + override val cordappProvider: CordappProvider get() = mockCordappProvider protected val servicesForResolution: ServicesForResolution get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParameters, validatedTransactions) 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 985e71b059..8f064ed587 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 @@ -62,6 +62,7 @@ import net.corda.testing.node.NotarySpec import net.corda.testing.node.User import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.NON_VALIDATING_RAFT import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.VALIDATING_RAFT +import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages import okhttp3.OkHttpClient import okhttp3.Request import rx.Subscription @@ -95,20 +96,19 @@ class DriverDSLImpl( val isDebug: Boolean, val startNodesInProcess: Boolean, val waitForAllNodesToFinish: Boolean, - extraCordappPackagesToScan: List, val jmxPolicy: JmxPolicy, val notarySpecs: List, val compatibilityZone: CompatibilityZoneParams?, val networkParameters: NetworkParameters, val notaryCustomOverrides: Map, - val inMemoryDB: Boolean + val inMemoryDB: Boolean, + val cordappsForAllNodes: Set ) : InternalDriverDSL { private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! private var _shutdownManager: ShutdownManager? = null override val shutdownManager get() = _shutdownManager!! - private val cordappPackages = extraCordappPackagesToScan + getCallerPackage() // Map from a nodes legal name to an observable emitting the number of nodes in its network map. private val networkVisibilityController = NetworkVisibilityController() /** @@ -206,26 +206,7 @@ class DriverDSLImpl( } } - override fun startNode( - defaultParameters: NodeParameters, - providedName: CordaX500Name?, - rpcUsers: List, - verifierType: VerifierType, - customOverrides: Map, - startInSameProcess: Boolean?, - maximumHeapSize: String - ): CordaFuture { - return startNode( - defaultParameters, - providedName, - rpcUsers, - verifierType, - customOverrides, - startInSameProcess, - maximumHeapSize, - null - ) - } + override fun startNode(defaultParameters: NodeParameters, providedName: CordaX500Name?, rpcUsers: List, verifierType: VerifierType, customOverrides: Map, startInSameProcess: Boolean?, maximumHeapSize: String) = startNode(defaultParameters, providedName, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, defaultParameters.additionalCordapps, defaultParameters.regenerateCordappsOnStart, null) override fun startNode( defaultParameters: NodeParameters, @@ -235,6 +216,20 @@ class DriverDSLImpl( customOverrides: Map, startInSameProcess: Boolean?, maximumHeapSize: String, + additionalCordapps: Set, + regenerateCordappsOnStart: Boolean + ) = startNode(defaultParameters, providedName, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, additionalCordapps, regenerateCordappsOnStart, null) + + override fun startNode( + defaultParameters: NodeParameters, + providedName: CordaX500Name?, + rpcUsers: List, + verifierType: VerifierType, + customOverrides: Map, + startInSameProcess: Boolean?, + maximumHeapSize: String, + additionalCordapps: Set, + regenerateCordappsOnStart: Boolean, bytemanPort: Int? ): CordaFuture { val p2pAddress = portAllocation.nextHostAndPort() @@ -251,7 +246,7 @@ class DriverDSLImpl( return registrationFuture.flatMap { networkMapAvailability.flatMap { // But starting the node proper does require the network map - startRegisteredNode(name, it, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, p2pAddress, bytemanPort) + startRegisteredNode(name, it, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, p2pAddress, additionalCordapps, regenerateCordappsOnStart, bytemanPort) } } } @@ -264,6 +259,8 @@ class DriverDSLImpl( startInSameProcess: Boolean? = null, maximumHeapSize: String = "512m", p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(), + additionalCordapps: Set = emptySet(), + regenerateCordappsOnStart: Boolean = false, bytemanPort: Int? = null): CordaFuture { val rpcAddress = portAllocation.nextHostAndPort() val rpcAdminAddress = portAllocation.nextHostAndPort() @@ -293,7 +290,7 @@ class DriverDSLImpl( allowMissingConfig = true, configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true) )).checkAndOverrideForInMemoryDB() - return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize, localNetworkMap, bytemanPort) + return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize, localNetworkMap, additionalCordapps, regenerateCordappsOnStart, bytemanPort) } private fun startNodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture { @@ -347,6 +344,7 @@ class DriverDSLImpl( NON_VALIDATING_BFT(false, CordaX500Name("BFT", "Zurich", "CH")) } + // TODO remove this internal fun startCordformNodes(cordforms: List): CordaFuture<*> { check(notarySpecs.isEmpty()) { "Specify notaries in the CordformDefinition" } check(compatibilityZone == null) { "Cordform nodes cannot be run with compatibilityZoneURL" } @@ -404,6 +402,7 @@ class DriverDSLImpl( }.transpose() } + // TODO remove this private fun startCordformNode(cordform: CordformNode, localNetworkMap: LocalNetworkMap): CordaFuture { val name = CordaX500Name.parse(cordform.name) // TODO We shouldn't have to allocate an RPC or web address if they're not specified. We're having to do this because of startNodeInternal @@ -432,9 +431,8 @@ class DriverDSLImpl( allowMissingConfig = true, configOverrides = rawConfig.toNodeOnly() ) - val cordaConfig = typesafe.parseAsNodeConfiguration() - val config = NodeConfig(rawConfig, cordaConfig).checkAndOverrideForInMemoryDB() - return startNodeInternal(config, webAddress, null, "512m", localNetworkMap, null) + val config = NodeConfig(typesafe).checkAndOverrideForInMemoryDB() + return startNodeInternal(config, webAddress, null, "512m", localNetworkMap, emptySet()) } @Suppress("DEPRECATION") @@ -656,7 +654,6 @@ class DriverDSLImpl( bytemanJarPath, null, systemProperties, - cordappPackages, "512m", *extraCmdLineFlag ) @@ -666,14 +663,20 @@ class DriverDSLImpl( } } - private fun startNodeInternal(config: NodeConfig, + private val sharedCordappsDirectories: Iterable by lazy { + TestCordappDirectories.cached(cordappsForAllNodes) + } + + private fun startNodeInternal(specifiedConfig: NodeConfig, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String, localNetworkMap: LocalNetworkMap?, - bytemanPort: Int?): CordaFuture { - val visibilityHandle = networkVisibilityController.register(config.corda.myLegalName) - val baseDirectory = config.corda.baseDirectory.createDirectories() + additionalCordapps: Set, + regenerateCordappsOnStart: Boolean = false, + bytemanPort: Int? = null): CordaFuture { + val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName) + val baseDirectory = specifiedConfig.corda.baseDirectory.createDirectories() localNetworkMap?.networkParametersCopier?.install(baseDirectory) localNetworkMap?.nodeInfosCopier?.addConfig(baseDirectory) @@ -682,10 +685,16 @@ class DriverDSLImpl( visibilityHandle.close() } - val useHTTPS = config.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") } + val useHTTPS = specifiedConfig.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") } + + val existingCorDappDirectoriesOption = if (regenerateCordappsOnStart) emptyList() else if (specifiedConfig.typesafe.hasPath(NodeConfiguration.cordappDirectoriesKey)) specifiedConfig.typesafe.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList() + + val cordappDirectories = existingCorDappDirectoriesOption + sharedCordappsDirectories.map { it.toString() } + TestCordappDirectories.cached(additionalCordapps, regenerateCordappsOnStart).map { it.toString() } + + val config = NodeConfig(specifiedConfig.typesafe.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories))) if (startInProcess ?: startNodesInProcess) { - val nodeAndThreadFuture = startInProcessNode(executorService, config, cordappPackages) + val nodeAndThreadFuture = startInProcessNode(executorService, config) shutdownManager.registerShutdown( nodeAndThreadFuture.map { (node, thread) -> { @@ -712,7 +721,7 @@ class DriverDSLImpl( } else { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null - val process = startOutOfProcessNode(config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, bytemanJarPath, bytemanPort, systemProperties, cordappPackages, maximumHeapSize) + val process = startOutOfProcessNode(config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, bytemanJarPath, bytemanPort, systemProperties, maximumHeapSize) // Destroy the child process when the parent exits.This is needed even when `waitForAllNodesToFinish` is // true because we don't want orphaned processes in the case that the parent process is terminated by the @@ -806,10 +815,12 @@ class DriverDSLImpl( private fun oneOf(array: Array) = array[Random().nextInt(array.size)] + fun cordappsInCurrentAndAdditionalPackages(packagesToScan: Iterable = emptySet()): Set = cordappsForPackages(getCallerPackage() + packagesToScan) + fun cordappsInCurrentAndAdditionalPackages(firstPackage: String, vararg otherPackages: String): Set = cordappsInCurrentAndAdditionalPackages(otherPackages.toList() + firstPackage) + private fun startInProcessNode( executorService: ScheduledExecutorService, - config: NodeConfig, - cordappPackages: List + config: NodeConfig ): CordaFuture, Thread>> { return executorService.fork { log.info("Starting in-process Node ${config.corda.myLegalName.organisation}") @@ -819,7 +830,7 @@ class DriverDSLImpl( // Write node.conf writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe.toNodeOnly()) // TODO pass the version in? - val node = InProcessNode(config.corda, MOCK_VERSION_INFO, cordappPackages).start() + val node = InProcessNode(config.corda, MOCK_VERSION_INFO).start() val nodeThread = thread(name = config.corda.myLegalName.organisation) { node.internals.run() } @@ -838,10 +849,10 @@ class DriverDSLImpl( bytemanJarPath: String?, bytemanPort: Int?, overriddenSystemProperties: Map, - cordappPackages: List, maximumHeapSize: String, vararg extraCmdLineFlag: String ): Process { + log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " + "debug port is " + (debugPort ?: "not enabled") + ", " + "jolokia monitoring port is " + (monitorPort ?: "not enabled") + ", " + @@ -856,11 +867,6 @@ class DriverDSLImpl( ) systemProperties += inheritFromParentProcess() - - if (cordappPackages.isNotEmpty()) { - systemProperties += Node.scanPackagesSystemProperty to cordappPackages.joinToString(Node.scanPackagesSeparator) - } - systemProperties += overriddenSystemProperties // See experimental/quasar-hook/README.md for how to generate. @@ -1075,6 +1081,8 @@ interface InternalDriverDSL : DriverDSL, CordformContext { customOverrides: Map = defaultParameters.customOverrides, startInSameProcess: Boolean? = defaultParameters.startInSameProcess, maximumHeapSize: String = defaultParameters.maximumHeapSize, + additionalCordapps: Set = defaultParameters.additionalCordapps, + regenerateCordappsOnStart: Boolean = defaultParameters.regenerateCordappsOnStart, bytemanPort: Int? = null ): CordaFuture } @@ -1132,13 +1140,13 @@ 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 + inMemoryDB = defaultParameters.inMemoryDB, + cordappsForAllNodes = defaultParameters.cordappsForAllNodes() ) ) val shutdownHook = addShutdownHook(driverDsl::shutdown) @@ -1212,12 +1220,12 @@ fun internalDriver( startNodesInProcess: Boolean = DriverParameters().startNodesInProcess, waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish, notarySpecs: List = DriverParameters().notarySpecs, - extraCordappPackagesToScan: List = DriverParameters().extraCordappPackagesToScan, jmxPolicy: JmxPolicy = DriverParameters().jmxPolicy, networkParameters: NetworkParameters = DriverParameters().networkParameters, compatibilityZone: CompatibilityZoneParams? = null, notaryCustomOverrides: Map = DriverParameters().notaryCustomOverrides, inMemoryDB: Boolean = DriverParameters().inMemoryDB, + cordappsForAllNodes: Set = DriverParameters().cordappsForAllNodes(), dsl: DriverDSLImpl.() -> A ): A { return genericDriver( @@ -1231,12 +1239,12 @@ fun internalDriver( startNodesInProcess = startNodesInProcess, waitForAllNodesToFinish = waitForAllNodesToFinish, notarySpecs = notarySpecs, - extraCordappPackagesToScan = extraCordappPackagesToScan, jmxPolicy = jmxPolicy, compatibilityZone = compatibilityZone, networkParameters = networkParameters, notaryCustomOverrides = notaryCustomOverrides, - inMemoryDB = inMemoryDB + inMemoryDB = inMemoryDB, + cordappsForAllNodes = cordappsForAllNodes ), coerce = { it }, dsl = dsl, @@ -1257,6 +1265,8 @@ private fun Config.toNodeOnly(): Config { return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this } +internal fun DriverParameters.cordappsForAllNodes(): Set = cordappsForAllNodes ?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan) + fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture { var customOverrides = emptyMap() if (!devMode) { 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 d41dfee363..3b417bac8a 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 @@ -22,10 +22,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.VisibleForTesting -import net.corda.core.internal.createDirectories -import net.corda.core.internal.createDirectory -import net.corda.core.internal.uncheckedCast +import net.corda.core.internal.* import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient @@ -35,14 +32,12 @@ import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SerializationWhitelist -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.contextLogger -import net.corda.core.utilities.hours -import net.corda.core.utilities.seconds +import net.corda.core.utilities.* import net.corda.node.VersionInfo +import net.corda.node.cordapp.CordappLoader import net.corda.node.internal.AbstractNode import net.corda.node.internal.StartedNode -import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.services.api.NodePropertiesStore import net.corda.node.services.api.SchemaService import net.corda.node.services.config.* @@ -58,16 +53,17 @@ import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.driver.TestCorDapp import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.setGlobalSerialization import net.corda.testing.internal.testThreadFactory import net.corda.testing.node.* -import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.sshd.common.util.security.SecurityUtils import rx.internal.schedulers.CachedThreadScheduler import java.math.BigInteger import java.nio.file.Path +import java.nio.file.Paths import java.security.KeyPair import java.security.PublicKey import java.time.Clock @@ -85,8 +81,7 @@ data class MockNodeArgs( val network: InternalMockNetwork, val id: Int, val entropyRoot: BigInteger, - val version: VersionInfo = MOCK_VERSION_INFO, - val extraCordappPackages: List = emptyList() + val version: VersionInfo = MOCK_VERSION_INFO ) data class InternalMockNodeParameters( @@ -95,25 +90,26 @@ data class InternalMockNodeParameters( val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val configOverrides: (NodeConfiguration) -> Any? = {}, val version: VersionInfo = MOCK_VERSION_INFO, - val extraCordappPackages: List = emptyList()) { + val additionalCordapps: Set? = null) { constructor(mockNodeParameters: MockNodeParameters) : this( mockNodeParameters.forcedID, mockNodeParameters.legalName, mockNodeParameters.entropyRoot, mockNodeParameters.configOverrides, MOCK_VERSION_INFO, - mockNodeParameters.extraCordappPackages + mockNodeParameters.additionalCordapps ) } -open class InternalMockNetwork(private val cordappPackages: List = emptyList(), - defaultParameters: MockNetworkParameters = MockNetworkParameters(), +open class InternalMockNetwork(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()), networkParameters: NetworkParameters = testNetworkParameters(), - val defaultFactory: (MockNodeArgs) -> MockNode = InternalMockNetwork::MockNode) { + val defaultFactory: (MockNodeArgs, CordappLoader?) -> MockNode = { args, cordappLoader -> cordappLoader?.let { MockNode(args, it) } ?: MockNode(args) }, + val cordappsForAllNodes: Set = emptySet()) { init { // Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS. // This SFTP support loads BouncyCastle, which we want to avoid. @@ -122,6 +118,10 @@ open class InternalMockNetwork(private val cordappPackages: List = empty require(networkParameters.notaries.isEmpty()) { "Define notaries using notarySpecs" } } + private companion object { + private val logger = loggerFor() + } + var nextNodeId = 0 private set private val filesystem = Jimfs.newFileSystem(unix()) @@ -138,6 +138,10 @@ open class InternalMockNetwork(private val cordappPackages: List = empty } private val sharedUserCount = AtomicInteger(0) + private val sharedCorDappsDirectories: Iterable by lazy { + TestCordappDirectories.cached(cordappsForAllNodes) + } + /** A read only view of the current set of nodes. */ val nodes: List get() = _nodes @@ -230,12 +234,11 @@ open class InternalMockNetwork(private val cordappPackages: List = empty } } - open class MockNode(args: MockNodeArgs) : AbstractNode( + open class MockNode(args: MockNodeArgs, cordappLoader: CordappLoader = JarScanningCordappLoader.fromDirectories(args.config.cordappDirectories)) : AbstractNode( args.config, TestClock(Clock.systemUTC()), args.version, - // Add the specified additional CorDapps. - CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages + args.extraCordappPackages), + cordappLoader, args.network.busyLatch ) { companion object { @@ -365,7 +368,7 @@ open class InternalMockNetwork(private val cordappPackages: List = empty return createUnstartedNode(parameters, defaultFactory) } - fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): N { + fun createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs, CordappLoader?) -> N): N { return createNodeImpl(parameters, nodeFactory, false) } @@ -374,11 +377,11 @@ open class InternalMockNetwork(private val cordappPackages: List = empty } /** Like the other [createNode] but takes a [nodeFactory] and propagates its [MockNode] subtype. */ - fun createNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): StartedNode { + fun createNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs, CordappLoader?) -> N): StartedNode { return uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!! } - private fun createNodeImpl(parameters: InternalMockNodeParameters, nodeFactory: (MockNodeArgs) -> N, start: Boolean): N { + private fun createNodeImpl(parameters: InternalMockNodeParameters, nodeFactory: (MockNodeArgs, CordappLoader?) -> N, start: Boolean): N { val id = parameters.forcedID ?: nextNodeId++ val config = mockNodeConfiguration().also { doReturn(baseDirectory(id).createDirectories()).whenever(it).baseDirectory @@ -388,7 +391,12 @@ open class InternalMockNetwork(private val cordappPackages: List = empty doReturn(emptyList()).whenever(it).extraNetworkMapKeys parameters.configOverrides(it) } - val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version, parameters.extraCordappPackages)) + + val cordapps: Set = parameters.additionalCordapps ?: emptySet() + val cordappDirectories = sharedCorDappsDirectories + TestCordappDirectories.cached(cordapps) + doReturn(cordappDirectories).whenever(config).cordappDirectories + + val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version), JarScanningCordappLoader.fromDirectories(cordappDirectories)) _nodes += node if (start) { node.start() @@ -396,7 +404,7 @@ open class InternalMockNetwork(private val cordappPackages: List = empty return node } - fun restartNode(node: StartedNode, nodeFactory: (MockNodeArgs) -> N): StartedNode { + fun restartNode(node: StartedNode, nodeFactory: (MockNodeArgs, CordappLoader?) -> N): StartedNode { node.internals.disableDBCloseOnStop() node.dispose() return createNode( @@ -407,7 +415,7 @@ open class InternalMockNetwork(private val cordappPackages: List = empty fun restartNode(node: StartedNode): StartedNode = restartNode(node, defaultFactory) - fun baseDirectory(nodeId: Int): Path = filesystem.getPath("/nodes/$nodeId") + fun baseDirectory(nodeId: Int): Path = testDirectory / "nodes/$nodeId" /** * Asks every node in order to process any queued up inbound messages. This may in turn result in nodes @@ -465,7 +473,6 @@ open class InternalMockNetwork(private val cordappPackages: List = empty fun waitQuiescent() { busyLatch.await() } - } open class MessagingServiceSpy(val messagingService: MessagingService) : MessagingService by messagingService diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/MutableTestCorDapp.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/MutableTestCorDapp.kt new file mode 100644 index 0000000000..28284e5de2 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/MutableTestCorDapp.kt @@ -0,0 +1,73 @@ +package net.corda.testing.node.internal + +import net.corda.core.internal.div +import net.corda.testing.driver.TestCorDapp +import java.io.File +import java.net.URL +import java.nio.file.Path + +internal class MutableTestCorDapp private constructor(override val name: String, override val version: String, override val vendor: String, override val title: String, private val willResourceBeAddedToCorDapp: (String, URL) -> Boolean, private val jarEntries: Set) : TestCorDapp.Mutable { + + constructor(name: String, version: String, vendor: String, title: String, classes: Set>, willResourceBeAddedToCorDapp: (String, URL) -> Boolean) : this(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntriesFromClasses(classes)) + + companion object { + + private const val jarExtension = ".jar" + private const val whitespace = " " + private const val whitespaceReplacement = "_" + private val productionPathSegments = setOf<(String) -> String>({ "out${File.separator}production${File.separator}classes" }, { fullyQualifiedName -> "main${File.separator}${fullyQualifiedName.packageToPath()}" }) + private val excludedCordaPackages = setOf("net.corda.core", "net.corda.node") + + fun filterTestCorDappClass(fullyQualifiedName: String, url: URL): Boolean { + + return isTestResource(fullyQualifiedName, url) || !isInExcludedCordaPackage(fullyQualifiedName) + } + + private fun isTestResource(fullyQualifiedName: String, url: URL): Boolean { + + return productionPathSegments.map { it.invoke(fullyQualifiedName) }.none { url.toString().contains(it) } + } + + private fun isInExcludedCordaPackage(packageName: String): Boolean { + + return excludedCordaPackages.any { packageName.startsWith(it) } + } + + private fun jarEntriesFromClasses(classes: Set>): Set { + + val illegal = classes.filter { it.protectionDomain?.codeSource?.location == null } + if (illegal.isNotEmpty()) { + throw IllegalArgumentException("Some classes do not have a location, typically because they are part of Java or Kotlin. Offending types were: ${illegal.joinToString(", ", "[", "]") { it.simpleName }}") + } + return classes.map(Class<*>::jarEntryInfo).toSet() + } + } + + override val classes: Set> = jarEntries.filterIsInstance(JarEntryInfo.ClassJarEntryInfo::class.java).map(JarEntryInfo.ClassJarEntryInfo::clazz).toSet() + + override val resources: Set = jarEntries.map(JarEntryInfo::url).toSet() + + override fun withName(name: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp) + + override fun withTitle(title: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp) + + override fun withVersion(version: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp) + + override fun withVendor(vendor: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp) + + override fun withClasses(classes: Set>) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp) + + override fun plusPackages(pckgs: Set) = withClasses(pckgs.map { allClassesForPackage(it) }.fold(classes) { all, packageClasses -> all + packageClasses }) + + override fun minusPackages(pckgs: Set) = withClasses(pckgs.map { allClassesForPackage(it) }.fold(classes) { all, packageClasses -> all - packageClasses }) + + override fun plusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable = MutableTestCorDapp(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntries + JarEntryInfo.ResourceJarEntryInfo(fullyQualifiedName, url)) + + override fun minusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable = MutableTestCorDapp(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntries - JarEntryInfo.ResourceJarEntryInfo(fullyQualifiedName, url)) + + override fun packageAsJarWithPath(jarFilePath: Path) = jarEntries.packageToCorDapp(jarFilePath, name, version, vendor, title, willResourceBeAddedToCorDapp) + + override fun packageAsJarInDirectory(parentDirectory: Path): Path = (parentDirectory / defaultJarName()).also { packageAsJarWithPath(it) } + + private fun defaultJarName(): String = "${name}_$version$jarExtension".replace(whitespace, whitespaceReplacement) +} \ No newline at end of file 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 8026652213..be906b05e9 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 @@ -10,6 +10,7 @@ package net.corda.testing.node.internal +import com.typesafe.config.ConfigValueFactory import net.corda.core.identity.CordaX500Name import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.transpose @@ -17,11 +18,11 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.node.NodeInfo import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.loggerFor import net.corda.node.VersionInfo import net.corda.node.internal.EnterpriseNode import net.corda.node.internal.Node import net.corda.node.internal.StartedNode -import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.config.* import net.corda.nodeapi.internal.config.toConfig import net.corda.nodeapi.internal.network.NetworkParametersCopier @@ -41,10 +42,12 @@ import java.nio.file.Path import java.util.concurrent.Executors import kotlin.concurrent.thread -// TODO Some of the logic here duplicates what's in the driver +// TODO Some of the logic here duplicates what's in the driver - the reason why it's not straightforward to replace it by using DriverDSLImpl in `init()` and `stopAllNodes()` is because of the platform version passed to nodes (driver doesn't support this, and it's a property of the Corda JAR) abstract class NodeBasedTest(private val cordappPackages: List = emptyList()) : IntegrationTest() { companion object { private val WHITESPACE = "\\s++".toRegex() + + private val logger = loggerFor() } @Rule @@ -112,15 +115,24 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi ) + configOverrides ) - val parsedConfig = config.parseAsNodeConfiguration().also { nodeConfiguration -> + val cordapps = cordappsForPackages(getCallerPackage(NodeBasedTest::class)?.let { cordappPackages + it } ?: cordappPackages) + + val existingCorDappDirectoriesOption = if (config.hasPath(NodeConfiguration.cordappDirectoriesKey)) config.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList() + + val cordappDirectories = existingCorDappDirectoriesOption + TestCordappDirectories.cached(cordapps).map { it.toString() } + + val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories)) + + val parsedConfig = specificConfig.parseAsNodeConfiguration().also { nodeConfiguration -> val errors = nodeConfiguration.validate() if (errors.isNotEmpty()) { throw IllegalStateException("Invalid node configuration. Errors where:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}") } } + defaultNetworkParameters.install(baseDirectory) - return InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), cordappPackages) + return InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion)) } @JvmOverloads @@ -153,8 +165,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi } } -class InProcessNode( - configuration: NodeConfiguration, versionInfo: VersionInfo, cordappPackages: List) : EnterpriseNode( - configuration, versionInfo, false, CordappLoader.createDefaultWithTestPackages(configuration, cordappPackages)) { +class InProcessNode(configuration: NodeConfiguration, versionInfo: VersionInfo) : EnterpriseNode(configuration, versionInfo, false) { + override fun getRxIoScheduler() = CachedThreadScheduler(testThreadFactory()).also { runOnStop += it::shutdown } } 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 0e9c2849eb..e3100b2840 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 @@ -37,8 +37,10 @@ import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.MAX_MESSAGE_SIZE import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.PortAllocation +import net.corda.testing.driver.TestCorDapp import net.corda.testing.node.NotarySpec 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 @@ -119,13 +121,13 @@ fun rpcDriver( useTestClock: Boolean = false, startNodesInProcess: Boolean = false, waitForNodesToFinish: Boolean = false, - extraCordappPackagesToScan: List = emptyList(), notarySpecs: List = emptyList(), externalTrace: Trace? = null, jmxPolicy: JmxPolicy = JmxPolicy(), networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()), notaryCustomOverrides: Map = emptyMap(), inMemoryDB: Boolean = true, + cordappsForAllNodes: Set = cordappsInCurrentAndAdditionalPackages(), dsl: RPCDriverDSL.() -> A ): A { return genericDriver( @@ -139,13 +141,13 @@ fun rpcDriver( isDebug = isDebug, startNodesInProcess = startNodesInProcess, waitForAllNodesToFinish = waitForNodesToFinish, - extraCordappPackagesToScan = extraCordappPackagesToScan, notarySpecs = notarySpecs, jmxPolicy = jmxPolicy, compatibilityZone = null, networkParameters = networkParameters, notaryCustomOverrides = notaryCustomOverrides, - inMemoryDB = inMemoryDB + inMemoryDB = inMemoryDB, + 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 new file mode 100644 index 0000000000..6f261817d2 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappDirectories.kt @@ -0,0 +1,70 @@ +package net.corda.testing.node.internal + +import net.corda.core.internal.createDirectories +import net.corda.core.internal.deleteRecursively +import net.corda.core.internal.div +import net.corda.core.internal.exists +import net.corda.core.utilities.loggerFor +import net.corda.testing.driver.TestCorDapp +import java.nio.file.Path +import java.nio.file.Paths +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap + +internal object TestCordappDirectories { + + private val logger = loggerFor() + + private const val whitespace = " " + private const val whitespaceReplacement = "_" + + private val cordappsCache: ConcurrentMap, Path> = ConcurrentHashMap, Path>() + + internal fun cached(cordapps: Iterable, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Iterable { + + cordappsDirectory.toFile().deleteOnExit() + return cordapps.map { cached(it, replaceExistingOnes, cordappsDirectory) } + } + + internal fun cached(cordapp: TestCorDapp, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Path { + + cordappsDirectory.toFile().deleteOnExit() + val cacheKey = cordapp.resources.map { it.toExternalForm() }.sorted() + return if (replaceExistingOnes) { + cordappsCache.remove(cacheKey) + cordappsCache.getOrPut(cacheKey) { + + val cordappDirectory = (cordappsDirectory / "${cordapp.name}_${cordapp.version}".replace(whitespace, whitespaceReplacement)).toAbsolutePath() + cordappDirectory.createDirectories() + cordapp.packageAsJarInDirectory(cordappDirectory) + cordappDirectory + } + } else { + cordappsCache.getOrPut(cacheKey) { + + val cordappDirectory = (cordappsDirectory / "${cordapp.name}_${cordapp.version}".replace(whitespace, whitespaceReplacement)).toAbsolutePath() + cordappDirectory.createDirectories() + cordapp.packageAsJarInDirectory(cordappDirectory) + cordappDirectory + } + } + } + + internal fun forPackages(packages: Iterable, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Iterable { + + cordappsDirectory.toFile().deleteOnExit() + val cordapps = simplifyScanPackages(packages).distinct().fold(emptySet()) { all, packageName -> all + testCorDapp(packageName) } + return cached(cordapps, replaceExistingOnes, cordappsDirectory) + } + + private val defaultCordappsDirectory: Path by lazy { + + val cordappsDirectory = (Paths.get("build") / "tmp" / getTimestampAsDirectoryName() / "generated-test-cordapps").toAbsolutePath() + logger.info("Initialising generated test CorDapps directory in $cordappsDirectory") + if (cordappsDirectory.exists()) { + cordappsDirectory.deleteRecursively() + } + cordappsDirectory.createDirectories() + cordappsDirectory + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..2ac02f8c92 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt @@ -0,0 +1,214 @@ +package net.corda.testing.node.internal + +import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner +import net.corda.core.internal.createDirectories +import net.corda.core.internal.deleteIfExists +import net.corda.core.internal.outputStream +import net.corda.node.internal.cordapp.createTestManifest +import net.corda.testing.driver.TestCorDapp +import org.apache.commons.io.IOUtils +import java.io.File +import java.io.OutputStream +import java.net.URI +import java.net.URL +import java.nio.file.Path +import java.nio.file.attribute.FileTime +import java.time.Instant +import java.util.* +import java.util.jar.JarOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import kotlin.reflect.KClass + +/** + * Packages some [JarEntryInfo] into a CorDapp JAR with specified [path]. + * @param path The path of the JAR. + * @param willResourceBeAddedBeToCorDapp A filter for the inclusion of [JarEntryInfo] in the JAR. + */ +internal fun Iterable.packageToCorDapp(path: Path, name: String, version: String, vendor: String, title: String = name, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean = { _, _ -> true }) { + + var hasContent = false + try { + hasContent = packageToCorDapp(path.outputStream(), name, version, vendor, title, willResourceBeAddedBeToCorDapp) + } finally { + if (!hasContent) { + path.deleteIfExists() + } + } +} + +/** + * Packages some [JarEntryInfo] into a CorDapp JAR using specified [outputStream]. + * @param outputStream The [OutputStream] for the JAR. + * @param willResourceBeAddedBeToCorDapp A filter for the inclusion of [JarEntryInfo] in the JAR. + */ +internal fun Iterable.packageToCorDapp(outputStream: OutputStream, name: String, version: String, vendor: String, title: String = name, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean = { _, _ -> true }): Boolean { + + val manifest = createTestManifest(name, title, version, vendor) + return JarOutputStream(outputStream, manifest).use { jos -> zip(jos, willResourceBeAddedBeToCorDapp) } +} + +/** + * Transforms a [Class] into a [JarEntryInfo]. + */ +internal fun Class<*>.jarEntryInfo(): JarEntryInfo { + + return JarEntryInfo.ClassJarEntryInfo(this) +} + +/** + * Packages some [TestCorDapp]s under a root [directory], each with it's own JAR. + * @param directory The parent directory in which CorDapp JAR will be created. + */ +fun Iterable.packageInDirectory(directory: Path) { + + directory.createDirectories() + forEach { cordapp -> cordapp.packageAsJarInDirectory(directory) } +} + +/** + * Returns all classes within the [targetPackage]. + */ +fun allClassesForPackage(targetPackage: String): Set> { + + val scanResult = FastClasspathScanner(targetPackage).strictWhitelist().scan() + return scanResult.namesOfAllClasses.filter { className -> className.startsWith(targetPackage) }.map(scanResult::classNameToClassRef).toSet() +} + +/** + * Maps each package to a [TestCorDapp] with resources found in that package. + */ +fun cordappsForPackages(packages: Iterable): Set { + + return simplifyScanPackages(packages).toSet().fold(emptySet()) { all, packageName -> all + testCorDapp(packageName) } +} + +/** + * Maps each package to a [TestCorDapp] with resources found in that package. + */ +fun cordappsForPackages(firstPackage: String, vararg otherPackages: String): Set { + + return cordappsForPackages(setOf(*otherPackages) + firstPackage) +} + +fun getCallerClass(directCallerClass: KClass<*>): Class<*>? { + + val stackTrace = Throwable().stackTrace + val index = stackTrace.indexOfLast { it.className == directCallerClass.java.name } + if (index == -1) return null + return try { + Class.forName(stackTrace[index + 1].className) + } catch (e: ClassNotFoundException) { + null + } +} + +fun getCallerPackage(directCallerClass: KClass<*>): String? { + + return getCallerClass(directCallerClass)?.`package`?.name +} + +/** + * Returns a [TestCorDapp] containing resources found in [packageName]. + */ +internal fun testCorDapp(packageName: String): TestCorDapp { + + val uuid = UUID.randomUUID() + val name = "$packageName-$uuid" + val version = "$uuid" + return TestCorDapp.Factory.create(name, version).plusPackage(packageName) +} + +/** + * Squashes child packages if the parent is present. Example: ["com.foo", "com.foo.bar"] into just ["com.foo"]. + */ +fun simplifyScanPackages(scanPackages: Iterable): List { + + return scanPackages.sorted().fold(emptyList()) { listSoFar, packageName -> + when { + listSoFar.isEmpty() -> listOf(packageName) + packageName.startsWith(listSoFar.last()) -> listSoFar + else -> listSoFar + packageName + } + } +} + +/** + * Transforms a class or package name into a path segment. + */ +internal fun String.packageToPath() = replace(".", File.separator) + +private fun Iterable.zip(outputStream: ZipOutputStream, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean): Boolean { + + val entries = filter { (fullyQualifiedName, url) -> willResourceBeAddedBeToCorDapp(fullyQualifiedName, url) } + if (entries.isNotEmpty()) { + zip(outputStream, entries) + } + return entries.isNotEmpty() +} + +private fun zip(outputStream: ZipOutputStream, allInfo: Iterable) { + + val time = FileTime.from(Instant.EPOCH) + val classLoader = Thread.currentThread().contextClassLoader + allInfo.distinctBy { it.url }.sortedBy { it.url.toExternalForm() }.forEach { info -> + + try { + val entry = ZipEntry(info.entryName).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time) + outputStream.putNextEntry(entry) + classLoader.getResourceAsStream(info.entryName).use { + IOUtils.copy(it, outputStream) + } + } finally { + outputStream.closeEntry() + } + } +} + +/** + * Represents a single resource to be added to a CorDapp JAR. + */ +internal sealed class JarEntryInfo(val fullyQualifiedName: String, val url: URL) { + + abstract val entryName: String + + /** + * Represents a class to be added to a CorDapp JAR. + */ + class ClassJarEntryInfo(val clazz: Class<*>) : JarEntryInfo(clazz.name, clazz.classFileURL()) { + + override val entryName = "${fullyQualifiedName.packageToPath()}$fileExtensionSeparator$classFileExtension" + } + + /** + * Represents a resource file to be added to a CorDapp JAR. + */ + class ResourceJarEntryInfo(fullyQualifiedName: String, url: URL) : JarEntryInfo(fullyQualifiedName, url) { + + override val entryName: String + get() { + val extensionIndex = fullyQualifiedName.lastIndexOf(fileExtensionSeparator) + return "${fullyQualifiedName.substring(0 until extensionIndex).packageToPath()}${fullyQualifiedName.substring(extensionIndex)}" + } + } + + operator fun component1(): String = fullyQualifiedName + + operator fun component2(): URL = url + + private companion object { + + private const val classFileExtension = "class" + private const val fileExtensionSeparator = "." + private const val whitespace = " " + private const val whitespaceReplacement = "%20" + + private fun Class<*>.classFileURL(): URL { + + require(protectionDomain?.codeSource?.location != null) { "Invalid class $name for test CorDapp. Classes without protection domain cannot be referenced. This typically happens for Java / Kotlin types." } + return URI.create("${protectionDomain.codeSource.location}/${name.packageToPath()}$fileExtensionSeparator$classFileExtension".escaped()).toURL() + } + + private fun String.escaped(): String = this.replace(whitespace, whitespaceReplacement) + } +} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt index 4c28221e42..ced7ca5d12 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/demorun/CordformNodeRunner.kt @@ -17,6 +17,7 @@ import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.PortAllocation +import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages import net.corda.testing.node.internal.internalDriver /** @@ -69,12 +70,12 @@ class CordformNodeRunner(private val cordformDefinition: CordformDefinition) { internalDriver( jmxPolicy = JmxPolicy(true), driverDirectory = cordformDefinition.nodesDirectory, - extraCordappPackagesToScan = extraPackagesToScan, // Notaries are manually specified in Cordform so we don't want the driver automatically starting any notarySpecs = emptyList(), // Start from after the largest port used to prevent port clash portAllocation = PortAllocation.Incremental(maxPort + 1), - waitForAllNodesToFinish = waitForAllNodesToFinish + waitForAllNodesToFinish = waitForAllNodesToFinish, + cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages(extraPackagesToScan) ) { cordformDefinition.setup(this) startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running diff --git a/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt index a81be3461d..8470ee2058 100644 --- a/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt +++ b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/InternalMockNetworkTests.kt @@ -19,7 +19,7 @@ class InternalMockNetworkTests { fun `does not leak serialization env if init fails`() { val e = Exception("didn't work") assertThatThrownBy { - object : InternalMockNetwork() { + object : InternalMockNetwork(cordappsForAllNodes = emptySet()) { override fun createNotaries() = throw e } }.isSameAs(e) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt index 412b7d2ca4..2f749395a1 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt @@ -17,7 +17,8 @@ import net.corda.core.internal.TEST_UPLOADER import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage -import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.cordapp.CordappLoader +import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.testing.services.MockAttachmentStorage import java.nio.file.Paths @@ -52,8 +53,7 @@ class MockCordappProvider( } } - override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? = cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second - ?: super.getContractAttachmentID(contractClassName) + override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? = cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second ?: super.getContractAttachmentID(contractClassName) private fun findOrImportAttachment(contractClassNames: List, data: ByteArray, attachments: MockAttachmentStorage): AttachmentId { val existingAttachment = attachments.files.filter { diff --git a/tools/dbmigration/src/main/kotlin/com/r3/corda/dbmigration/Launcher.kt b/tools/dbmigration/src/main/kotlin/com/r3/corda/dbmigration/Launcher.kt index 3054d6970c..0be8eb8c9d 100644 --- a/tools/dbmigration/src/main/kotlin/com/r3/corda/dbmigration/Launcher.kt +++ b/tools/dbmigration/src/main/kotlin/com/r3/corda/dbmigration/Launcher.kt @@ -23,7 +23,7 @@ import net.corda.core.internal.div import net.corda.core.internal.exists import net.corda.core.schemas.MappedSchema import net.corda.node.internal.DataSourceFactory.createDatasourceFromDriverJarFolders -import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.configOf import net.corda.node.services.config.parseAsNodeConfiguration @@ -133,7 +133,7 @@ private fun runCommand(options: OptionSet, parser: OptionParser) { errorAndExit("Not a valid node folder. Could not find the config file: '$config'.") } val nodeConfig = ConfigHelper.loadConfig(baseDirectory, config).parseAsNodeConfiguration() - val cordappLoader = CordappLoader.createDefault(baseDirectory) + val cordappLoader = JarScanningCordappLoader.fromDirectories(setOf(baseDirectory)) val schemaService = NodeSchemaService(extraSchemas = cordappLoader.cordappSchemas, includeNotarySchemas = nodeConfig.notary != null)