Merge remote-tracking branch 'remotes/open/master' into merges/july-23-11-26

# Conflicts:
#	confidential-identities/src/test/kotlin/net/corda/confidential/IdentitySyncFlowTests.kt
#	docs/source/changelog.rst
#	node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt
#	node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt
#	node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt
#	node/src/integration-test/kotlin/net/corda/node/services/BFTSMaRtTests.kt
#	node/src/main/kotlin/net/corda/node/internal/Node.kt
#	node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt
#	node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt
#	node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt
#	node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt
#	node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt
#	node/src/test/kotlin/net/corda/node/services/TimedFlowTests.kt
#	settings.gradle
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt
This commit is contained in:
Michele Sollecito 2018-07-23 15:22:39 +01:00
commit a4d65dae22
73 changed files with 1839 additions and 518 deletions

View File

@ -5825,6 +5825,8 @@ public interface net.corda.testing.driver.DriverDSL
@NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.NodeHandle> startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List<net.corda.testing.node.User>, net.corda.testing.driver.VerifierType, java.util.Map<String, ?>, Boolean, String)
@NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.NodeHandle> startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List<net.corda.testing.node.User>, net.corda.testing.driver.VerifierType, java.util.Map<String, ?>, Boolean, String, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>, boolean)
@NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.WebserverHandle> startWebserver(net.corda.testing.driver.NodeHandle)
@NotNull
public abstract net.corda.core.concurrent.CordaFuture<net.corda.testing.driver.WebserverHandle> 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 <init>()
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map<String, ?>, boolean, boolean)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map<String, ?>, boolean, boolean, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean)
public <init>(boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map<String, String>, boolean, boolean, boolean, java.util.List<net.corda.testing.node.NotarySpec>, java.util.List<String>, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public final boolean component1()
@NotNull
public final java.util.List<String> 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<Class<?>> getClasses()
@NotNull
public abstract String getName()
@NotNull
public abstract java.util.Set<java.net.URL> 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 <init>()
@NotNull
public static final net.corda.testing.driver.TestCorDapp$Mutable create(String, String, String, String, java.util.Set<? extends Class<?>>, kotlin.jvm.functions.Function2<? super String, ? super java.net.URL, Boolean>)
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<? extends Class<?>>, kotlin.jvm.functions.Function2<? super String, ? super java.net.URL, Boolean>)
##
@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<String>)
@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<String>)
@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<? extends Class<?>>)
@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 <init>()
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<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
@NotNull
public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
@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<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
@NotNull
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
@NotNull
public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters)
@NotNull
public final java.util.List<String> getCordappPackages()
@ -6263,6 +6343,7 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O
public <init>()
public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>)
public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
public <init>(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
@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<? super net.corda.node.services.config.NodeConfiguration, ?>)
@NotNull
public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.List<String>)
@NotNull
public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1<? super net.corda.node.services.config.NodeConfiguration, ?>, java.util.Set<? extends net.corda.testing.driver.TestCorDapp>)
public boolean equals(Object)
@NotNull
public final kotlin.jvm.functions.Function1<net.corda.node.services.config.NodeConfiguration, Object> 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 <init>()
public <init>(java.util.List<String>)
public <init>(java.util.List<String>, net.corda.core.identity.CordaX500Name)
public <init>(java.util.List<String>, net.corda.core.identity.CordaX500Name, java.security.KeyPair, java.security.KeyPair...)
public <init>(java.util.List<String>, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService)
public <init>(java.util.List<String>, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService, java.security.KeyPair, java.security.KeyPair...)
public <init>(java.util.List<String>, net.corda.testing.core.TestIdentity, net.corda.core.node.services.IdentityService, net.corda.core.node.NetworkParameters, java.security.KeyPair...)
public <init>(java.util.List<String>, net.corda.testing.core.TestIdentity, net.corda.core.node.services.IdentityService, java.security.KeyPair...)
public <init>(java.util.List<String>, net.corda.testing.core.TestIdentity, java.security.KeyPair...)
public <init>(Iterable<String>)
public <init>(Iterable<String>, net.corda.core.identity.CordaX500Name)
public <init>(Iterable<String>, net.corda.core.identity.CordaX500Name, java.security.KeyPair, java.security.KeyPair...)
public <init>(Iterable<String>, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService)
public <init>(Iterable<String>, net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService, java.security.KeyPair, java.security.KeyPair...)
public <init>(Iterable<String>, net.corda.testing.core.TestIdentity, net.corda.core.node.services.IdentityService, net.corda.core.node.NetworkParameters, java.security.KeyPair...)
public <init>(Iterable<String>, net.corda.testing.core.TestIdentity, net.corda.core.node.services.IdentityService, java.security.KeyPair...)
public <init>(Iterable<String>, net.corda.testing.core.TestIdentity, java.security.KeyPair...)
public <init>(net.corda.core.identity.CordaX500Name)
public <init>(net.corda.core.identity.CordaX500Name, java.security.KeyPair, java.security.KeyPair...)
public <init>(net.corda.core.identity.CordaX500Name, net.corda.core.node.services.IdentityService)

View File

@ -3,9 +3,9 @@
# PR Checklist:
- [ ] Have you run the unit, integration and smoke tests as described here? https://docs.corda.net/head/testing.html
- [ ] If you added/changed public APIs, did you write/update the JavaDocs?
- [ ] If the changes are of interest to application developers, have you added them to the changelog, and potentially release notes?
- [ ] If you are contributing for the first time, please read the agreement in CONTRIBUTING.md now and add to this Pull Request that you agree to it.
- [ ] Have you run the unit, integration and smoke tests as described [here](https://docs.corda.net/head/testing.html)?
- [ ] If you added public APIs, did you write the JavaDocs?
- [ ] If the changes are of interest to application developers, have you added them to the [changelog](https://github.com/corda/corda/blob/master/docs/source/changelog.rst), and potentially the [release notes](https://github.com/corda/corda/blob/master/docs/source/release-notes.rst)?
- [ ] If you are contributing for the first time, please read the agreement in [CONTRIBUTING.md](https://github.com/corda/corda/blob/master/CONTRIBUTING.md) now and add a comment to this pull request stating that your PR is in accordance with the [Developer's Certificate of Origin](https://github.com/corda/corda/blob/master/CONTRIBUTING.md#developer-certificate-of-origin).
Thanks for your code, it's appreciated! :)

View File

@ -185,7 +185,7 @@ allprojects {
jvmTarget = "1.8"
javaParameters = true // Useful for reflection.
freeCompilerArgs = ['-Xjvm-default=compatibility']
allWarningsAsErrors = true
allWarningsAsErrors = project.hasProperty('compilation.allWarningsAsErrors') ? project.property('compilation.allWarningsAsErrors').toBoolean() : true
}
}
@ -201,7 +201,7 @@ allprojects {
}
tasks.withType(Test) {
failFast = true
failFast = project.hasProperty('tests.failFast') ? project.property('tests.failFast').toBoolean() : true
// Prevent the project from creating temporary files outside of the build directory.
systemProperty 'java.io.tmpdir', buildDir.absolutePath

View File

@ -29,6 +29,7 @@ import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.startFlow
import org.junit.After
import org.junit.Before
@ -44,7 +45,7 @@ class IdentitySyncFlowTests {
fun before() {
// We run this in parallel threads to help catch any race conditions that may exist.
mockNet = InternalMockNetwork(
cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"),
cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset", "net.corda.finance.schemas"),
networkSendManuallyPumped = false,
threadPerNode = true
)

View File

@ -137,7 +137,7 @@ class AttachmentTests : WithMockNet {
// Makes a node that doesn't do sanity checking at load time.
private fun makeBadNode(name: CordaX500Name) = mockNet.createNode(
InternalMockNodeParameters(legalName = randomise(name)),
nodeFactory = { args ->
nodeFactory = { args, _ ->
object : InternalMockNetwork.MockNode(args) {
override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false }
}

View File

@ -30,6 +30,7 @@ import net.corda.testing.core.*
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.cordappsForPackages
import org.junit.AfterClass
import org.junit.Test
@ -37,7 +38,7 @@ class CollectSignaturesFlowTests : WithContracts {
companion object {
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
private val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), miniCorp, rigorousMock())
private val classMockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.core.flows"))
private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts", "net.corda.core.flows"))
private const val MAGIC_NUMBER = 1337

View File

@ -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<WithFinality.FinalityInvoker>(),
startFlow<ContractUpgradeFlow.Initiate<*, *>>(),
startFlow<ContractUpgradeFlow.Authorise>(),

View File

@ -35,6 +35,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.startFlow
import org.junit.AfterClass
import org.junit.Test
@ -42,10 +43,7 @@ import java.util.*
class ContractUpgradeFlowTest : WithContracts, WithFinality {
companion object {
private val classMockNet = InternalMockNetwork(cordappPackages = listOf(
"net.corda.testing.contracts",
"net.corda.finance.contracts.asset",
"net.corda.core.flows"))
private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows"))
@JvmStatic
@AfterClass

View File

@ -24,13 +24,14 @@ import net.corda.finance.issuedBy
import net.corda.node.internal.StartedNode
import net.corda.testing.core.*
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.cordappsForPackages
import org.junit.AfterClass
import org.junit.Test
class FinalityFlowTests : WithFinality {
companion object {
private val CHARLIE = TestIdentity(CHARLIE_NAME, 90).party
private val classMockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset"))
private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset"))
@JvmStatic
@AfterClass

View File

@ -171,7 +171,7 @@ class AttachmentSerializationTest {
}
private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String {
client = mockNet.restartNode(client) { args ->
client = mockNet.restartNode(client) { args, _ ->
object : InternalMockNetwork.MockNode(args) {
override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad }
}

View File

@ -6,6 +6,7 @@ release, see :doc:`upgrade-notes`.
Unreleased
----------
* Introduced ``TestCorDapp`` and utilities to support asymmetric setups for nodes through ``DriverDSL``, ``MockNetwork`` and ``MockServices``.
* ``freeLocalHostAndPort``, ``freePort``, and ``getFreeLocalPorts`` from ``TestUtils`` have been deprecated as they
don't provide any guarantee the returned port will be available which can result in flaky tests. Use ``PortAllocation.Incremental``

View File

@ -158,9 +158,9 @@ Installing the CorDapp JAR
.. note:: Before installing a CorDapp, you must create one or more nodes to install it on. For instructions, please see
:doc:`generating-a-node`.
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
the node's JAR and configuration files are stored.
At start-up, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore, in order to install a CorDapp on
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder (where ``node_dir`` is the folder in which
the node's JAR and configuration files are stored) and the node restarted.
CorDapp configuration files
---------------------------

View File

@ -317,7 +317,7 @@ Assuming all went well, you can view the newly-created IOU by accessing the vaul
*Via web/example:*
* PartyA: Navigate to http://localhost:10009/web/example and hit the "refresh" button
* PartyA: Navigate to http://localhost:10012/web/example and hit the "refresh" button
* PartyB: Navigate to http://localhost:10012/web/example and hit the "refresh" button
The vault and web front-end of PartyC (at ``localhost:10015``) will not display any IOUs. This is because PartyC was
not involved in this transaction.

View File

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

View File

@ -21,18 +21,25 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.MockCordappConfigProvider
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.getTimestampAsDirectoryName
import net.corda.testing.node.internal.packageInDirectory
import net.corda.testing.services.MockAttachmentStorage
import org.junit.Assert.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Rule
import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths
class AttachmentsClassLoaderStaticContractTests {
private companion object {
@ -70,7 +77,7 @@ class AttachmentsClassLoaderStaticContractTests {
}
private val serviceHub = rigorousMock<ServicesForResolution>().also {
doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage(), testNetworkParameters().whitelistedContractImplementations)).whenever(it).cordappProvider
doReturn(CordappProviderImpl(cordappLoaderForPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage(), testNetworkParameters().whitelistedContractImplementations)).whenever(it).cordappProvider
doReturn(testNetworkParameters()).whenever(it).networkParameters
}
@ -92,4 +99,18 @@ class AttachmentsClassLoaderStaticContractTests {
assertNotNull(contract)
}
private fun cordappLoaderForPackages(packages: Iterable<String>): 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())
}
}

View File

@ -144,6 +144,7 @@ dependencies {
testCompile project(':test-utils')
testCompile project(':client:jfx')
testCompile project(':finance')
testCompile project(':finance:isolated')
// sample test schemas
testCompile project(path: ':finance', configuration: 'testArtifacts')

View File

@ -0,0 +1,117 @@
package net.corda.node
import co.paralleluniverse.fibers.Suspendable
import com.google.common.base.Stopwatch
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.InProcess
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.internalServices
import net.corda.testing.internal.performance.div
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
import net.corda.testing.node.internal.internalDriver
import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector
import net.corda.testing.node.internal.performance.startReporter
import net.corda.testing.node.internal.performance.startTightLoopInjector
import org.junit.Ignore
import org.junit.Test
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.streams.toList
@Ignore("Run these locally")
class NodePerformanceTests {
@StartableByRPC
class EmptyFlow : FlowLogic<Unit>() {
@Suspendable
override fun call() {
}
}
private data class FlowMeasurementResult(
val flowPerSecond: Double,
val averageMs: Double
)
@Test
fun `empty flow per second`() {
driver(DriverParameters(startNodesInProcess = true)) {
val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow<EmptyFlow>())))).get()
CordaRPCClient(a.rpcAddress).use("A", "A") { connection ->
val timings = Collections.synchronizedList(ArrayList<Long>())
val N = 10000
val overallTiming = Stopwatch.createStarted().apply {
startTightLoopInjector(
parallelism = 8,
numberOfInjections = N,
queueBound = 50
) {
val timing = Stopwatch.createStarted().apply {
connection.proxy.startFlow(::EmptyFlow).returnValue.get()
}.stop().elapsed(TimeUnit.MICROSECONDS)
timings.add(timing)
}
}.stop().elapsed(TimeUnit.MICROSECONDS)
println(
FlowMeasurementResult(
flowPerSecond = N / (overallTiming * 0.000001),
averageMs = timings.average() * 0.001
)
)
}
}
}
@Test
fun `empty flow rate`() {
internalDriver(startNodesInProcess = true) {
val a = startNode(rpcUsers = listOf(User("A", "A", setOf(startFlow<EmptyFlow>())))).get()
a as InProcess
val metricRegistry = startReporter(this.shutdownManager, a.internalServices.monitoringService.metrics)
CordaRPCClient(a.rpcAddress).use("A", "A") { connection ->
startPublishingFixedRateInjector(metricRegistry, 8, 5.minutes, 2000L / TimeUnit.SECONDS) {
connection.proxy.startFlow(::EmptyFlow).returnValue.get()
}
}
}
}
@Test
fun `self pay rate`() {
val user = User("A", "A", setOf(startFlow<CashIssueFlow>(), startFlow<CashPaymentFlow>()))
internalDriver(
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(user))),
startNodesInProcess = true,
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance")
) {
val notary = defaultNotaryNode.getOrThrow() as InProcess
val metricRegistry = startReporter(this.shutdownManager, notary.internalServices.monitoringService.metrics)
CordaRPCClient(notary.rpcAddress).use("A", "A") { connection ->
println("ISSUING")
val doneFutures = (1..100).toList().parallelStream().map {
connection.proxy.startFlow(::CashIssueFlow, 1.DOLLARS, OpaqueBytes.of(0), defaultNotaryIdentity).returnValue
}.toList()
doneFutures.transpose().get()
println("STARTING PAYMENT")
startPublishingFixedRateInjector(metricRegistry, 8, 5.minutes, 100L / TimeUnit.SECONDS) {
connection.proxy.startFlow(::CashPaymentFlow, 1.DOLLARS, defaultNotaryIdentity).returnValue.get()
}
}
}
}
}

View File

@ -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<Unit>() {
@Suspendable
override fun call() {
val pongSession = initiateFlow(pongParty)
pongSession.sendAndReceive<Unit>(times)
for (i in 1..times) {
val j = pongSession.sendAndReceive<Int>(i).unwrap { it }
assertEquals(i, j)
}
}
}
@InitiatedBy(Ping::class)
class Pong(private val pingSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val times = pingSession.sendAndReceive<Int>(Unit).unwrap { it }
for (i in 1..times) {
val j = pingSession.sendAndReceive<Int>(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()
}
}
}

View File

@ -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<SendMessageFlow>(), 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<TestCorDapp>?,
cordappsVersionAtStartup: Set<TestCorDapp>,
cordappsVersionAtRestart: Set<TestCorDapp>,
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")
}
}

View File

@ -58,7 +58,7 @@ class AttachmentLoadingTests : IntegrationTest() {
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val attachments = MockAttachmentStorage()
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments, testNetworkParameters().whitelistedContractImplementations)
private val provider = CordappProviderImpl(JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments, testNetworkParameters().whitelistedContractImplementations)
private val cordapp get() = provider.cordapps.first()
private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!!
private val appContext get() = provider.getAppContext(cordapp)
@ -86,13 +86,6 @@ class AttachmentLoadingTests : IntegrationTest() {
startNode(providedName = bankBName)
).transpose().getOrThrow()
}
private fun DriverDSL.installIsolatedCordappTo(nodeName: CordaX500Name) {
// Copy the app jar to the first node. The second won't have it.
val path = (baseDirectory(nodeName) / "cordapps").createDirectories() / "isolated.jar"
logger.info("Installing isolated jar to $path")
isolatedJAR.openStream().use { it.copyTo(path) }
}
}
private val services = object : ServicesForResolution {
@ -125,9 +118,9 @@ class AttachmentLoadingTests : IntegrationTest() {
@Test
fun `test that attachments retrieved over the network are not used for code`() {
withoutTestSerialization {
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
installIsolatedCordappTo(bankAName)
val (bankA, bankB) = createTwoNodes()
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList(), cordappsForAllNodes = emptySet())) {
val bankA = startNode(providedName = bankAName, additionalCordapps = cordappsForPackages("net.corda.finance.contracts.isolated")).getOrThrow()
val bankB = startNode(providedName = bankBName, additionalCordapps = cordappsForPackages("net.corda.finance.contracts.isolated")).getOrThrow()
assertFailsWith<CordaRuntimeException>("Party C=CH,L=Zurich,O=BankB rejected session request: Don't know net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") {
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
}
@ -135,16 +128,4 @@ class AttachmentLoadingTests : IntegrationTest() {
Unit
}
}
@Test
fun `tests that if the attachment is loaded on both sides already that a flow can run`() {
withoutTestSerialization {
driver {
installIsolatedCordappTo(bankAName)
installIsolatedCordappTo(bankBName)
val (bankA, bankB) = createTwoNodes()
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
}
}
}
}
}

View File

@ -16,11 +16,7 @@ import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.crypto.sha256
import net.corda.core.crypto.*
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow
@ -49,6 +45,7 @@ import net.corda.testing.core.singleIdentity
import net.corda.testing.node.TestClock
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.internal.InternalMockNodeParameters
@ -63,18 +60,8 @@ import java.nio.file.Paths
import java.time.Duration
import java.time.Instant
import java.util.concurrent.ExecutionException
import kotlin.collections.List
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.distinct
import kotlin.collections.forEach
import kotlin.collections.last
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mapIndexedNotNull
import kotlin.collections.plus
import kotlin.collections.single
import kotlin.collections.zip
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
@ -94,7 +81,7 @@ class BFTNotaryServiceTests {
@JvmStatic
fun before() {
IntegrationTest.globalSetUp() //Enterprise only - remote db setup
mockNet = InternalMockNetwork(listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"))
val clusterSize = minClusterSize(1)
val started = startBftClusterAndNode(clusterSize, mockNet)
notary = started.first

View File

@ -14,6 +14,7 @@ import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.internal.startFlow
@ -34,7 +35,7 @@ class BFTSMaRtTests : IntegrationTest() {
@Before
fun before() {
mockNet = InternalMockNetwork(listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"))
}
@After

View File

@ -32,6 +32,7 @@ import net.corda.testing.internal.DEV_ROOT_CA
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
import net.corda.testing.node.internal.SharedCompatibilityZoneParams
import net.corda.testing.node.internal.internalDriver
import net.corda.testing.node.internal.network.NetworkMapServer
@ -100,7 +101,7 @@ class NodeRegistrationTest : IntegrationTest() {
compatibilityZone = compatibilityZone,
initialiseSerialization = false,
notarySpecs = listOf(NotarySpec(notaryName)),
extraCordappPackagesToScan = listOf("net.corda.finance"),
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance"),
notaryCustomOverrides = mapOf("devMode" to false)
) {
val (alice, genevieve) = listOf(

View File

@ -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<SignedTransaction>() {
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<Unit>() {
@Suspendable
override fun call() {
val tx = subFlow(ReceiveTransactionFlow(session, statesToRecord = StatesToRecord.ALL_VISIBLE))
serviceHub.addSignature(tx)
}
}

View File

@ -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<Cordapp>
/**
* 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<Class<out FlowLogic<*>>, Cordapp>
/**
* Returns all [MappedSchema] found inside the [Cordapp]s.
*/
val cordappSchemas: Set<MappedSchema>
}

View File

@ -67,10 +67,11 @@ import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes
import net.corda.node.CordaClock
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.CheckpointVerifier.verifyCheckpointsCompatible
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappConfigFileProvider
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy

View File

@ -22,7 +22,6 @@ import net.corda.core.internal.Emoji
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.utilities.loggerFor
import net.corda.node.VersionInfo
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.RelayConfiguration
import net.corda.node.services.statemachine.MultiThreadedStateMachineExecutor
@ -38,9 +37,8 @@ import java.util.concurrent.TimeUnit
open class EnterpriseNode(configuration: NodeConfiguration,
versionInfo: VersionInfo,
initialiseSerialization: Boolean = true,
cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo)
) : Node(configuration, versionInfo, initialiseSerialization, cordappLoader) {
initialiseSerialization: Boolean = true
) : Node(configuration, versionInfo, initialiseSerialization) {
companion object {
private val logger by lazy { loggerFor<EnterpriseNode>() }

View File

@ -35,8 +35,9 @@ import net.corda.node.SimpleClock
import net.corda.node.VersionInfo
import net.corda.node.internal.artemis.ArtemisBroker
import net.corda.node.internal.artemis.BrokerAddresses
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.core.internal.errors.AddressBindingException
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
@ -124,15 +125,11 @@ open class Node(configuration: NodeConfiguration,
}
private val sameVmNodeCounter = AtomicInteger()
const val scanPackagesSystemProperty = "net.corda.node.cordapp.scan.packages"
const val scanPackagesSeparator = ","
@JvmStatic
protected fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader {
return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages ->
CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator), versionInfo)
} ?: CordappLoader.createDefault(configuration.baseDirectory, versionInfo)
return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories, versionInfo)
}
// TODO: make this configurable.
const val MAX_RPC_MESSAGE_SIZE = 10485760

View File

@ -23,6 +23,7 @@ import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.loggerFor
import net.corda.node.cordapp.CordappLoader
import java.net.URL
import java.util.concurrent.ConcurrentHashMap

View File

@ -1,16 +1,5 @@
/*
* R3 Proprietary and Confidential
*
* Copyright (c) 2018 R3 Limited. All rights reserved.
*
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
*
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
*/
package net.corda.node.internal.cordapp
import com.github.benmanes.caffeine.cache.Caffeine
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
import net.corda.core.cordapp.Cordapp
@ -18,50 +7,25 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.*
import net.corda.core.internal.*
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.SchedulableFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.StartableByService
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.copyTo
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.list
import net.corda.core.internal.objectOrNewInstance
import net.corda.core.internal.outputStream
import net.corda.core.internal.toPath
import net.corda.core.internal.toTypedArray
import net.corda.core.internal.walk
import net.corda.core.node.services.CordaService
import net.corda.node.VersionInfo
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.utilities.contextLogger
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.coreContractClasses
import net.corda.serialization.internal.DefaultWhitelist
import org.apache.commons.collections4.map.LRUMap
import java.lang.reflect.Modifier
import java.net.JarURLConnection
import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.attribute.FileTime
import java.time.Instant
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.jar.JarInputStream
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.reflect.KClass
import kotlin.streams.toList
@ -70,20 +34,11 @@ import kotlin.streams.toList
*
* @property cordappJarPaths The classpath of cordapp JARs
*/
class CordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>, versionInfo: VersionInfo) {
val cordapps: List<Cordapp> by lazy { loadCordapps() + coreCordapp }
val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader)
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN) : CordappLoaderTemplate() {
// Create a map of the CorDapps that provide a Flow. If a flow is not in this map it is a Core flow.
// It also checks that there is only one CorDapp containing that flow class
val flowCordappMap: Map<Class<out FlowLogic<*>>, 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<Cordapp> by lazy { loadCordapps() + coreCordapp }
override val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader)
init {
if (cordappJarPaths.isEmpty()) {
@ -93,7 +48,47 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
}
}
val cordappSchemas: Set<MappedSchema> get() = cordapps.flatMap { it.customSchemas }.toSet()
companion object {
private val logger = contextLogger()
/**
* Creates a CordappLoader from multiple directories.
*
* @param corDappDirectories Directories used to scan for CorDapp JARs.
*/
fun fromDirectories(corDappDirectories: Iterable<Path>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader {
logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}")
return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }, versionInfo)
}
/**
* Creates a CordappLoader loader out of a list of JAR URLs.
*
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
*/
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN) = JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo)
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
private fun jarUrlsInDirectory(directory: Path): List<URL> {
return if (!directory.exists()) {
emptyList()
} else {
directory.list { paths ->
// `toFile()` can't be used here...
paths.filter { it.toString().endsWith(".jar") }.map { it.toUri().toURL() }.toList()
}
}
}
/** A list of the core RPC flows present in Corda */
private val coreRPCFlows = listOf(
ContractUpgradeFlow.Initiate::class.java,
ContractUpgradeFlow.Authorise::class.java,
ContractUpgradeFlow.Deauthorise::class.java)
}
/** A Cordapp representing the core package which is not scanned automatically. */
@VisibleForTesting
@ -107,165 +102,36 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
serializationWhitelists = listOf(),
serializationCustomSerializers = listOf(),
customSchemas = setOf(),
jarPath = ContractUpgradeFlow.javaClass.protectionDomain.codeSource.location, // Core JAR location
info = CordappImpl.Info("corda-core", versionInfo.vendor, versionInfo.releaseVersion),
allFlows = listOf(),
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
jarHash = SecureHash.allOnesHash
)
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.
*
* @param baseDir The directory that this node is running in. Will use this to resolve the cordapps directory
* for classpath scanning.
*/
fun createDefault(baseDir: Path, versionInfo: VersionInfo = VersionInfo.UNKNOWN) = CordappLoader(getNodeCordappURLs(baseDir), versionInfo)
// Cache for CordappLoaders to avoid costly classpath scanning
private val cordappLoadersCache = Caffeine.newBuilder().softValues().build<List<RestrictedURL>, CordappLoader>()
private val generatedCordapps = ConcurrentHashMap<URL, Path>()
private fun simplifyScanPackages(scanPackages: List<String>): List<String> {
return scanPackages.sorted().fold(emptyList()) { listSoFar, packageName ->
when {
listSoFar.isEmpty() -> listOf(packageName)
packageName.startsWith(listSoFar.last()) -> listSoFar // Squash ["com.foo", "com.foo.bar"] into just ["com.foo"]
else -> listSoFar + packageName
}
}
}
/**
* Create a dev mode CordappLoader for test environments that creates and loads cordapps from the classpath
* and cordapps directory. This is intended mostly for use by the driver.
*
* @param testPackages See [createWithTestPackages]
*/
@VisibleForTesting
fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
if (!configuration.devMode) {
logger.warn("Package scanning should only occur in dev mode!")
}
val urls = getNodeCordappURLs(configuration.baseDirectory) + simplifyScanPackages(testPackages).flatMap(this::getPackageURLs)
return cordappLoadersCache.asMap().computeIfAbsent(urls) { values -> CordappLoader(values, versionInfo) }
}
/**
* Create a dev mode CordappLoader for test environments that creates and loads cordapps from the classpath.
* This is intended for use in unit and integration tests.
*
* @param testPackages List of package names that contain CorDapp classes that can be automatically turned into
* CorDapps.
*/
@VisibleForTesting
fun createWithTestPackages(testPackages: List<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
val urls = simplifyScanPackages(testPackages).flatMap(this::getPackageURLs)
return cordappLoadersCache.asMap().computeIfAbsent(urls) { values -> CordappLoader(values, versionInfo) }
}
/**
* Creates a dev mode CordappLoader intended only to be used in test environments
*
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection
*/
@VisibleForTesting
fun createDevMode(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN) = CordappLoader(scanJars.map { RestrictedURL(it, null) }, versionInfo)
private fun getPackageURLs(scanPackage: String): List<RestrictedURL> {
val resource = scanPackage.replace('.', '/')
return this::class.java.classLoader.getResources(resource)
.asSequence()
// This is to only scan classes from test folders.
.filter { url ->
!url.toString().contains("main/$resource") || listOf("net.corda.core", "net.corda.node", "net.corda.finance").none { scanPackage.startsWith(it) }
}
.map { url ->
if (url.protocol == "jar") {
// When running tests from gradle this may be a corda module jar, so restrict to scanPackage:
RestrictedURL((url.openConnection() as JarURLConnection).jarFileURL, scanPackage)
} else {
// No need to restrict as createDevCordappJar has already done that:
RestrictedURL(createDevCordappJar(scanPackage, url, resource).toUri().toURL(), null)
}
}
.toList()
}
/** Takes a package of classes and creates a JAR from them - only use in tests. */
private fun createDevCordappJar(scanPackage: String, url: URL, resource: String): Path {
return generatedCordapps.computeIfAbsent(url) {
// TODO Using the driver in out-of-process mode causes each node to have their own copy of the same dev CorDapps
val cordappDir = (Paths.get("build") / "tmp" / "generated-test-cordapps").createDirectories()
val uuid = UUID.randomUUID()
val cordappJar = cordappDir / "$scanPackage-$uuid.jar"
logger.info("Generating a test-only CorDapp of classes discovered for package $scanPackage in $url: $cordappJar")
val manifest = createTestManifest(resource, scanPackage, uuid)
val scanDir = url.toPath()
JarOutputStream(cordappJar.outputStream(), manifest).use { jos ->
scanDir.walk {
it.forEach {
val entryPath = "$resource/${scanDir.relativize(it).toString().replace('\\', '/')}"
val time = FileTime.from(Instant.EPOCH)
val entry = ZipEntry(entryPath).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
jos.putNextEntry(entry)
if (it.isRegularFile()) {
it.copyTo(jos)
}
jos.closeEntry()
}
}
}
cordappJar
}
}
private fun getNodeCordappURLs(baseDir: Path): List<RestrictedURL> {
val cordappsDir = baseDir / CORDAPPS_DIR_NAME
return if (!cordappsDir.exists()) {
emptyList()
} else {
cordappsDir.list {
it.filter { it.toString().endsWith(".jar") }.map { RestrictedURL(it.toUri().toURL(), null) }.toList()
}
}
}
/** A list of the core RPC flows present in Corda */
private val coreRPCFlows = listOf(
ContractUpgradeFlow.Initiate::class.java,
ContractUpgradeFlow.Authorise::class.java,
ContractUpgradeFlow.Deauthorise::class.java)
private fun loadCordapps(): List<Cordapp> {
return cordappJarPaths.map { scanCordapp(it).toCordapp(it) }
}
private fun loadCordapps(): List<Cordapp> {
return cordappJarPaths.map {
val url = it.url
val name = url.toPath().fileName.toString().removeSuffix(".jar")
val info = url.openStream().let(::JarInputStream).use { it.manifest }.toCordappInfo(name)
val scanResult = scanCordapp(it)
CordappImpl(contractClassNames = findContractClassNames(scanResult),
initiatedFlows = findInitiatedFlows(scanResult),
rpcFlows = findRPCFlows(scanResult),
serviceFlows = findServiceFlows(scanResult),
schedulableFlows = findSchedulableFlows(scanResult),
services = findServices(scanResult),
serializationWhitelists = findPlugins(it),
serializationCustomSerializers = findSerializers(scanResult),
customSchemas = findCustomSchemas(scanResult),
allFlows = findAllFlows(scanResult),
jarPath = it.url,
info = info,
jarHash = getJarHash(it.url),
name = name
)
}
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): Cordapp {
val name = url.url.toPath().fileName.toString().removeSuffix(".jar")
val info = url.url.openStream().let(::JarInputStream).use { it.manifest }.toCordappInfo(name)
return CordappImpl(
findContractClassNames(this),
findInitiatedFlows(this),
findRPCFlows(this),
findServiceFlows(this),
findSchedulableFlows(this),
findServices(this),
findPlugins(url),
findSerializers(this),
findCustomSchemas(this),
findAllFlows(this),
url.url,
info,
getJarHash(url.url),
name
)
}
private fun getJarHash(url: URL): SecureHash.SHA256 = url.openStream().readFully().sha256()
@ -328,11 +194,12 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
}
private val cachedScanResult = LRUMap<RestrictedURL, RestrictedScanResult>(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<out FlowLogic<*>>) : Comparator<Class<out FlowLogic<*>>> {
@ -406,3 +273,26 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
* Thrown when scanning CorDapps.
*/
class MultipleCordappsForFlowException(message: String) : Exception(message)
abstract class CordappLoaderTemplate : CordappLoader {
override val flowCordappMap: Map<Class<out FlowLogic<*>>, 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<MappedSchema> by lazy {
cordapps.flatMap { it.customSchemas }.toSet()
}
override val appClassLoader: ClassLoader by lazy {
URLClassLoader(cordapps.stream().map { it.jarPath }.toTypedArray(), javaClass.classLoader)
}
}

View File

@ -16,10 +16,9 @@ import java.util.*
import java.util.jar.Attributes
import java.util.jar.Manifest
internal fun createTestManifest(name: String, title: String, jarUUID: UUID): Manifest {
fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest {
val manifest = Manifest()
val version = "test-$jarUUID"
val vendor = "R3"
// Mandatory manifest attribute. If not present, all other entries are silently skipped.
manifest.mainAttributes[Attributes.Name.MANIFEST_VERSION] = "1.0"
@ -37,11 +36,18 @@ internal fun createTestManifest(name: String, title: String, jarUUID: UUID): Man
return manifest
}
internal operator fun Manifest.set(key: String, value: String) {
internal fun createTestManifest(name: String, title: String, jarUUID: UUID): Manifest {
return createTestManifest(name, title, "test-$jarUUID", "R3")
}
operator fun Manifest.set(key: String, value: String) {
mainAttributes.putValue(key, value)
}
internal fun Manifest?.toCordappInfo(defaultShortName: String): Cordapp.Info {
var unknown = CordappImpl.Info.UNKNOWN
(this?.mainAttributes?.getValue("Name") ?: defaultShortName).let { shortName ->
unknown = unknown.copy(shortName = shortName)

View File

@ -15,6 +15,7 @@ import com.typesafe.config.ConfigException
import net.corda.core.context.AuthServiceId
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.TimedFlow
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds
@ -39,6 +40,7 @@ val Int.MB: Long get() = this * 1024L * 1024L
private val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1)
private val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1)
private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps"
interface NodeConfiguration : NodeSSLConfiguration {
val myLegalName: CordaX500Name
@ -83,6 +85,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
val effectiveH2Settings: NodeH2Settings?
val flowMonitorPeriodMillis: Duration get() = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS
val flowMonitorSuspensionLoggingThresholdMillis: Duration get() = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS
val cordappDirectories: List<Path> get() = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT)
fun validate(): List<String>
@ -97,6 +100,8 @@ interface NodeConfiguration : NodeSSLConfiguration {
val defaultAttachmentContentCacheSize: Long = 10.MB
const val defaultAttachmentCacheBound = 1024L
const val cordappDirectoriesKey = "cordappDirectories"
}
}
@ -262,7 +267,8 @@ data class NodeConfigurationImpl(
// do not use or remove (used by Capsule)
private val jarDirs: List<String> = 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<Path> = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT)
) : NodeConfiguration {
companion object {
private val logger = loggerFor<NodeConfigurationImpl>()

View File

@ -0,0 +1 @@
key=value

View File

@ -49,6 +49,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.expect
import net.corda.testing.core.expectEvents
import net.corda.testing.core.sequence
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.internal.InternalMockNodeParameters
@ -91,7 +92,7 @@ class CordaRPCOpsImplTest {
@Before
fun setup() {
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"))
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset", "net.corda.finance.schemas"))
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
rpc = aliceNode.rpcOps
CURRENT_RPC_CONTEXT.set(RpcAuthContext(InvocationContext.rpc(testActor()), buildSubject("TEST_USER", emptySet())))

View File

@ -15,6 +15,7 @@ import net.corda.core.node.services.CordaService
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.cordappsForPackages
import org.junit.After
import org.junit.Assert.assertTrue
import org.junit.Test
@ -27,7 +28,7 @@ class NodeUnloadHandlerTests {
val shutdownLatch = CountDownLatch(1)
}
private val mockNet = InternalMockNetwork(cordappPackages = listOf(javaClass.packageName), notarySpecs = emptyList())
private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(javaClass.packageName), notarySpecs = emptyList())
@After
fun cleanUp() {

View File

@ -85,7 +85,7 @@ class CordappProviderImplTests {
fun `test cordapp configuration`() {
val configProvider = MockCordappConfigProvider()
configProvider.cordappConfigs[isolatedCordappName] = validConfig
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
val provider = CordappProviderImpl(loader, configProvider, attachmentStore, whitelistedContractImplementations)
val expected = provider.getAppContext(provider.cordapps.first()).config
@ -94,7 +94,7 @@ class CordappProviderImplTests {
}
private fun newCordappProvider(vararg urls: URL): CordappProviderImpl {
val loader = CordappLoader.createDevMode(urls.toList())
val loader = JarScanningCordappLoader.fromJarUrls(urls.toList())
return CordappProviderImpl(loader, stubConfigProvider, attachmentStore, whitelistedContractImplementations)
}
}

View File

@ -12,8 +12,13 @@ package net.corda.node.internal.cordapp
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.*
import net.corda.node.cordapp.CordappLoader
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.getTimestampAsDirectoryName
import net.corda.testing.node.internal.packageInDirectory
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths
@InitiatingFlow
@ -40,7 +45,7 @@ class DummyRPCFlow : FlowLogic<Unit>() {
override fun call() = Unit
}
class CordappLoaderTest {
class JarScanningCordappLoaderTest {
private companion object {
const val testScanPackage = "net.corda.node.internal.cordapp"
const val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract"
@ -50,14 +55,14 @@ class CordappLoaderTest {
@Test
fun `test that classes that aren't in cordapps aren't loaded`() {
// Basedir will not be a corda node directory so the dummy flow shouldn't be recognised as a part of a cordapp
val loader = CordappLoader.createDefault(Paths.get("."))
val loader = JarScanningCordappLoader.fromDirectories(listOf(Paths.get(".")))
assertThat(loader.cordapps).containsOnly(loader.coreCordapp)
}
@Test
fun `isolated JAR contains a CorDapp with a contract and plugin`() {
val isolatedJAR = CordappLoaderTest::class.java.getResource("isolated.jar")!!
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("isolated.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
val actual = loader.cordapps.toTypedArray()
assertThat(actual).hasSize(2)
@ -75,13 +80,11 @@ class CordappLoaderTest {
@Test
fun `flows are loaded by loader`() {
val loader = CordappLoader.createWithTestPackages(listOf(testScanPackage))
val loader = cordappLoaderForPackages(listOf(testScanPackage))
val actual = loader.cordapps.toTypedArray()
// One core cordapp, one cordapp from this source tree, and two others due to identically named locations
// in resources and the non-test part of node. This is okay due to this being test code. In production this
// cannot happen. In gradle it will also pick up the node jar.
assertThat(actual.size == 4 || actual.size == 5).isTrue()
// One core cordapp, one cordapp from this source tree. In gradle it will also pick up the node jar.
assertThat(actual.size == 2 || actual.size == 3).isTrue()
val actualCordapp = actual.single { !it.initiatedFlows.isEmpty() }
assertThat(actualCordapp.initiatedFlows).first().hasSameClassAs(DummyFlow::class.java)
@ -91,14 +94,14 @@ class CordappLoaderTest {
@Test
fun `duplicate packages are ignored`() {
val loader = CordappLoader.createWithTestPackages(listOf(testScanPackage, testScanPackage))
val loader = cordappLoaderForPackages(listOf(testScanPackage, testScanPackage))
val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows }
assertThat(cordapps).hasSize(1)
}
@Test
fun `sub-packages are ignored`() {
val loader = CordappLoader.createWithTestPackages(listOf("net.corda", testScanPackage))
val loader = cordappLoaderForPackages(listOf("net.corda", testScanPackage))
val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows }
assertThat(cordapps).hasSize(1)
}
@ -107,10 +110,24 @@ class CordappLoaderTest {
// being used internally. Later iterations will use a classloader per cordapp and this test can be retired.
@Test
fun `cordapp classloader can load cordapp classes`() {
val isolatedJAR = CordappLoaderTest::class.java.getResource("isolated.jar")!!
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("isolated.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
loader.appClassLoader.loadClass(isolatedContractId)
loader.appClassLoader.loadClass(isolatedFlowName)
}
private fun cordappLoaderForPackages(packages: Iterable<String>): 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())
}
}

View File

@ -60,6 +60,7 @@ import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.InMemoryMessagingNetwork
import net.corda.testing.node.MockServices
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.pumpReceive
@ -89,7 +90,7 @@ import kotlin.test.assertTrue
@RunWith(Parameterized::class)
class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
companion object {
private val cordappPackages = listOf("net.corda.finance.contracts", "net.corda.finance.schemas")
private val cordappPackages = setOf("net.corda.finance.contracts")
@JvmStatic
@Parameterized.Parameters(name = "Anonymous = {0}")
fun data(): Collection<Boolean> = listOf(true, false)
@ -117,7 +118,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
// We run this in parallel threads to help catch any race conditions that may exist. The other tests
// we run in the unit test thread exclusively to speed things up, ensure deterministic results and
// allow interruption half way through.
mockNet = InternalMockNetwork(cordappPackages = cordappPackages, threadPerNode = true)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages), threadPerNode = true)
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
val notaryNode = mockNet.defaultNotaryNode
@ -169,7 +170,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test(expected = InsufficientBalanceException::class)
fun `trade cash for commercial paper fails using soft locking`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages, threadPerNode = true)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages), threadPerNode = true)
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
val notaryNode = mockNet.defaultNotaryNode
@ -227,7 +228,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `shutdown and restore`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
val notaryNode = mockNet.defaultNotaryNode
@ -326,11 +327,20 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
// of gets and puts.
private fun makeNodeWithTracking(name: CordaX500Name): StartedNode<InternalMockNetwork.MockNode> {
// Create a node in the mock network ...
return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args ->
object : InternalMockNetwork.MockNode(args) {
// That constructs a recording tx storage
override fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage {
return RecordingTransactionStorage(database, super.makeTransactionStorage(database, transactionCacheSizeBytes))
return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args, cordappLoader ->
if (cordappLoader != null) {
object : InternalMockNetwork.MockNode(args, cordappLoader) {
// That constructs a recording tx storage
override fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage {
return RecordingTransactionStorage(database, super.makeTransactionStorage(database, transactionCacheSizeBytes))
}
}
} else {
object : InternalMockNetwork.MockNode(args) {
// That constructs a recording tx storage
override fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage {
return RecordingTransactionStorage(database, super.makeTransactionStorage(database, transactionCacheSizeBytes))
}
}
}
})
@ -338,7 +348,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `check dependencies of sale asset are resolved`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
val notaryNode = mockNet.defaultNotaryNode
val aliceNode = makeNodeWithTracking(ALICE_NAME)
val bobNode = makeNodeWithTracking(BOB_NAME)
@ -442,7 +452,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `track works`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
val notaryNode = mockNet.defaultNotaryNode
val aliceNode = makeNodeWithTracking(ALICE_NAME)
val bobNode = makeNodeWithTracking(BOB_NAME)
@ -520,7 +530,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `dependency with error on buyer side`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
runWithError(ledgerIdentityService, true, false, "at least one cash input")
@ -529,7 +539,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `dependency with error on seller side`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
runWithError(ledgerIdentityService, false, true, "Issuances have a time-window")

View File

@ -11,11 +11,7 @@
package net.corda.node.modes.draining
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.SchedulableState
import net.corda.core.contracts.ScheduledActivity
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.contracts.*
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowLogicRefFactory
@ -30,6 +26,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.startFlow
@ -61,7 +58,7 @@ class ScheduledFlowsDrainingModeTest {
@Before
fun setup() {
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"), threadPerNode = true)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), threadPerNode = true)
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
bobNode = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME))
notary = mockNet.defaultNotaryIdentity

View File

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

View File

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

View File

@ -48,6 +48,7 @@ import net.corda.testing.node.InMemoryMessagingNetwork
import net.corda.testing.node.MockNetworkParameters
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.startFlow
import org.junit.Before
import org.junit.ClassRule
@ -108,8 +109,8 @@ class TimedFlowTestRule(val clusterSize: Int) : ExternalResource() {
override fun before() {
mockNet = InternalMockNetwork(
listOf("net.corda.testing.contracts", "net.corda.node.services"),
MockNetworkParameters().withServicePeerAllocationStrategy(InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin()),
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts", "net.corda.node.services"),
defaultParameters = MockNetworkParameters().withServicePeerAllocationStrategy(InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin()),
threadPerNode = true
)
val started = startClusterAndNode(mockNet)

View File

@ -33,6 +33,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.internal.InternalMockNodeParameters
@ -111,7 +112,7 @@ class ScheduledFlowTests {
@Before
fun setup() {
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"), threadPerNode = true)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), threadPerNode = true)
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
bobNode = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME))
notary = mockNet.defaultNotaryIdentity

View File

@ -25,6 +25,7 @@ import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.InProcessImpl
import net.corda.testing.internal.vault.DummyLinearStateSchemaV1
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InternalMockNetwork
import org.hibernate.annotations.Cascade
import org.hibernate.annotations.CascadeType
@ -41,7 +42,7 @@ class NodeSchemaServiceTest {
*/
@Test
fun `registering custom schemas for testing with MockNode`() {
val mockNet = InternalMockNetwork(cordappPackages = listOf(DummyLinearStateSchemaV1::class.packageName))
val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(DummyLinearStateSchemaV1::class.packageName))
val mockNode = mockNet.createNode()
val schemaService = mockNode.services.schemaService
assertTrue(schemaService.schemaOptions.containsKey(DummyLinearStateSchemaV1))
@ -74,8 +75,6 @@ class NodeSchemaServiceTest {
/**
* Note: this test verifies auto-scanning to register identified [MappedSchema] schemas.
* By default, Driver uses the caller package for auto-scanning:
* System.setProperty("net.corda.node.cordapp.scan.packages", callerPackage)
*/
@Test
fun `auto scanning of custom schemas for testing with Driver`() {

View File

@ -78,7 +78,7 @@ class FlowFrameworkTests {
@JvmStatic
fun beforeClass() {
mockNet = InternalMockNetwork(
cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"),
cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts", "net.corda.testing.contracts"),
servicePeerAllocationStrategy = RoundRobin()
)
@ -491,7 +491,7 @@ class FlowFrameworkTripartyTests {
@JvmStatic
fun beforeClass() {
mockNet = InternalMockNetwork(
cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"),
cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts", "net.corda.testing.contracts"),
servicePeerAllocationStrategy = RoundRobin()
)
@ -655,7 +655,7 @@ class FlowFrameworkPersistenceTests {
@Before
fun start() {
mockNet = InternalMockNetwork(
cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"),
cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts", "net.corda.testing.contracts"),
servicePeerAllocationStrategy = RoundRobin()
)
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))

View File

@ -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 = {

View File

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

View File

@ -25,6 +25,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetworkNotarySpec
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.startFlow
@ -43,7 +44,7 @@ class NotaryServiceTests {
@Before
fun setup() {
mockNet = InternalMockNetwork(
cordappPackages = listOf("net.corda.testing.contracts"),
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"),
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME, validating = false))
)
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))

View File

@ -46,6 +46,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.TestClock
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InMemoryMessage
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
@ -72,7 +73,7 @@ class ValidatingNotaryServiceTests {
@Before
fun setup() {
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"))
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
notaryNode = mockNet.defaultNotaryNode
notary = mockNet.defaultNotaryIdentity

View File

@ -39,6 +39,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.testing.core.singleIdentity
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.startFlow
import org.junit.After
@ -90,7 +91,7 @@ class VaultSoftLockManagerTest {
private val mockVault = rigorousMock<VaultServiceInternal>().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

View File

@ -21,6 +21,7 @@ import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.packageName
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService
import net.corda.core.node.services.queryBy

View File

@ -23,6 +23,7 @@ import net.corda.node.internal.StartedNode
import net.corda.testing.core.*
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.startFlow
import org.junit.After
import org.junit.Before
@ -40,7 +41,7 @@ class CashIssueAndPaymentFlowTests {
@Before
fun start() {
mockNet = InternalMockNetwork(servicePeerAllocationStrategy = RoundRobin(),
cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"))
cordappsForAllNodes = cordappsForPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"))
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bankOfCorda = bankOfCordaNode.info.singleIdentity()

View File

@ -22,9 +22,8 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode
import net.corda.testing.core.*
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.startFlow
import org.junit.After
import org.junit.Before
@ -51,7 +50,7 @@ class CashIssueAndPayNoSelectionTests(private val anonymous: Boolean) {
@Before
fun start() {
mockNet = InternalMockNetwork(servicePeerAllocationStrategy = RoundRobin(),
cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"))
cordappsForAllNodes = cordappsForPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"))
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bankOfCorda = bankOfCordaNode.info.singleIdentity()

View File

@ -23,6 +23,7 @@ import net.corda.node.internal.StartedNode
import net.corda.testing.core.*
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.node.internal.startFlow
import org.junit.After
import org.junit.Before
@ -42,7 +43,7 @@ class CashPaymentFlowTests {
fun start() {
mockNet = InternalMockNetwork(
servicePeerAllocationStrategy = RoundRobin(),
cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"))
cordappsForAllNodes = cordappsForPackages("com.r3.corda.enterprise.perftestcordapp.contracts.asset", "com.r3.corda.enterprise.perftestcordapp.schemas"))
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bankOfCorda = bankOfCordaNode.info.singleIdentity()

View File

@ -64,10 +64,7 @@ import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.TEST_TX_TIME
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.*
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.pumpReceive
import net.corda.testing.node.internal.startFlow
import net.corda.testing.node.internal.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
@ -99,8 +96,8 @@ internal fun CheckpointStorage.checkpoints(): List<SerializedBytes<Checkpoint>>
@RunWith(Parameterized::class)
class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
companion object {
private val cordappPackages = listOf(
"com.r3.corda.enterprise.perftestcordapp.contracts", "com.r3.corda.enterprise.perftestcordapp.schemas")
private val cordappPackages = listOf("com.r3.corda.enterprise.perftestcordapp.contracts", "com.r3.corda.enterprise.perftestcordapp.schemas")
private val cordappsForAllNodes = cordappsForPackages(cordappPackages)
@JvmStatic
@Parameterized.Parameters(name = "Anonymous = {0}")
fun data(): Collection<Boolean> = listOf(true, false)
@ -128,7 +125,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
// We run this in parallel threads to help catch any race conditions that may exist. The other tests
// we run in the unit test thread exclusively to speed things up, ensure deterministic results and
// allow interruption half way through.
mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(threadPerNode = true, cordappsForAllNodes = cordappsForAllNodes)
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
val notaryNode = mockNet.defaultNotaryNode
@ -181,7 +178,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test(expected = InsufficientBalanceException::class)
fun `trade cash for commercial paper fails using soft locking`() {
mockNet = InternalMockNetwork(threadPerNode = true, cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(threadPerNode = true, cordappsForAllNodes = cordappsForAllNodes)
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
val notaryNode = mockNet.defaultNotaryNode
@ -239,7 +236,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `shutdown and restore`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForAllNodes)
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
val notaryNode = mockNet.defaultNotaryNode
@ -340,7 +337,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
private fun makeNodeWithTracking(
name: CordaX500Name): StartedNode<InternalMockNetwork.MockNode> {
// Create a node in the mock network ...
return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args ->
return mockNet.createNode(InternalMockNodeParameters(legalName = name), nodeFactory = { args, _ ->
object : InternalMockNetwork.MockNode(args) {
// That constructs a recording tx storage
override fun makeTransactionStorage(database: CordaPersistence, transactionCacheSizeBytes: Long): WritableTransactionStorage {
@ -352,7 +349,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `check dependencies of sale asset are resolved`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForAllNodes)
val notaryNode = mockNet.defaultNotaryNode
val aliceNode = makeNodeWithTracking(ALICE_NAME)
val bobNode = makeNodeWithTracking(BOB_NAME)
@ -456,7 +453,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `track works`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForAllNodes)
val notaryNode = mockNet.defaultNotaryNode
val aliceNode = makeNodeWithTracking(ALICE_NAME)
val bobNode = makeNodeWithTracking(BOB_NAME)
@ -534,7 +531,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `dependency with error on buyer side`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForAllNodes)
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
runWithError(ledgerIdentityService, true, false, "at least one cash input")
@ -543,7 +540,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
@Test
fun `dependency with error on seller side`() {
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForAllNodes)
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
runWithError(ledgerIdentityService, false, true, "Issuances have a time-window")

View File

@ -23,7 +23,7 @@ import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.nodeapi.DummyContractBackdoor
import net.corda.testing.common.internal.testNetworkParameters
@ -67,7 +67,7 @@ class AttachmentsClassLoaderTests {
val testSerialization = SerializationEnvironmentRule()
private val attachments = MockAttachmentStorage()
private val networkParameters = testNetworkParameters()
private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments, networkParameters.whitelistedContractImplementations)
private val cordappProvider = CordappProviderImpl(JarScanningCordappLoader.fromJarUrls(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments, networkParameters.whitelistedContractImplementations)
private val cordapp get() = cordappProvider.cordapps.first()
private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
private val appContext get() = cordappProvider.getAppContext(cordapp)

View File

@ -86,3 +86,15 @@ include 'cordform-common'
include 'hsm-tool'
include 'launcher'
include 'node:dist'
buildCache {
local {
enabled = false
}
remote(HttpBuildCache) {
// url = 'http://localhost:5071/cache/'
// CI server: gradle-build-cache
url = 'http://40.114.193.246:80/cache/'
push = true
}
}

View File

@ -30,10 +30,7 @@ import net.corda.testing.driver.PortAllocation.Incremental
import net.corda.testing.driver.internal.internalServices
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User
import net.corda.testing.node.internal.DriverDSLImpl
import net.corda.testing.node.internal.genericDriver
import net.corda.testing.node.internal.getTimestampAsDirectoryName
import net.corda.testing.node.internal.newContext
import net.corda.testing.node.internal.*
import rx.Observable
import java.nio.file.Path
import java.nio.file.Paths
@ -146,6 +143,9 @@ abstract class PortAllocation {
* @property startInSameProcess Determines if the node should be started inside the same process the Driver is running
* in. If null the Driver-level value will be used.
* @property maximumHeapSize The maximum JVM heap size to use for the node.
* @property logLevel Logging level threshold.
* @property additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL].
* @property regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node.
*/
@Suppress("unused")
data class NodeParameters(
@ -155,8 +155,57 @@ data class NodeParameters(
val customOverrides: Map<String, Any?> = emptyMap(),
val startInSameProcess: Boolean? = null,
val maximumHeapSize: String = "512m",
val logLevel: String? = null
val logLevel: String? = null,
val additionalCordapps: Set<TestCorDapp> = 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<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
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<User>,
@ -171,7 +220,44 @@ data class NodeParameters(
customOverrides,
startInSameProcess,
maximumHeapSize,
null)
null,
additionalCordapps = emptySet(),
regenerateCordappsOnStart = false)
/**
* Helper builder for configuring a [Node] from Java.
*
* @param providedName Optional name of the node, which will be its legal name in [Party]. Defaults to something
* random. Note that this must be unique as the driver uses it as a primary key!
* @param rpcUsers List of users who are authorised to use the RPC system. Defaults to a single user with
* all permissions.
* @param verifierType The type of transaction verifier to use. See: [VerifierType]
* @param customOverrides A map of custom node configuration overrides.
* @param startInSameProcess Determines if the node should be started inside the same process the Driver is running
* in. If null the Driver-level value will be used.
* @param maximumHeapSize The maximum JVM heap size to use for the node.
* @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL].
* @param regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node.
*/
constructor(
providedName: CordaX500Name?,
rpcUsers: List<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?,
maximumHeapSize: String,
additionalCordapps: Set<TestCorDapp> = emptySet(),
regenerateCordappsOnStart: Boolean = false
) : this(
providedName,
rpcUsers,
verifierType,
customOverrides,
startInSameProcess,
maximumHeapSize,
null,
additionalCordapps,
regenerateCordappsOnStart)
fun copy(
providedName: CordaX500Name?,
@ -189,6 +275,25 @@ data class NodeParameters(
maximumHeapSize,
null)
fun copy(
providedName: CordaX500Name?,
rpcUsers: List<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
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<User>): NodeParameters = copy(rpcUsers = rpcUsers)
fun withVerifierType(verifierType: VerifierType): NodeParameters = copy(verifierType = verifierType)
@ -196,6 +301,8 @@ data class NodeParameters(
fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess)
fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize)
fun withLogLevel(logLevel: String?): NodeParameters = copy(logLevel = logLevel)
fun withAdditionalCordapps(additionalCordapps: Set<TestCorDapp>): NodeParameters = copy(additionalCordapps = additionalCordapps)
fun withDeleteExistingCordappsDirectory(regenerateCordappsOnStart: Boolean): NodeParameters = copy(regenerateCordappsOnStart = regenerateCordappsOnStart)
}
/**
@ -240,12 +347,12 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
startNodesInProcess = defaultParameters.startNodesInProcess,
waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish,
notarySpecs = defaultParameters.notarySpecs,
extraCordappPackagesToScan = defaultParameters.extraCordappPackagesToScan,
jmxPolicy = defaultParameters.jmxPolicy,
compatibilityZone = null,
networkParameters = defaultParameters.networkParameters,
notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
inMemoryDB = defaultParameters.inMemoryDB
inMemoryDB = defaultParameters.inMemoryDB,
cordappsForAllNodes = defaultParameters.cordappsForAllNodes()
),
coerce = { it },
dsl = dsl,
@ -282,6 +389,7 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
* @property inMemoryDB Whether to use in-memory H2 for new nodes rather then on-disk (the node starts quicker, however
* the data is not persisted between node restarts). Has no effect if node is configured
* in any way to use database other than H2.
* @property cordappsForAllNodes [TestCorDapp]s that will be added to each node started by the [DriverDSL].
*/
@Suppress("unused")
data class DriverParameters(
@ -299,8 +407,44 @@ data class DriverParameters(
val networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()),
val notaryCustomOverrides: Map<String, Any?> = emptyMap(),
val initialiseSerialization: Boolean = true,
val inMemoryDB: Boolean = true
val inMemoryDB: Boolean = true,
val cordappsForAllNodes: Set<TestCorDapp>? = null
) {
constructor(
isDebug: Boolean = false,
driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()),
portAllocation: PortAllocation = PortAllocation.Incremental(10000),
debugPortAllocation: PortAllocation = PortAllocation.Incremental(5005),
systemProperties: Map<String, String> = emptyMap(),
useTestClock: Boolean = false,
startNodesInProcess: Boolean = false,
waitForAllNodesToFinish: Boolean = false,
notarySpecs: List<NotarySpec> = listOf(NotarySpec(DUMMY_NOTARY_NAME)),
extraCordappPackagesToScan: List<String> = emptyList(),
jmxPolicy: JmxPolicy = JmxPolicy(),
networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()),
notaryCustomOverrides: Map<String, Any?> = emptyMap(),
initialiseSerialization: Boolean = true,
inMemoryDB: Boolean = true
) : this(
isDebug,
driverDirectory,
portAllocation,
debugPortAllocation,
systemProperties,
useTestClock,
startNodesInProcess,
waitForAllNodesToFinish,
notarySpecs,
extraCordappPackagesToScan,
jmxPolicy,
networkParameters,
notaryCustomOverrides,
initialiseSerialization,
inMemoryDB,
cordappsForAllNodes = null
)
constructor(
isDebug: Boolean,
driverDirectory: Path,
@ -329,7 +473,41 @@ data class DriverParameters(
networkParameters,
emptyMap(),
true,
true
true,
cordappsForAllNodes = null
)
constructor(
isDebug: Boolean,
driverDirectory: Path,
portAllocation: PortAllocation,
debugPortAllocation: PortAllocation,
systemProperties: Map<String, String>,
useTestClock: Boolean,
startNodesInProcess: Boolean,
waitForAllNodesToFinish: Boolean,
notarySpecs: List<NotarySpec>,
extraCordappPackagesToScan: List<String>,
jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters,
cordappsForAllNodes: Set<TestCorDapp>? = null
) : this(
isDebug,
driverDirectory,
portAllocation,
debugPortAllocation,
systemProperties,
useTestClock,
startNodesInProcess,
waitForAllNodesToFinish,
notarySpecs,
extraCordappPackagesToScan,
jmxPolicy,
networkParameters,
emptyMap(),
true,
true,
cordappsForAllNodes
)
constructor(
@ -362,7 +540,43 @@ data class DriverParameters(
networkParameters,
emptyMap(),
initialiseSerialization,
inMemoryDB
inMemoryDB,
cordappsForAllNodes = null
)
constructor(
isDebug: Boolean,
driverDirectory: Path,
portAllocation: PortAllocation,
debugPortAllocation: PortAllocation,
systemProperties: Map<String, String>,
useTestClock: Boolean,
startNodesInProcess: Boolean,
waitForAllNodesToFinish: Boolean,
notarySpecs: List<NotarySpec>,
extraCordappPackagesToScan: List<String>,
jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters,
initialiseSerialization: Boolean,
inMemoryDB: Boolean,
cordappsForAllNodes: Set<TestCorDapp>? = null
) : this(
isDebug,
driverDirectory,
portAllocation,
debugPortAllocation,
systemProperties,
useTestClock,
startNodesInProcess,
waitForAllNodesToFinish,
notarySpecs,
extraCordappPackagesToScan,
jmxPolicy,
networkParameters,
emptyMap(),
initialiseSerialization,
inMemoryDB,
cordappsForAllNodes
)
fun withIsDebug(isDebug: Boolean): DriverParameters = copy(isDebug = isDebug)
@ -380,6 +594,7 @@ data class DriverParameters(
fun withNetworkParameters(networkParameters: NetworkParameters): DriverParameters = copy(networkParameters = networkParameters)
fun withNotaryCustomOverrides(notaryCustomOverrides: Map<String, Any?>): DriverParameters = copy(notaryCustomOverrides = notaryCustomOverrides)
fun withInMemoryDB(inMemoryDB: Boolean): DriverParameters = copy(inMemoryDB = inMemoryDB)
fun withCordappsForAllNodes(cordappsForAllNodes: Set<TestCorDapp>?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes)
fun copy(
isDebug: Boolean,
@ -441,4 +656,69 @@ data class DriverParameters(
notaryCustomOverrides = emptyMap(),
initialiseSerialization = initialiseSerialization
)
fun copy(
isDebug: Boolean,
driverDirectory: Path,
portAllocation: PortAllocation,
debugPortAllocation: PortAllocation,
systemProperties: Map<String, String>,
useTestClock: Boolean,
startNodesInProcess: Boolean,
waitForAllNodesToFinish: Boolean,
notarySpecs: List<NotarySpec>,
extraCordappPackagesToScan: List<String>,
jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters,
cordappsForAllNodes: Set<TestCorDapp>?
) = 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<String, String>,
useTestClock: Boolean,
startNodesInProcess: Boolean,
waitForAllNodesToFinish: Boolean,
notarySpecs: List<NotarySpec>,
extraCordappPackagesToScan: List<String>,
jmxPolicy: JmxPolicy,
networkParameters: NetworkParameters,
initialiseSerialization: Boolean,
cordappsForAllNodes: Set<TestCorDapp>?
) = 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
)
}

View File

@ -90,6 +90,38 @@ interface DriverDSL {
maximumHeapSize: String = defaultParameters.maximumHeapSize
): CordaFuture<NodeHandle>
/**
* 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<User> = defaultParameters.rpcUsers,
verifierType: VerifierType = defaultParameters.verifierType,
customOverrides: Map<String, Any?> = defaultParameters.customOverrides,
startInSameProcess: Boolean? = defaultParameters.startInSameProcess,
maximumHeapSize: String = defaultParameters.maximumHeapSize,
additionalCordapps: Set<TestCorDapp> = defaultParameters.additionalCordapps,
regenerateCordappsOnStart: Boolean = defaultParameters.regenerateCordappsOnStart
): CordaFuture<NodeHandle>
/**
* Helper function for starting a [Node] with custom parameters from Java.
*

View File

@ -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<Class<*>>
val resources: Set<URL>
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<Class<*>> = 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<Class<*>>): TestCorDapp.Mutable
fun plusPackages(pckgs: Set<String>): TestCorDapp.Mutable
fun minusPackages(pckgs: Set<String>): 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
}
}

View File

@ -24,10 +24,8 @@ import net.corda.node.internal.StartedNode
import net.corda.node.services.config.NodeConfiguration
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.node.internal.InternalMockMessagingService
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.newContext
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.node.internal.*
import rx.Observable
import java.math.BigInteger
import java.nio.file.Path
@ -43,22 +41,36 @@ import java.nio.file.Path
* @property entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value,
* but can be overridden to cause nodes to have stable or colliding identity/service keys.
* @property configOverrides Add/override behaviour of the [NodeConfiguration] mock object.
* @property extraCordappPackages Extra CorDapp packages to include for this node.
* @property additionalCordapps [TestCorDapp]s that will be added to this node in addition to the ones shared by all nodes, which get specified at [MockNetwork] level.
*/
@Suppress("unused")
data class MockNodeParameters @JvmOverloads constructor(
data class MockNodeParameters constructor(
val forcedID: Int? = null,
val legalName: CordaX500Name? = null,
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
val configOverrides: (NodeConfiguration) -> Any? = {},
val extraCordappPackages: List<String> = emptyList()) {
val additionalCordapps: Set<TestCorDapp>) {
@JvmOverloads
constructor(
forcedID: Int? = null,
legalName: CordaX500Name? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: (NodeConfiguration) -> Any? = {},
extraCordappPackages: List<String> = 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<String>): MockNodeParameters = copy(extraCordappPackages = extraCordappPackages)
fun withExtraCordappPackages(extraCordappPackages: List<String>): MockNodeParameters = copy(forcedID = forcedID, legalName = legalName, entropyRoot = entropyRoot, configOverrides = configOverrides, extraCordappPackages = extraCordappPackages)
fun withAdditionalCordapps(additionalCordapps: Set<TestCorDapp>): 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<String> = emptyList()): MockNodeParameters {
return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, extraCordappPackages)
}
}
@ -215,6 +227,7 @@ class StartedMockNode private constructor(private val node: StartedNode<Internal
* @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient.
* @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be
* empty as notaries are defined by [notarySpecs].
* @property cordappsForAllNodes [TestCorDapp]s that will be added to each node started by the [MockNetwork].
*/
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
open class MockNetwork(
@ -224,11 +237,23 @@ open class MockNetwork(
val threadPerNode: Boolean = defaultParameters.threadPerNode,
val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
val notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
val networkParameters: NetworkParameters = defaultParameters.networkParameters) {
val networkParameters: NetworkParameters = defaultParameters.networkParameters,
val cordappsForAllNodes: Set<TestCorDapp> = cordappsForPackages(cordappPackages)) {
@JvmOverloads
constructor(cordappPackages: List<String>, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters)
private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(cordappPackages, defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters)
constructor(
cordappPackages: List<String>,
defaultParameters: MockNetworkParameters = MockNetworkParameters(),
networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped,
threadPerNode: Boolean = defaultParameters.threadPerNode,
servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
notarySpecs: List<MockNetworkNotarySpec> = defaultParameters.notarySpecs,
networkParameters: NetworkParameters = defaultParameters.networkParameters
) : this(emptyList(), defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters, cordappsForAllNodes = cordappsForPackages(cordappPackages))
private val internalMockNetwork: InternalMockNetwork = InternalMockNetwork(defaultParameters, networkSendManuallyPumped, threadPerNode, servicePeerAllocationStrategy, notarySpecs, networkParameters = networkParameters, cordappsForAllNodes = cordappsForAllNodes)
/** In a mock network, nodes have an incrementing integer ID. Real networks do not have this. Returns the next ID that will be used. */
val nextNodeId get(): Int = internalMockNetwork.nextNodeId
@ -272,7 +297,26 @@ open class MockNetwork(
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: (NodeConfiguration) -> Any? = {},
extraCordappPackages: List<String> = 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<TestCorDapp>): StartedMockNode {
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps)
return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters)))
}
@ -295,7 +339,26 @@ open class MockNetwork(
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: (NodeConfiguration) -> Any? = {},
extraCordappPackages: List<String> = 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<TestCorDapp> = emptySet()): UnstartedMockNode {
val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps)
return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters)))
}

View File

@ -28,9 +28,10 @@ import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.ServicesForResolutionImpl
import net.corda.node.internal.configureDatabase
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.services.api.*
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.schema.HibernateObserver
@ -72,7 +73,15 @@ open class MockServices private constructor(
private val initialIdentity: TestIdentity,
private val moreKeys: Array<out KeyPair>
) : ServiceHub {
companion object {
private fun cordappLoaderForPackages(packages: Iterable<String>): CordappLoader {
val cordappPaths = TestCordappDirectories.forPackages(packages)
return JarScanningCordappLoader.fromDirectories(cordappPaths)
}
/**
* Make properties appropriate for creating a DataSource for unit tests.
* Defaults configuration of in-memory H2 instance. If 'databaseProvider' system property is set then creates
@ -102,7 +111,7 @@ open class MockServices private constructor(
initialIdentity: TestIdentity,
networkParameters: NetworkParameters = testNetworkParameters(),
vararg moreKeys: KeyPair): Pair<CordaPersistence, MockServices> {
val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages)
val cordappLoader = cordappLoaderForPackages(cordappPackages)
val dataSourceProps = makeInternalTestDataSourceProperties(initialIdentity.name.organisation, SecureHash.randomSHA256().toString())
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(initialIdentity.name.organisation), identityService::wellKnownPartyFromX500Name, identityService::wellKnownPartyFromAnonymous, schemaService)
@ -124,14 +133,6 @@ open class MockServices private constructor(
return Pair(database, mockService)
}
@JvmStatic
private fun getCallerPackage(): String {
// TODO: In Java 9 there's a new stack walker API that is better than this.
// The magic number '3' here is to chop off this method, an invisible bridge method generated by the
// compiler and then the c'tor itself.
return Throwable().stackTrace[3].className.split('.').dropLast(1).joinToString(".")
}
// Because Kotlin is dumb and makes not publicly visible objects public, thus changing the public API.
private val mockStateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage()
}
@ -155,39 +156,38 @@ open class MockServices private constructor(
* (you can get one from [makeTestIdentityService]) and represents the given identity.
*/
@JvmOverloads
constructor(cordappPackages: List<String>,
constructor(cordappPackages: Iterable<String>,
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<String>,
constructor(cordappPackages: Iterable<String>,
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<String>, initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService(), key: KeyPair, vararg moreKeys: KeyPair) :
constructor(cordappPackages: Iterable<String>, 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<String>, initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService()) :
constructor(cordappPackages: Iterable<String>, 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<String>) : this(cordappPackages, CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
constructor(cordappPackages: Iterable<String>) : this(cordappPackages, CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
/**
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service
@ -195,7 +195,7 @@ open class MockServices private constructor(
*/
@JvmOverloads
constructor(initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService(), key: KeyPair, vararg moreKeys: KeyPair)
: this(listOf(getCallerPackage()), TestIdentity(initialIdentityName, key), identityService, *moreKeys)
: this(listOf(getCallerPackage(MockServices::class)!!), TestIdentity(initialIdentityName, key), identityService, *moreKeys)
/**
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service
@ -203,7 +203,7 @@ open class MockServices private constructor(
*/
@JvmOverloads
constructor(initialIdentityName: CordaX500Name, identityService: IdentityService = makeTestIdentityService())
: this(listOf(getCallerPackage()), TestIdentity(initialIdentityName), identityService)
: this(listOf(getCallerPackage(MockServices::class)!!), TestIdentity(initialIdentityName), identityService)
/**
* A helper constructor that requires at least one test identity to be registered, and which takes the package of
@ -212,7 +212,7 @@ open class MockServices private constructor(
* it is aware of.
*/
constructor(firstIdentity: TestIdentity, vararg moreIdentities: TestIdentity) : this(
listOf(getCallerPackage()),
listOf(getCallerPackage(MockServices::class)!!),
firstIdentity,
makeTestIdentityService(*listOf(firstIdentity, *moreIdentities).map { it.identity }.toTypedArray()),
firstIdentity.keyPair
@ -222,7 +222,7 @@ open class MockServices private constructor(
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses a default service
* identity.
*/
constructor() : this(listOf(getCallerPackage()), CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
constructor() : this(listOf(getCallerPackage(MockServices::class)!!), CordaX500Name("TestIdentity", "", "GB"), makeTestIdentityService())
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
txs.forEach {
@ -241,7 +241,9 @@ open class MockServices private constructor(
return NodeInfo(listOf(NetworkHostAndPort("mock.node.services", 10000)), listOf(initialIdentity.identity), 1, serial = 1L)
}
override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2)
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments, networkParameters.whitelistedContractImplementations)
override val cordappProvider: CordappProvider get() = mockCordappProvider
protected val servicesForResolution: ServicesForResolution get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParameters, validatedTransactions)

View File

@ -62,6 +62,7 @@ import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User
import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.NON_VALIDATING_RAFT
import net.corda.testing.node.internal.DriverDSLImpl.ClusterType.VALIDATING_RAFT
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
import okhttp3.OkHttpClient
import okhttp3.Request
import rx.Subscription
@ -95,20 +96,19 @@ class DriverDSLImpl(
val isDebug: Boolean,
val startNodesInProcess: Boolean,
val waitForAllNodesToFinish: Boolean,
extraCordappPackagesToScan: List<String>,
val jmxPolicy: JmxPolicy,
val notarySpecs: List<NotarySpec>,
val compatibilityZone: CompatibilityZoneParams?,
val networkParameters: NetworkParameters,
val notaryCustomOverrides: Map<String, Any?>,
val inMemoryDB: Boolean
val inMemoryDB: Boolean,
val cordappsForAllNodes: Set<TestCorDapp>
) : InternalDriverDSL {
private var _executorService: ScheduledExecutorService? = null
val executorService get() = _executorService!!
private var _shutdownManager: ShutdownManager? = null
override val shutdownManager get() = _shutdownManager!!
private val cordappPackages = extraCordappPackagesToScan + getCallerPackage()
// Map from a nodes legal name to an observable emitting the number of nodes in its network map.
private val networkVisibilityController = NetworkVisibilityController()
/**
@ -206,26 +206,7 @@ class DriverDSLImpl(
}
}
override fun startNode(
defaultParameters: NodeParameters,
providedName: CordaX500Name?,
rpcUsers: List<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?,
maximumHeapSize: String
): CordaFuture<NodeHandle> {
return startNode(
defaultParameters,
providedName,
rpcUsers,
verifierType,
customOverrides,
startInSameProcess,
maximumHeapSize,
null
)
}
override fun startNode(defaultParameters: NodeParameters, providedName: CordaX500Name?, rpcUsers: List<User>, verifierType: VerifierType, customOverrides: Map<String, Any?>, startInSameProcess: Boolean?, maximumHeapSize: String) = startNode(defaultParameters, providedName, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, defaultParameters.additionalCordapps, defaultParameters.regenerateCordappsOnStart, null)
override fun startNode(
defaultParameters: NodeParameters,
@ -235,6 +216,20 @@ class DriverDSLImpl(
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?,
maximumHeapSize: String,
additionalCordapps: Set<TestCorDapp>,
regenerateCordappsOnStart: Boolean
) = startNode(defaultParameters, providedName, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, additionalCordapps, regenerateCordappsOnStart, null)
override fun startNode(
defaultParameters: NodeParameters,
providedName: CordaX500Name?,
rpcUsers: List<User>,
verifierType: VerifierType,
customOverrides: Map<String, Any?>,
startInSameProcess: Boolean?,
maximumHeapSize: String,
additionalCordapps: Set<TestCorDapp>,
regenerateCordappsOnStart: Boolean,
bytemanPort: Int?
): CordaFuture<NodeHandle> {
val p2pAddress = portAllocation.nextHostAndPort()
@ -251,7 +246,7 @@ class DriverDSLImpl(
return registrationFuture.flatMap {
networkMapAvailability.flatMap {
// But starting the node proper does require the network map
startRegisteredNode(name, it, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, p2pAddress, bytemanPort)
startRegisteredNode(name, it, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, p2pAddress, additionalCordapps, regenerateCordappsOnStart, bytemanPort)
}
}
}
@ -264,6 +259,8 @@ class DriverDSLImpl(
startInSameProcess: Boolean? = null,
maximumHeapSize: String = "512m",
p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(),
additionalCordapps: Set<TestCorDapp> = emptySet(),
regenerateCordappsOnStart: Boolean = false,
bytemanPort: Int? = null): CordaFuture<NodeHandle> {
val rpcAddress = portAllocation.nextHostAndPort()
val rpcAdminAddress = portAllocation.nextHostAndPort()
@ -293,7 +290,7 @@ class DriverDSLImpl(
allowMissingConfig = true,
configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true)
)).checkAndOverrideForInMemoryDB()
return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize, localNetworkMap, bytemanPort)
return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize, localNetworkMap, additionalCordapps, regenerateCordappsOnStart, bytemanPort)
}
private fun startNodeRegistration(providedName: CordaX500Name, rootCert: X509Certificate, compatibilityZoneURL: URL): CordaFuture<NodeConfig> {
@ -347,6 +344,7 @@ class DriverDSLImpl(
NON_VALIDATING_BFT(false, CordaX500Name("BFT", "Zurich", "CH"))
}
// TODO remove this
internal fun startCordformNodes(cordforms: List<CordformNode>): CordaFuture<*> {
check(notarySpecs.isEmpty()) { "Specify notaries in the CordformDefinition" }
check(compatibilityZone == null) { "Cordform nodes cannot be run with compatibilityZoneURL" }
@ -404,6 +402,7 @@ class DriverDSLImpl(
}.transpose()
}
// TODO remove this
private fun startCordformNode(cordform: CordformNode, localNetworkMap: LocalNetworkMap): CordaFuture<NodeHandle> {
val name = CordaX500Name.parse(cordform.name)
// TODO We shouldn't have to allocate an RPC or web address if they're not specified. We're having to do this because of startNodeInternal
@ -432,9 +431,8 @@ class DriverDSLImpl(
allowMissingConfig = true,
configOverrides = rawConfig.toNodeOnly()
)
val cordaConfig = typesafe.parseAsNodeConfiguration()
val config = NodeConfig(rawConfig, cordaConfig).checkAndOverrideForInMemoryDB()
return startNodeInternal(config, webAddress, null, "512m", localNetworkMap, null)
val config = NodeConfig(typesafe).checkAndOverrideForInMemoryDB()
return startNodeInternal(config, webAddress, null, "512m", localNetworkMap, emptySet())
}
@Suppress("DEPRECATION")
@ -656,7 +654,6 @@ class DriverDSLImpl(
bytemanJarPath,
null,
systemProperties,
cordappPackages,
"512m",
*extraCmdLineFlag
)
@ -666,14 +663,20 @@ class DriverDSLImpl(
}
}
private fun startNodeInternal(config: NodeConfig,
private val sharedCordappsDirectories: Iterable<Path> by lazy {
TestCordappDirectories.cached(cordappsForAllNodes)
}
private fun startNodeInternal(specifiedConfig: NodeConfig,
webAddress: NetworkHostAndPort,
startInProcess: Boolean?,
maximumHeapSize: String,
localNetworkMap: LocalNetworkMap?,
bytemanPort: Int?): CordaFuture<NodeHandle> {
val visibilityHandle = networkVisibilityController.register(config.corda.myLegalName)
val baseDirectory = config.corda.baseDirectory.createDirectories()
additionalCordapps: Set<TestCorDapp>,
regenerateCordappsOnStart: Boolean = false,
bytemanPort: Int? = null): CordaFuture<NodeHandle> {
val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName)
val baseDirectory = specifiedConfig.corda.baseDirectory.createDirectories()
localNetworkMap?.networkParametersCopier?.install(baseDirectory)
localNetworkMap?.nodeInfosCopier?.addConfig(baseDirectory)
@ -682,10 +685,16 @@ class DriverDSLImpl(
visibilityHandle.close()
}
val useHTTPS = config.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") }
val useHTTPS = specifiedConfig.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") }
val existingCorDappDirectoriesOption = if (regenerateCordappsOnStart) emptyList<String>() else if (specifiedConfig.typesafe.hasPath(NodeConfiguration.cordappDirectoriesKey)) specifiedConfig.typesafe.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList()
val cordappDirectories = existingCorDappDirectoriesOption + sharedCordappsDirectories.map { it.toString() } + TestCordappDirectories.cached(additionalCordapps, regenerateCordappsOnStart).map { it.toString() }
val config = NodeConfig(specifiedConfig.typesafe.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories)))
if (startInProcess ?: startNodesInProcess) {
val nodeAndThreadFuture = startInProcessNode(executorService, config, cordappPackages)
val nodeAndThreadFuture = startInProcessNode(executorService, config)
shutdownManager.registerShutdown(
nodeAndThreadFuture.map { (node, thread) ->
{
@ -712,7 +721,7 @@ class DriverDSLImpl(
} else {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val monitorPort = if (jmxPolicy.startJmxHttpServer) jmxPolicy.jmxHttpServerPortAllocation?.nextPort() else null
val process = startOutOfProcessNode(config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, bytemanJarPath, bytemanPort, systemProperties, cordappPackages, maximumHeapSize)
val process = startOutOfProcessNode(config, quasarJarPath, debugPort, jolokiaJarPath, monitorPort, bytemanJarPath, bytemanPort, systemProperties, maximumHeapSize)
// Destroy the child process when the parent exits.This is needed even when `waitForAllNodesToFinish` is
// true because we don't want orphaned processes in the case that the parent process is terminated by the
@ -806,10 +815,12 @@ class DriverDSLImpl(
private fun <A> oneOf(array: Array<A>) = array[Random().nextInt(array.size)]
fun cordappsInCurrentAndAdditionalPackages(packagesToScan: Iterable<String> = emptySet()): Set<TestCorDapp> = cordappsForPackages(getCallerPackage() + packagesToScan)
fun cordappsInCurrentAndAdditionalPackages(firstPackage: String, vararg otherPackages: String): Set<TestCorDapp> = cordappsInCurrentAndAdditionalPackages(otherPackages.toList() + firstPackage)
private fun startInProcessNode(
executorService: ScheduledExecutorService,
config: NodeConfig,
cordappPackages: List<String>
config: NodeConfig
): CordaFuture<Pair<StartedNode<Node>, Thread>> {
return executorService.fork {
log.info("Starting in-process Node ${config.corda.myLegalName.organisation}")
@ -819,7 +830,7 @@ class DriverDSLImpl(
// Write node.conf
writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe.toNodeOnly())
// TODO pass the version in?
val node = InProcessNode(config.corda, MOCK_VERSION_INFO, cordappPackages).start()
val node = InProcessNode(config.corda, MOCK_VERSION_INFO).start()
val nodeThread = thread(name = config.corda.myLegalName.organisation) {
node.internals.run()
}
@ -838,10 +849,10 @@ class DriverDSLImpl(
bytemanJarPath: String?,
bytemanPort: Int?,
overriddenSystemProperties: Map<String, String>,
cordappPackages: List<String>,
maximumHeapSize: String,
vararg extraCmdLineFlag: String
): Process {
log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " +
"debug port is " + (debugPort ?: "not enabled") + ", " +
"jolokia monitoring port is " + (monitorPort ?: "not enabled") + ", " +
@ -856,11 +867,6 @@ class DriverDSLImpl(
)
systemProperties += inheritFromParentProcess()
if (cordappPackages.isNotEmpty()) {
systemProperties += Node.scanPackagesSystemProperty to cordappPackages.joinToString(Node.scanPackagesSeparator)
}
systemProperties += overriddenSystemProperties
// See experimental/quasar-hook/README.md for how to generate.
@ -1075,6 +1081,8 @@ interface InternalDriverDSL : DriverDSL, CordformContext {
customOverrides: Map<String, Any?> = defaultParameters.customOverrides,
startInSameProcess: Boolean? = defaultParameters.startInSameProcess,
maximumHeapSize: String = defaultParameters.maximumHeapSize,
additionalCordapps: Set<TestCorDapp> = defaultParameters.additionalCordapps,
regenerateCordappsOnStart: Boolean = defaultParameters.regenerateCordappsOnStart,
bytemanPort: Int? = null
): CordaFuture<NodeHandle>
}
@ -1132,13 +1140,13 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
isDebug = defaultParameters.isDebug,
startNodesInProcess = defaultParameters.startNodesInProcess,
waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish,
extraCordappPackagesToScan = defaultParameters.extraCordappPackagesToScan,
jmxPolicy = defaultParameters.jmxPolicy,
notarySpecs = defaultParameters.notarySpecs,
compatibilityZone = null,
networkParameters = defaultParameters.networkParameters,
notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
inMemoryDB = defaultParameters.inMemoryDB
inMemoryDB = defaultParameters.inMemoryDB,
cordappsForAllNodes = defaultParameters.cordappsForAllNodes()
)
)
val shutdownHook = addShutdownHook(driverDsl::shutdown)
@ -1212,12 +1220,12 @@ fun <A> internalDriver(
startNodesInProcess: Boolean = DriverParameters().startNodesInProcess,
waitForAllNodesToFinish: Boolean = DriverParameters().waitForAllNodesToFinish,
notarySpecs: List<NotarySpec> = DriverParameters().notarySpecs,
extraCordappPackagesToScan: List<String> = DriverParameters().extraCordappPackagesToScan,
jmxPolicy: JmxPolicy = DriverParameters().jmxPolicy,
networkParameters: NetworkParameters = DriverParameters().networkParameters,
compatibilityZone: CompatibilityZoneParams? = null,
notaryCustomOverrides: Map<String, Any?> = DriverParameters().notaryCustomOverrides,
inMemoryDB: Boolean = DriverParameters().inMemoryDB,
cordappsForAllNodes: Set<TestCorDapp> = DriverParameters().cordappsForAllNodes(),
dsl: DriverDSLImpl.() -> A
): A {
return genericDriver(
@ -1231,12 +1239,12 @@ fun <A> internalDriver(
startNodesInProcess = startNodesInProcess,
waitForAllNodesToFinish = waitForAllNodesToFinish,
notarySpecs = notarySpecs,
extraCordappPackagesToScan = extraCordappPackagesToScan,
jmxPolicy = jmxPolicy,
compatibilityZone = compatibilityZone,
networkParameters = networkParameters,
notaryCustomOverrides = notaryCustomOverrides,
inMemoryDB = inMemoryDB
inMemoryDB = inMemoryDB,
cordappsForAllNodes = cordappsForAllNodes
),
coerce = { it },
dsl = dsl,
@ -1257,6 +1265,8 @@ private fun Config.toNodeOnly(): Config {
return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this
}
internal fun DriverParameters.cordappsForAllNodes(): Set<TestCorDapp> = cordappsForAllNodes ?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan)
fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture<NodeHandle> {
var customOverrides = emptyMap<String, String>()
if (!devMode) {

View File

@ -22,10 +22,7 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.createDirectories
import net.corda.core.internal.createDirectory
import net.corda.core.internal.uncheckedCast
import net.corda.core.internal.*
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.RPCOps
import net.corda.core.messaging.SingleMessageRecipient
@ -35,14 +32,12 @@ import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.hours
import net.corda.core.utilities.seconds
import net.corda.core.utilities.*
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.AbstractNode
import net.corda.node.internal.StartedNode
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.services.api.NodePropertiesStore
import net.corda.node.services.api.SchemaService
import net.corda.node.services.config.*
@ -58,16 +53,17 @@ import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.setGlobalSerialization
import net.corda.testing.internal.testThreadFactory
import net.corda.testing.node.*
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.apache.activemq.artemis.utils.ReusableLatch
import org.apache.sshd.common.util.security.SecurityUtils
import rx.internal.schedulers.CachedThreadScheduler
import java.math.BigInteger
import java.nio.file.Path
import java.nio.file.Paths
import java.security.KeyPair
import java.security.PublicKey
import java.time.Clock
@ -85,8 +81,7 @@ data class MockNodeArgs(
val network: InternalMockNetwork,
val id: Int,
val entropyRoot: BigInteger,
val version: VersionInfo = MOCK_VERSION_INFO,
val extraCordappPackages: List<String> = emptyList()
val version: VersionInfo = MOCK_VERSION_INFO
)
data class InternalMockNodeParameters(
@ -95,25 +90,26 @@ data class InternalMockNodeParameters(
val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
val configOverrides: (NodeConfiguration) -> Any? = {},
val version: VersionInfo = MOCK_VERSION_INFO,
val extraCordappPackages: List<String> = emptyList()) {
val additionalCordapps: Set<TestCorDapp>? = 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<String> = 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<MockNetworkNotarySpec> = 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<TestCorDapp> = emptySet()) {
init {
// Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS.
// This SFTP support loads BouncyCastle, which we want to avoid.
@ -122,6 +118,10 @@ open class InternalMockNetwork(private val cordappPackages: List<String> = empty
require(networkParameters.notaries.isEmpty()) { "Define notaries using notarySpecs" }
}
private companion object {
private val logger = loggerFor<InternalMockNetwork>()
}
var nextNodeId = 0
private set
private val filesystem = Jimfs.newFileSystem(unix())
@ -138,6 +138,10 @@ open class InternalMockNetwork(private val cordappPackages: List<String> = empty
}
private val sharedUserCount = AtomicInteger(0)
private val sharedCorDappsDirectories: Iterable<Path> by lazy {
TestCordappDirectories.cached(cordappsForAllNodes)
}
/** A read only view of the current set of nodes. */
val nodes: List<MockNode> get() = _nodes
@ -230,12 +234,11 @@ open class InternalMockNetwork(private val cordappPackages: List<String> = empty
}
}
open class MockNode(args: MockNodeArgs) : AbstractNode(
open class MockNode(args: MockNodeArgs, cordappLoader: CordappLoader = JarScanningCordappLoader.fromDirectories(args.config.cordappDirectories)) : AbstractNode(
args.config,
TestClock(Clock.systemUTC()),
args.version,
// Add the specified additional CorDapps.
CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages + args.extraCordappPackages),
cordappLoader,
args.network.busyLatch
) {
companion object {
@ -365,7 +368,7 @@ open class InternalMockNetwork(private val cordappPackages: List<String> = empty
return createUnstartedNode(parameters, defaultFactory)
}
fun <N : MockNode> createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): N {
fun <N : MockNode> createUnstartedNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs, CordappLoader?) -> N): N {
return createNodeImpl(parameters, nodeFactory, false)
}
@ -374,11 +377,11 @@ open class InternalMockNetwork(private val cordappPackages: List<String> = empty
}
/** Like the other [createNode] but takes a [nodeFactory] and propagates its [MockNode] subtype. */
fun <N : MockNode> createNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs) -> N): StartedNode<N> {
fun <N : MockNode> createNode(parameters: InternalMockNodeParameters = InternalMockNodeParameters(), nodeFactory: (MockNodeArgs, CordappLoader?) -> N): StartedNode<N> {
return uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!!
}
private fun <N : MockNode> createNodeImpl(parameters: InternalMockNodeParameters, nodeFactory: (MockNodeArgs) -> N, start: Boolean): N {
private fun <N : MockNode> createNodeImpl(parameters: InternalMockNodeParameters, nodeFactory: (MockNodeArgs, CordappLoader?) -> N, start: Boolean): N {
val id = parameters.forcedID ?: nextNodeId++
val config = mockNodeConfiguration().also {
doReturn(baseDirectory(id).createDirectories()).whenever(it).baseDirectory
@ -388,7 +391,12 @@ open class InternalMockNetwork(private val cordappPackages: List<String> = empty
doReturn(emptyList<SecureHash>()).whenever(it).extraNetworkMapKeys
parameters.configOverrides(it)
}
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version, parameters.extraCordappPackages))
val cordapps: Set<TestCorDapp> = parameters.additionalCordapps ?: emptySet()
val cordappDirectories = sharedCorDappsDirectories + TestCordappDirectories.cached(cordapps)
doReturn(cordappDirectories).whenever(config).cordappDirectories
val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version), JarScanningCordappLoader.fromDirectories(cordappDirectories))
_nodes += node
if (start) {
node.start()
@ -396,7 +404,7 @@ open class InternalMockNetwork(private val cordappPackages: List<String> = empty
return node
}
fun <N : MockNode> restartNode(node: StartedNode<N>, nodeFactory: (MockNodeArgs) -> N): StartedNode<N> {
fun <N : MockNode> restartNode(node: StartedNode<N>, nodeFactory: (MockNodeArgs, CordappLoader?) -> N): StartedNode<N> {
node.internals.disableDBCloseOnStop()
node.dispose()
return createNode(
@ -407,7 +415,7 @@ open class InternalMockNetwork(private val cordappPackages: List<String> = empty
fun restartNode(node: StartedNode<MockNode>): StartedNode<MockNode> = restartNode(node, defaultFactory)
fun baseDirectory(nodeId: Int): Path = filesystem.getPath("/nodes/$nodeId")
fun baseDirectory(nodeId: Int): Path = testDirectory / "nodes/$nodeId"
/**
* Asks every node in order to process any queued up inbound messages. This may in turn result in nodes
@ -465,7 +473,6 @@ open class InternalMockNetwork(private val cordappPackages: List<String> = empty
fun waitQuiescent() {
busyLatch.await()
}
}
open class MessagingServiceSpy(val messagingService: MessagingService) : MessagingService by messagingService

View File

@ -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<JarEntryInfo>) : TestCorDapp.Mutable {
constructor(name: String, version: String, vendor: String, title: String, classes: Set<Class<*>>, 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<Class<*>>): Set<JarEntryInfo> {
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<Class<*>> = jarEntries.filterIsInstance(JarEntryInfo.ClassJarEntryInfo::class.java).map(JarEntryInfo.ClassJarEntryInfo::clazz).toSet()
override val resources: Set<URL> = 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<Class<*>>) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp)
override fun plusPackages(pckgs: Set<String>) = withClasses(pckgs.map { allClassesForPackage(it) }.fold(classes) { all, packageClasses -> all + packageClasses })
override fun minusPackages(pckgs: Set<String>) = 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)
}

View File

@ -10,6 +10,7 @@
package net.corda.testing.node.internal
import com.typesafe.config.ConfigValueFactory
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.transpose
@ -17,11 +18,11 @@ import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.node.VersionInfo
import net.corda.node.internal.EnterpriseNode
import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.services.config.*
import net.corda.nodeapi.internal.config.toConfig
import net.corda.nodeapi.internal.network.NetworkParametersCopier
@ -41,10 +42,12 @@ import java.nio.file.Path
import java.util.concurrent.Executors
import kotlin.concurrent.thread
// TODO Some of the logic here duplicates what's in the driver
// TODO Some of the logic here duplicates what's in the driver - the reason why it's not straightforward to replace it by using DriverDSLImpl in `init()` and `stopAllNodes()` is because of the platform version passed to nodes (driver doesn't support this, and it's a property of the Corda JAR)
abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyList()) : IntegrationTest() {
companion object {
private val WHITESPACE = "\\s++".toRegex()
private val logger = loggerFor<NodeBasedTest>()
}
@Rule
@ -112,15 +115,24 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
) + configOverrides
)
val parsedConfig = config.parseAsNodeConfiguration().also { nodeConfiguration ->
val cordapps = cordappsForPackages(getCallerPackage(NodeBasedTest::class)?.let { cordappPackages + it } ?: cordappPackages)
val existingCorDappDirectoriesOption = if (config.hasPath(NodeConfiguration.cordappDirectoriesKey)) config.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList()
val cordappDirectories = existingCorDappDirectoriesOption + TestCordappDirectories.cached(cordapps).map { it.toString() }
val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories))
val parsedConfig = specificConfig.parseAsNodeConfiguration().also { nodeConfiguration ->
val errors = nodeConfiguration.validate()
if (errors.isNotEmpty()) {
throw IllegalStateException("Invalid node configuration. Errors where:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}")
}
}
defaultNetworkParameters.install(baseDirectory)
return InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), cordappPackages)
return InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion))
}
@JvmOverloads
@ -153,8 +165,7 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
}
}
class InProcessNode(
configuration: NodeConfiguration, versionInfo: VersionInfo, cordappPackages: List<String>) : EnterpriseNode(
configuration, versionInfo, false, CordappLoader.createDefaultWithTestPackages(configuration, cordappPackages)) {
class InProcessNode(configuration: NodeConfiguration, versionInfo: VersionInfo) : EnterpriseNode(configuration, versionInfo, false) {
override fun getRxIoScheduler() = CachedThreadScheduler(testThreadFactory()).also { runOnStop += it::shutdown }
}

View File

@ -37,8 +37,10 @@ import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.MAX_MESSAGE_SIZE
import net.corda.testing.driver.JmxPolicy
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.TransportConfiguration
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
@ -119,13 +121,13 @@ fun <A> rpcDriver(
useTestClock: Boolean = false,
startNodesInProcess: Boolean = false,
waitForNodesToFinish: Boolean = false,
extraCordappPackagesToScan: List<String> = emptyList(),
notarySpecs: List<NotarySpec> = emptyList(),
externalTrace: Trace? = null,
jmxPolicy: JmxPolicy = JmxPolicy(),
networkParameters: NetworkParameters = testNetworkParameters(notaries = emptyList()),
notaryCustomOverrides: Map<String, Any?> = emptyMap(),
inMemoryDB: Boolean = true,
cordappsForAllNodes: Set<TestCorDapp> = cordappsInCurrentAndAdditionalPackages(),
dsl: RPCDriverDSL.() -> A
): A {
return genericDriver(
@ -139,13 +141,13 @@ fun <A> 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 },

View File

@ -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<TestCordappDirectories>()
private const val whitespace = " "
private const val whitespaceReplacement = "_"
private val cordappsCache: ConcurrentMap<List<String>, Path> = ConcurrentHashMap<List<String>, Path>()
internal fun cached(cordapps: Iterable<TestCorDapp>, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Iterable<Path> {
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<String>, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Iterable<Path> {
cordappsDirectory.toFile().deleteOnExit()
val cordapps = simplifyScanPackages(packages).distinct().fold(emptySet<TestCorDapp>()) { 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
}
}

View File

@ -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<JarEntryInfo>.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<JarEntryInfo>.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<TestCorDapp>.packageInDirectory(directory: Path) {
directory.createDirectories()
forEach { cordapp -> cordapp.packageAsJarInDirectory(directory) }
}
/**
* Returns all classes within the [targetPackage].
*/
fun allClassesForPackage(targetPackage: String): Set<Class<*>> {
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<String>): Set<TestCorDapp> {
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<TestCorDapp> {
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<String>): List<String> {
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<JarEntryInfo>.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<JarEntryInfo>) {
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)
}
}

View File

@ -17,6 +17,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.getOrThrow
import net.corda.testing.driver.JmxPolicy
import net.corda.testing.driver.PortAllocation
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
import net.corda.testing.node.internal.internalDriver
/**
@ -69,12 +70,12 @@ class CordformNodeRunner(private val cordformDefinition: CordformDefinition) {
internalDriver(
jmxPolicy = JmxPolicy(true),
driverDirectory = cordformDefinition.nodesDirectory,
extraCordappPackagesToScan = extraPackagesToScan,
// Notaries are manually specified in Cordform so we don't want the driver automatically starting any
notarySpecs = emptyList(),
// Start from after the largest port used to prevent port clash
portAllocation = PortAllocation.Incremental(maxPort + 1),
waitForAllNodesToFinish = waitForAllNodesToFinish
waitForAllNodesToFinish = waitForAllNodesToFinish,
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages(extraPackagesToScan)
) {
cordformDefinition.setup(this)
startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running

View File

@ -19,7 +19,7 @@ class InternalMockNetworkTests {
fun `does not leak serialization env if init fails`() {
val e = Exception("didn't work")
assertThatThrownBy {
object : InternalMockNetwork() {
object : InternalMockNetwork(cordappsForAllNodes = emptySet()) {
override fun createNotaries() = throw e
}
}.isSameAs(e)

View File

@ -17,7 +17,8 @@ import net.corda.core.internal.TEST_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.testing.services.MockAttachmentStorage
import java.nio.file.Paths
@ -52,8 +53,7 @@ class MockCordappProvider(
}
}
override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? = cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second
?: super.getContractAttachmentID(contractClassName)
override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? = cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second ?: super.getContractAttachmentID(contractClassName)
private fun findOrImportAttachment(contractClassNames: List<ContractClassName>, data: ByteArray, attachments: MockAttachmentStorage): AttachmentId {
val existingAttachment = attachments.files.filter {

View File

@ -23,7 +23,7 @@ import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.schemas.MappedSchema
import net.corda.node.internal.DataSourceFactory.createDatasourceFromDriverJarFolders
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.configOf
import net.corda.node.services.config.parseAsNodeConfiguration
@ -133,7 +133,7 @@ private fun runCommand(options: OptionSet, parser: OptionParser) {
errorAndExit("Not a valid node folder. Could not find the config file: '$config'.")
}
val nodeConfig = ConfigHelper.loadConfig(baseDirectory, config).parseAsNodeConfiguration()
val cordappLoader = CordappLoader.createDefault(baseDirectory)
val cordappLoader = JarScanningCordappLoader.fromDirectories(setOf(baseDirectory))
val schemaService = NodeSchemaService(extraSchemas = cordappLoader.cordappSchemas, includeNotarySchemas = nodeConfig.notary != null)