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/build.gradle b/build.gradle index c66c7cd5f8..6bfe76239e 100644 --- a/build.gradle +++ b/build.gradle @@ -169,7 +169,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') : false } } @@ -185,7 +185,7 @@ allprojects { } tasks.withType(Test) { - failFast = true + failFast = project.hasProperty('tests.failFast') ? project.property('tests.failFast') : false // Prevent the project from creating temporary files outside of the build directory. systemProperty 'java.io.tmpdir', buildDir.absolutePath diff --git a/client/rpc/.attach_pid14784 b/client/rpc/.attach_pid14784 new file mode 100644 index 0000000000..e69de29bb2 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 4550d91539..c094749d4a 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt @@ -19,6 +19,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 @@ -34,7 +35,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"), + cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset"), networkSendManuallyPumped = false, threadPerNode = true ) diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index bfd85d732c..8e23c5c01b 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -48,4 +48,20 @@ interface Cordapp { val jarPath: URL val cordappClasses: List val jarHash: SecureHash.SHA256 + + /** + * CorDapp's information, including vendor and version. + * + * @property shortName Cordapp's shortName + * @property vendor Cordapp's vendor + * @property version Cordapp's version + */ + @DoNotImplement + interface Info { + val shortName: String + val vendor: String + val version: String + + fun hasUnknownFields(): Boolean + } } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt index cf8ddc85a0..a8021e8663 100644 --- a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt @@ -33,4 +33,16 @@ data class CordappImpl( * TODO: Also add [SchedulableFlow] as a Cordapp class */ override val cordappClasses = ((rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames) + + data class Info(override val shortName: String, override val vendor: String, override val version: String): Cordapp.Info { + companion object { + private const val UNKNOWN_VALUE = "Unknown" + + val UNKNOWN = Info(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE) + } + + override fun hasUnknownFields(): Boolean { + return setOf(shortName, vendor, version).any { it == UNKNOWN_VALUE } + } + } } 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 10692820f7..fb4c37ab37 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -127,7 +127,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 81a6a4061a..e681fc210d 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -20,6 +20,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 @@ -27,7 +28,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 dfc4d89038..1426bad37a 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -25,6 +25,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 @@ -32,10 +33,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 b352d3f49f..9b6d8baee9 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -14,13 +14,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 ac5116f938..1f70f0b01c 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -161,7 +161,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 16bb1e8481..b8baf0a528 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,6 +6,8 @@ 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`` instead. 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 b43207c56b..62e8825ec6 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 @@ -11,18 +11,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 { @@ -60,7 +67,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 } @@ -82,4 +89,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 4a255e1e27..5be94a98bd 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -131,6 +131,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 index 09412296b5..3834f9efaf 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -17,11 +17,12 @@ 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.node.User 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 @@ -96,7 +97,7 @@ class NodePerformanceTests { internalDriver( notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(user))), startNodesInProcess = true, - extraCordappPackagesToScan = listOf("net.corda.finance") + cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance") ) { val notary = defaultNotaryNode.getOrThrow() as InProcess val metricRegistry = startReporter(this.shutdownManager, notary.internalServices.monitoringService.metrics) diff --git a/node/src/integration-test/kotlin/net/corda/node/flows/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 45f584780a..dc83ebfb52 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 @@ -14,9 +14,6 @@ import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.concurrent.transpose -import net.corda.core.internal.copyTo -import net.corda.core.internal.createDirectories -import net.corda.core.internal.div import net.corda.core.internal.toLedgerTransaction import net.corda.core.node.NetworkParameters import net.corda.core.node.ServicesForResolution @@ -26,7 +23,7 @@ import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_BANK_A_NAME @@ -40,6 +37,7 @@ import net.corda.testing.driver.driver import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.withoutTestSerialization +import net.corda.testing.node.internal.cordappsForPackages import net.corda.testing.services.MockAttachmentStorage import org.junit.Assert.assertEquals import org.junit.Rule @@ -52,7 +50,7 @@ class AttachmentLoadingTests { @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) @@ -75,13 +73,6 @@ class AttachmentLoadingTests { 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 { @@ -114,9 +105,9 @@ class AttachmentLoadingTests { @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() } @@ -124,16 +115,4 @@ class AttachmentLoadingTests { 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 ab171b2c1c..1923a34ec6 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 @@ -6,11 +6,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 @@ -37,6 +33,7 @@ import net.corda.testing.contracts.DummyContract 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.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork.MockNode import net.corda.testing.node.internal.InternalMockNodeParameters @@ -50,18 +47,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 @@ -75,7 +62,7 @@ class BFTNotaryServiceTests { @BeforeClass @JvmStatic fun before() { - 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 3f947a6d33..275ca4ccc4 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 @@ -12,6 +12,7 @@ import net.corda.node.services.transactions.minClusterSize import net.corda.testing.contracts.DummyContract 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.startFlow @@ -24,7 +25,7 @@ class BFTSMaRtTests { @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 6f0246660d..40f01263c0 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -20,6 +20,7 @@ import net.corda.testing.core.singleIdentity import net.corda.testing.driver.PortAllocation import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.node.NotarySpec +import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages import net.corda.testing.node.internal.SharedCompatibilityZoneParams import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.network.NetworkMapServer @@ -87,7 +88,7 @@ class NodeRegistrationTest { 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 32f6d70614..d60cbf67de 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -59,10 +59,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/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 74837dda5a..279193473b 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -25,8 +25,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 @@ -114,12 +115,8 @@ open class Node(configuration: NodeConfiguration, } private val sameVmNodeCounter = AtomicInteger() - const val scanPackagesSystemProperty = "net.corda.node.cordapp.scan.packages" - const val scanPackagesSeparator = "," private fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader { - return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages -> - CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator)) - } ?: CordappLoader.createDefault(configuration.baseDirectory) + return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories) } // 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 7b8e3f26f8..5f0c710a32 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 @@ -13,6 +13,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 57% 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 cedd54b1fc..94dc45a0fb 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,6 +1,5 @@ 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 @@ -15,23 +14,16 @@ 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.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.JarOutputStream -import java.util.zip.ZipEntry import kotlin.reflect.KClass import kotlin.streams.toList @@ -40,20 +32,11 @@ import kotlin.streams.toList * * @property cordappJarPaths The classpath of cordapp JARs */ -class CordappLoader private constructor(private val cordappJarPaths: List) { - 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) : 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()) { @@ -63,126 +46,37 @@ class CordappLoader private constructor(private val cordappJarPaths: List get() = cordapps.flatMap { it.customSchemas }.toSet() - companion object { private val logger = contextLogger() - /** - * Default cordapp dir name - */ - private const val CORDAPPS_DIR_NAME = "cordapps" /** - * Creates a default CordappLoader intended to be used in non-dev or non-test environments. + * Creates a CordappLoader from multiple directories. * - * @param baseDir The directory that this node is running in. Will use this to resolve the cordapps directory - * for classpath scanning. + * @param corDappDirectories Directories used to scan for CorDapp JARs. */ - fun createDefault(baseDir: Path) = CordappLoader(getNodeCordappURLs(baseDir)) + fun fromDirectories(corDappDirectories: Iterable): CordappLoader { - // Cache for CordappLoaders to avoid costly classpath scanning - private val cordappLoadersCache = Caffeine.newBuilder().softValues().build, 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 - } - } + logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}") + return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }) } /** - * 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. + * Creates a CordappLoader loader out of a list of JAR URLs. * - * @param testPackages See [createWithTestPackages] + * @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection. */ - @VisibleForTesting - fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List): 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, ::CordappLoader) - } + fun fromJarUrls(scanJars: List) = JarScanningCordappLoader(scanJars.map { it.restricted() }) - /** - * 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): CordappLoader { - val urls = simplifyScanPackages(testPackages).flatMap(this::getPackageURLs) - return cordappLoadersCache.asMap().computeIfAbsent(urls, ::CordappLoader) - } + private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName) - /** - * 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) = CordappLoader(scanJars.map { RestrictedURL(it, null) }) + private fun jarUrlsInDirectory(directory: Path): List { - 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 cordappJar = cordappDir / "$scanPackage-${UUID.randomUUID()}.jar" - logger.info("Generating a test-only CorDapp of classes discovered for package $scanPackage in $url: $cordappJar") - JarOutputStream(cordappJar.outputStream()).use { jos -> - val scanDir = url.toPath() - 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()) { + return if (!directory.exists()) { emptyList() } else { - cordappsDir.list { - it.filter { it.toString().endsWith(".jar") }.map { RestrictedURL(it.toUri().toURL(), null) }.toList() + directory.list { paths -> + // `toFile()` can't be used here... + paths.filter { it.toString().endsWith(".jar") }.map { it.toUri().toURL() }.toList() } } } @@ -212,22 +106,25 @@ class CordappLoader private constructor(private val cordappJarPaths: List { - return cordappJarPaths.map { - val scanResult = scanCordapp(it) - CordappImpl(findContractClassNames(scanResult), - findInitiatedFlows(scanResult), - findRPCFlows(scanResult), - findServiceFlows(scanResult), - findSchedulableFlows(scanResult), - findServices(scanResult), - findPlugins(it), - findSerializers(scanResult), - findCustomSchemas(scanResult), - findAllFlows(scanResult), - it.url, - getJarHash(it.url) - ) - } + return cordappJarPaths.map { scanCordapp(it).toCordapp(it) } + } + + private fun RestrictedScanResult.toCordapp(url: RestrictedURL): Cordapp { + + return CordappImpl( + findContractClassNames(this), + findInitiatedFlows(this), + findRPCFlows(this), + findServiceFlows(this), + findSchedulableFlows(this), + findServices(this), + findPlugins(url), + findSerializers(this), + findCustomSchemas(this), + findAllFlows(this), + url.url, + getJarHash(url.url) + ) } private fun getJarHash(url: URL): SecureHash.SHA256 = url.openStream().readFully().sha256() @@ -290,11 +187,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>> { @@ -368,3 +266,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 new file mode 100644 index 0000000000..86d6a3168b --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt @@ -0,0 +1,47 @@ +package net.corda.node.internal.cordapp + +import net.corda.core.cordapp.Cordapp +import net.corda.core.internal.cordapp.CordappImpl +import java.util.* +import java.util.jar.Attributes +import java.util.jar.Manifest + +fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest { + + val manifest = Manifest() + + // Mandatory manifest attribute. If not present, all other entries are silently skipped. + manifest.mainAttributes[Attributes.Name.MANIFEST_VERSION] = "1.0" + + manifest["Name"] = name + + manifest["Specification-Title"] = title + manifest["Specification-Version"] = version + manifest["Specification-Vendor"] = vendor + + manifest["Implementation-Title"] = title + manifest["Implementation-Version"] = version + manifest["Implementation-Vendor"] = vendor + + return manifest +} + +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) + } + this?.mainAttributes?.getValue("Implementation-Vendor")?.let { vendor -> + unknown = unknown.copy(vendor = vendor) + } + this?.mainAttributes?.getValue("Implementation-Version")?.let { version -> + unknown = unknown.copy(version = version) + } + return unknown +} \ No newline at end of file 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 b4087a2870..e900cdfd76 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 @@ -5,6 +5,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 @@ -27,6 +28,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 @@ -67,6 +69,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 @@ -81,6 +84,8 @@ interface NodeConfiguration : NodeSSLConfiguration { val defaultAttachmentContentCacheSize: Long = 10.MB const val defaultAttachmentCacheBound = 1024L + + const val cordappDirectoriesKey = "cordappDirectories" } } @@ -202,7 +207,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 fe97431308..d1e9de485c 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -39,6 +39,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 @@ -81,7 +82,7 @@ class CordaRPCOpsImplTest { @Before fun setup() { - mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset")) + mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset")) 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 295c6ede59..614045efca 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NodeUnloadHandlerTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NodeUnloadHandlerTests.kt @@ -6,6 +6,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 @@ -18,7 +19,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 1a9e2a4951..dcaf6e8b5f 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 @@ -75,7 +75,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 @@ -84,7 +84,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 65% 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 2474a436d2..1ab3482533 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 @@ -2,8 +2,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 @@ -30,7 +35,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" @@ -40,19 +45,19 @@ 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(".")) - assertThat(loader.cordapps).containsOnly(CordappLoader.coreCordapp) + val loader = JarScanningCordappLoader.fromDirectories(listOf(Paths.get("."))) + assertThat(loader.cordapps).containsOnly(JarScanningCordappLoader.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) - val actualCordapp = actual.single { it != CordappLoader.coreCordapp } + val actualCordapp = actual.single { it != JarScanningCordappLoader.coreCordapp } assertThat(actualCordapp.contractClassNames).isEqualTo(listOf(isolatedContractId)) assertThat(actualCordapp.initiatedFlows.single().name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Acceptor") assertThat(actualCordapp.rpcFlows).isEmpty() @@ -65,13 +70,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) @@ -81,14 +84,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) } @@ -97,10 +100,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 dc7a3505e0..6ff4f6f9ce 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -50,6 +50,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 @@ -79,7 +80,7 @@ import kotlin.test.assertTrue @RunWith(Parameterized::class) class TwoPartyTradeFlowTests(private val anonymous: Boolean) { companion object { - private val cordappPackages = listOf("net.corda.finance.contracts") + private val cordappPackages = setOf("net.corda.finance.contracts") @JvmStatic @Parameterized.Parameters(name = "Anonymous = {0}") fun data(): Collection = listOf(true, false) @@ -107,7 +108,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 @@ -159,7 +160,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 @@ -217,7 +218,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 @@ -316,11 +317,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)) + } } } }) @@ -328,7 +338,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) @@ -432,7 +442,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) @@ -510,7 +520,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") @@ -519,7 +529,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 a96f4abc6a..e528c696d9 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 @@ -1,11 +1,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 @@ -20,6 +16,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 @@ -51,7 +48,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 9d9bcf7f56..a12a776e89 100644 --- a/node/src/test/kotlin/net/corda/node/services/TimedFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/TimedFlowTests.kt @@ -35,6 +35,7 @@ import net.corda.testing.core.singleIdentity import net.corda.testing.internal.LogHelper import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockNetworkParameters +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 @@ -67,8 +68,8 @@ class TimedFlowTests { @JvmStatic fun setup() { 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 a878fb5a18..b9281bb5c7 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 @@ -23,6 +23,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 @@ -101,7 +102,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 0a86aad1da..e258334897 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 @@ -15,6 +15,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 @@ -31,7 +32,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)) @@ -64,8 +65,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 d29b5b6c90..c8938f562c 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 @@ -68,7 +68,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() ) @@ -481,7 +481,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() ) @@ -645,7 +645,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 a09a027cee..62ca2a17fc 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 @@ -15,6 +15,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 @@ -33,7 +34,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 03f53ceac9..e9968648ba 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 @@ -36,6 +36,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 @@ -62,7 +63,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 180f8e87d0..bf0fdd2c97 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 @@ -29,6 +29,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 @@ -80,7 +81,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 fe270603b5..a22723697d 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 @@ -11,6 +11,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/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt b/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt index 4a3694fbd4..fe826bbc8c 100644 --- a/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt +++ b/serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt @@ -13,7 +13,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 @@ -57,7 +57,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/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 16e3ec37dc..e413f5d90c 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 @@ -20,10 +20,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 @@ -136,6 +133,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( @@ -145,8 +145,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, @@ -161,7 +210,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?, @@ -179,6 +265,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) @@ -186,6 +291,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) } /** @@ -230,12 +337,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, @@ -272,6 +379,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( @@ -289,8 +397,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, @@ -319,7 +463,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( @@ -352,7 +530,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) @@ -370,6 +584,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, @@ -431,4 +646,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 996d902ce0..1c4c01c3c0 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 @@ -80,6 +80,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 16be35160f..a3f031058f 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 @@ -14,10 +14,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 @@ -33,22 +31,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) } } @@ -205,6 +217,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 @@ -262,7 +287,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))) } @@ -285,7 +329,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 c6d99267b4..d1f0fcfa05 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 @@ -18,9 +18,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 @@ -36,6 +37,8 @@ import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.MockCordappProvider import net.corda.testing.node.internal.MockKeyManagementService import net.corda.testing.node.internal.MockTransactionStorage +import net.corda.testing.node.internal.TestCordappDirectories +import net.corda.testing.node.internal.getCallerPackage import net.corda.testing.services.MockAttachmentStorage import java.security.KeyPair import java.sql.Connection @@ -64,7 +67,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. * @@ -97,7 +108,8 @@ 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 = makeTestDataSourceProperties() val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService) @@ -119,14 +131,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() } @@ -150,39 +154,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 @@ -190,7 +193,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 @@ -198,7 +201,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 @@ -207,7 +210,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 @@ -217,7 +220,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 { @@ -236,7 +239,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 39b9b6306c..fe9bc5d2a8 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 @@ -54,6 +54,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 @@ -87,20 +88,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() /** @@ -186,6 +186,8 @@ class DriverDSLImpl( } } + 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) + override fun startNode( defaultParameters: NodeParameters, providedName: CordaX500Name?, @@ -193,7 +195,9 @@ class DriverDSLImpl( verifierType: VerifierType, customOverrides: Map, startInSameProcess: Boolean?, - maximumHeapSize: String + maximumHeapSize: String, + additionalCordapps: Set, + regenerateCordappsOnStart: Boolean ): CordaFuture { val p2pAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name @@ -209,7 +213,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) + startRegisteredNode(name, it, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, p2pAddress, additionalCordapps, regenerateCordappsOnStart) } } } @@ -221,7 +225,9 @@ class DriverDSLImpl( customOverrides: Map, startInSameProcess: Boolean? = null, maximumHeapSize: String = "512m", - p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort()): CordaFuture { + p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(), + additionalCordapps: Set = emptySet(), + regenerateCordappsOnStart: Boolean = false): CordaFuture { val rpcAddress = portAllocation.nextHostAndPort() val rpcAdminAddress = portAllocation.nextHostAndPort() val webAddress = portAllocation.nextHostAndPort() @@ -249,7 +255,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) + return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize, localNetworkMap, additionalCordapps, regenerateCordappsOnStart) } private fun startNodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture { @@ -303,6 +309,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" } @@ -360,6 +367,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 @@ -388,9 +396,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) + val config = NodeConfig(typesafe).checkAndOverrideForInMemoryDB() + return startNodeInternal(config, webAddress, null, "512m", localNetworkMap, emptySet()) } @Suppress("DEPRECATION") @@ -614,7 +621,6 @@ class DriverDSLImpl( jolokiaJarPath, monitorPort, systemProperties, - cordappPackages, "512m", *extraCmdLineFlag ) @@ -624,13 +630,19 @@ 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?): CordaFuture { - val visibilityHandle = networkVisibilityController.register(config.corda.myLegalName) - val baseDirectory = config.corda.baseDirectory.createDirectories() + localNetworkMap: LocalNetworkMap?, + additionalCordapps: Set, + regenerateCordappsOnStart: Boolean = false): CordaFuture { + val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName) + val baseDirectory = specifiedConfig.corda.baseDirectory.createDirectories() localNetworkMap?.networkParametersCopier?.install(baseDirectory) localNetworkMap?.nodeInfosCopier?.addConfig(baseDirectory) @@ -639,10 +651,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) -> { @@ -669,7 +687,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, systemProperties, cordappPackages, maximumHeapSize) + val process = startOutOfProcessNode(config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, 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 @@ -763,10 +781,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}") @@ -776,7 +796,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() } @@ -793,10 +813,10 @@ class DriverDSLImpl( jolokiaJarPath: String, monitorPort: 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")) @@ -810,11 +830,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. @@ -1064,13 +1079,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) @@ -1144,12 +1159,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( @@ -1163,12 +1178,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, @@ -1188,3 +1203,5 @@ fun writeConfig(path: Path, filename: String, config: Config) { private fun Config.toNodeOnly(): Config { return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this } + +internal fun DriverParameters.cordappsForAllNodes(): Set = cordappsForAllNodes ?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan) \ No newline at end of file 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 5251a29c57..cbdfd70c5e 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 @@ -12,10 +12,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 @@ -25,14 +22,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.* @@ -49,6 +44,7 @@ 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 @@ -59,6 +55,7 @@ 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 @@ -76,8 +73,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( @@ -86,25 +82,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. @@ -113,6 +110,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()) @@ -129,6 +130,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 @@ -221,12 +226,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 { @@ -356,7 +360,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) } @@ -365,11 +369,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 @@ -378,7 +382,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() @@ -386,7 +395,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( @@ -397,7 +406,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 @@ -455,7 +464,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 faedf4d5ec..d8e6eb007c 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt @@ -1,5 +1,6 @@ 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 @@ -7,10 +8,10 @@ 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.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 @@ -29,10 +30,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()) { companion object { private val WHITESPACE = "\\s++".toRegex() + + private val logger = loggerFor() } @Rule @@ -99,14 +102,23 @@ 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) - val node = InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), cordappPackages).start() + val node = InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion)).start() nodes += node ensureAllNetworkMapCachesHaveAllNodeInfos() thread(name = legalName.organisation) { @@ -130,8 +142,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi } } -class InProcessNode( - configuration: NodeConfiguration, versionInfo: VersionInfo, cordappPackages: List) : Node( - configuration, versionInfo, false, CordappLoader.createDefaultWithTestPackages(configuration, cordappPackages)) { +class InProcessNode(configuration: NodeConfiguration, versionInfo: VersionInfo) : Node(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 ba9684fa4f..f6d7614cdd 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 @@ -27,8 +27,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 @@ -109,13 +111,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(), notaryCustomOverrides: Map = emptyMap(), inMemoryDB: Boolean = true, + cordappsForAllNodes: Set = cordappsInCurrentAndAdditionalPackages(), dsl: RPCDriverDSL.() -> A ): A { return genericDriver( @@ -129,13 +131,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 225688a2fe..c023e7cf46 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 @@ -7,6 +7,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 /** @@ -59,12 +60,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 4ca0c97154..ba5f777102 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 @@ -9,7 +9,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 b7f83c21d5..2a536efe13 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 @@ -7,7 +7,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 @@ -42,8 +43,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 {