mirror of
https://github.com/corda/corda.git
synced 2025-06-16 22:28:15 +00:00
[CORDA-1799]: Avoid generating test CorDapp JARs from each out of process node started by the driver (#3641)
This commit is contained in:
committed by
GitHub
parent
e7f3847839
commit
abc1d99eaa
@ -17,11 +17,12 @@ import net.corda.node.services.Permissions.Companion.startFlow
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.InProcess
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.internalServices
|
||||
import net.corda.testing.internal.performance.div
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
|
||||
import net.corda.testing.node.internal.internalDriver
|
||||
import net.corda.testing.node.internal.performance.startPublishingFixedRateInjector
|
||||
import net.corda.testing.node.internal.performance.startReporter
|
||||
@ -96,7 +97,7 @@ class NodePerformanceTests {
|
||||
internalDriver(
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = listOf(user))),
|
||||
startNodesInProcess = true,
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance")
|
||||
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance")
|
||||
) {
|
||||
val notary = defaultNotaryNode.getOrThrow() as InProcess
|
||||
val metricRegistry = startReporter(this.shutdownManager, notary.internalServices.monitoringService.metrics)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -14,9 +14,6 @@ import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.toLedgerTransaction
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
@ -26,7 +23,7 @@ import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
@ -40,6 +37,7 @@ import net.corda.testing.driver.driver
|
||||
import net.corda.testing.internal.MockCordappConfigProvider
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.internal.withoutTestSerialization
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
@ -52,7 +50,7 @@ class AttachmentLoadingTests {
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val attachments = MockAttachmentStorage()
|
||||
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments, testNetworkParameters().whitelistedContractImplementations)
|
||||
private val provider = CordappProviderImpl(JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments, testNetworkParameters().whitelistedContractImplementations)
|
||||
private val cordapp get() = provider.cordapps.first()
|
||||
private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!!
|
||||
private val appContext get() = provider.getAppContext(cordapp)
|
||||
@ -75,13 +73,6 @@ class AttachmentLoadingTests {
|
||||
startNode(providedName = bankBName)
|
||||
).transpose().getOrThrow()
|
||||
}
|
||||
|
||||
private fun DriverDSL.installIsolatedCordappTo(nodeName: CordaX500Name) {
|
||||
// Copy the app jar to the first node. The second won't have it.
|
||||
val path = (baseDirectory(nodeName) / "cordapps").createDirectories() / "isolated.jar"
|
||||
logger.info("Installing isolated jar to $path")
|
||||
isolatedJAR.openStream().use { it.copyTo(path) }
|
||||
}
|
||||
}
|
||||
|
||||
private val services = object : ServicesForResolution {
|
||||
@ -114,9 +105,9 @@ class AttachmentLoadingTests {
|
||||
@Test
|
||||
fun `test that attachments retrieved over the network are not used for code`() {
|
||||
withoutTestSerialization {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
installIsolatedCordappTo(bankAName)
|
||||
val (bankA, bankB) = createTwoNodes()
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList(), cordappsForAllNodes = emptySet())) {
|
||||
val bankA = startNode(providedName = bankAName, additionalCordapps = cordappsForPackages("net.corda.finance.contracts.isolated")).getOrThrow()
|
||||
val bankB = startNode(providedName = bankBName, additionalCordapps = cordappsForPackages("net.corda.finance.contracts.isolated")).getOrThrow()
|
||||
assertFailsWith<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()
|
||||
}
|
||||
@ -124,16 +115,4 @@ class AttachmentLoadingTests {
|
||||
Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tests that if the attachment is loaded on both sides already that a flow can run`() {
|
||||
withoutTestSerialization {
|
||||
driver {
|
||||
installIsolatedCordappTo(bankAName)
|
||||
installIsolatedCordappTo(bankBName)
|
||||
val (bankA, bankB) = createTwoNodes()
|
||||
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,11 +6,7 @@ import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.isFulfilledBy
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.NotaryError
|
||||
import net.corda.core.flows.NotaryException
|
||||
import net.corda.core.flows.NotaryFlow
|
||||
@ -37,6 +33,7 @@ import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.core.dummyCommand
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.TestClock
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
@ -50,18 +47,8 @@ import java.nio.file.Paths
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.ExecutionException
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.distinct
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.last
|
||||
import kotlin.collections.listOf
|
||||
import kotlin.collections.map
|
||||
import kotlin.collections.mapIndexedNotNull
|
||||
import kotlin.collections.plus
|
||||
import kotlin.collections.single
|
||||
import kotlin.collections.zip
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
@ -75,7 +62,7 @@ class BFTNotaryServiceTests {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() {
|
||||
mockNet = InternalMockNetwork(listOf("net.corda.testing.contracts"))
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"))
|
||||
val clusterSize = minClusterSize(1)
|
||||
val started = startBftClusterAndNode(clusterSize, mockNet)
|
||||
notary = started.first
|
||||
|
@ -12,6 +12,7 @@ import net.corda.node.services.transactions.minClusterSize
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.core.dummyCommand
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
@ -24,7 +25,7 @@ class BFTSMaRtTests {
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
mockNet = InternalMockNetwork(listOf("net.corda.testing.contracts"))
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"))
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -20,6 +20,7 @@ import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.internal.DEV_ROOT_CA
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages
|
||||
import net.corda.testing.node.internal.SharedCompatibilityZoneParams
|
||||
import net.corda.testing.node.internal.internalDriver
|
||||
import net.corda.testing.node.internal.network.NetworkMapServer
|
||||
@ -87,7 +88,7 @@ class NodeRegistrationTest {
|
||||
compatibilityZone = compatibilityZone,
|
||||
initialiseSerialization = false,
|
||||
notarySpecs = listOf(NotarySpec(notaryName)),
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance"),
|
||||
cordappsForAllNodes = cordappsInCurrentAndAdditionalPackages("net.corda.finance"),
|
||||
notaryCustomOverrides = mapOf("devMode" to false)
|
||||
) {
|
||||
val (alice, genevieve) = listOf(
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
32
node/src/main/kotlin/net/corda/node/cordapp/CordappLoader.kt
Normal file
32
node/src/main/kotlin/net/corda/node/cordapp/CordappLoader.kt
Normal 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>
|
||||
}
|
@ -59,10 +59,11 @@ import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.node.CordaClock
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.cordapp.CordappLoader
|
||||
import net.corda.node.internal.CheckpointVerifier.verifyCheckpointsCompatible
|
||||
import net.corda.node.internal.classloading.requireAnnotation
|
||||
import net.corda.node.internal.cordapp.CordappConfigFileProvider
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||
import net.corda.node.internal.cordapp.CordappProviderInternal
|
||||
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy
|
||||
|
@ -25,8 +25,9 @@ import net.corda.node.SimpleClock
|
||||
import net.corda.node.VersionInfo
|
||||
import net.corda.node.internal.artemis.ArtemisBroker
|
||||
import net.corda.node.internal.artemis.BrokerAddresses
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.internal.cordapp.JarScanningCordappLoader
|
||||
import net.corda.core.internal.errors.AddressBindingException
|
||||
import net.corda.node.cordapp.CordappLoader
|
||||
import net.corda.node.internal.security.RPCSecurityManagerImpl
|
||||
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
|
||||
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||
@ -114,12 +115,8 @@ open class Node(configuration: NodeConfiguration,
|
||||
}
|
||||
|
||||
private val sameVmNodeCounter = AtomicInteger()
|
||||
const val scanPackagesSystemProperty = "net.corda.node.cordapp.scan.packages"
|
||||
const val scanPackagesSeparator = ","
|
||||
private fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader {
|
||||
return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages ->
|
||||
CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator))
|
||||
} ?: CordappLoader.createDefault(configuration.baseDirectory)
|
||||
return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories)
|
||||
}
|
||||
// TODO: make this configurable.
|
||||
const val MAX_RPC_MESSAGE_SIZE = 10485760
|
||||
|
@ -13,6 +13,7 @@ import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.cordapp.CordappLoader
|
||||
import java.net.URL
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.node.internal.cordapp
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
|
||||
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
@ -15,23 +14,16 @@ import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.node.cordapp.CordappLoader
|
||||
import net.corda.node.internal.classloading.requireAnnotation
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.nodeapi.internal.coreContractClasses
|
||||
import net.corda.serialization.internal.DefaultWhitelist
|
||||
import org.apache.commons.collections4.map.LRUMap
|
||||
import java.lang.reflect.Modifier
|
||||
import java.net.JarURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.attribute.FileTime
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.streams.toList
|
||||
|
||||
@ -40,20 +32,11 @@ import kotlin.streams.toList
|
||||
*
|
||||
* @property cordappJarPaths The classpath of cordapp JARs
|
||||
*/
|
||||
class CordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>) {
|
||||
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>) : 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()) {
|
||||
@ -63,126 +46,37 @@ 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()
|
||||
/**
|
||||
* Default cordapp dir name
|
||||
*/
|
||||
private const val CORDAPPS_DIR_NAME = "cordapps"
|
||||
|
||||
/**
|
||||
* Creates a default CordappLoader intended to be used in non-dev or non-test environments.
|
||||
* Creates a CordappLoader from multiple directories.
|
||||
*
|
||||
* @param baseDir The directory that this node is running in. Will use this to resolve the cordapps directory
|
||||
* for classpath scanning.
|
||||
* @param corDappDirectories Directories used to scan for CorDapp JARs.
|
||||
*/
|
||||
fun createDefault(baseDir: Path) = CordappLoader(getNodeCordappURLs(baseDir))
|
||||
fun fromDirectories(corDappDirectories: Iterable<Path>): CordappLoader {
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}")
|
||||
return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() })
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dev mode CordappLoader for test environments that creates and loads cordapps from the classpath
|
||||
* and cordapps directory. This is intended mostly for use by the driver.
|
||||
* Creates a CordappLoader loader out of a list of JAR URLs.
|
||||
*
|
||||
* @param testPackages See [createWithTestPackages]
|
||||
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List<String>): CordappLoader {
|
||||
if (!configuration.devMode) {
|
||||
logger.warn("Package scanning should only occur in dev mode!")
|
||||
}
|
||||
val urls = getNodeCordappURLs(configuration.baseDirectory) + simplifyScanPackages(testPackages).flatMap(this::getPackageURLs)
|
||||
return cordappLoadersCache.asMap().computeIfAbsent(urls, ::CordappLoader)
|
||||
}
|
||||
fun fromJarUrls(scanJars: List<URL>) = JarScanningCordappLoader(scanJars.map { it.restricted() })
|
||||
|
||||
/**
|
||||
* Create a dev mode CordappLoader for test environments that creates and loads cordapps from the classpath.
|
||||
* This is intended for use in unit and integration tests.
|
||||
*
|
||||
* @param testPackages List of package names that contain CorDapp classes that can be automatically turned into
|
||||
* CorDapps.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun createWithTestPackages(testPackages: List<String>): CordappLoader {
|
||||
val urls = simplifyScanPackages(testPackages).flatMap(this::getPackageURLs)
|
||||
return cordappLoadersCache.asMap().computeIfAbsent(urls, ::CordappLoader)
|
||||
}
|
||||
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
|
||||
|
||||
/**
|
||||
* Creates a dev mode CordappLoader intended only to be used in test environments
|
||||
*
|
||||
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun createDevMode(scanJars: List<URL>) = CordappLoader(scanJars.map { RestrictedURL(it, null) })
|
||||
private fun jarUrlsInDirectory(directory: Path): List<URL> {
|
||||
|
||||
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 cordappJar = cordappDir / "$scanPackage-${UUID.randomUUID()}.jar"
|
||||
logger.info("Generating a test-only CorDapp of classes discovered for package $scanPackage in $url: $cordappJar")
|
||||
JarOutputStream(cordappJar.outputStream()).use { jos ->
|
||||
val scanDir = url.toPath()
|
||||
scanDir.walk {
|
||||
it.forEach {
|
||||
val entryPath = "$resource/${scanDir.relativize(it).toString().replace('\\', '/')}"
|
||||
val time = FileTime.from(Instant.EPOCH)
|
||||
val entry = ZipEntry(entryPath).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
|
||||
jos.putNextEntry(entry)
|
||||
if (it.isRegularFile()) {
|
||||
it.copyTo(jos)
|
||||
}
|
||||
jos.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
cordappJar
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNodeCordappURLs(baseDir: Path): List<RestrictedURL> {
|
||||
val cordappsDir = baseDir / CORDAPPS_DIR_NAME
|
||||
return if (!cordappsDir.exists()) {
|
||||
return if (!directory.exists()) {
|
||||
emptyList()
|
||||
} else {
|
||||
cordappsDir.list {
|
||||
it.filter { it.toString().endsWith(".jar") }.map { RestrictedURL(it.toUri().toURL(), null) }.toList()
|
||||
directory.list { paths ->
|
||||
// `toFile()` can't be used here...
|
||||
paths.filter { it.toString().endsWith(".jar") }.map { it.toUri().toURL() }.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,22 +106,25 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
||||
}
|
||||
|
||||
private fun loadCordapps(): List<Cordapp> {
|
||||
return cordappJarPaths.map {
|
||||
val scanResult = scanCordapp(it)
|
||||
CordappImpl(findContractClassNames(scanResult),
|
||||
findInitiatedFlows(scanResult),
|
||||
findRPCFlows(scanResult),
|
||||
findServiceFlows(scanResult),
|
||||
findSchedulableFlows(scanResult),
|
||||
findServices(scanResult),
|
||||
findPlugins(it),
|
||||
findSerializers(scanResult),
|
||||
findCustomSchemas(scanResult),
|
||||
findAllFlows(scanResult),
|
||||
it.url,
|
||||
getJarHash(it.url)
|
||||
)
|
||||
}
|
||||
return cordappJarPaths.map { scanCordapp(it).toCordapp(it) }
|
||||
}
|
||||
|
||||
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): Cordapp {
|
||||
|
||||
return CordappImpl(
|
||||
findContractClassNames(this),
|
||||
findInitiatedFlows(this),
|
||||
findRPCFlows(this),
|
||||
findServiceFlows(this),
|
||||
findSchedulableFlows(this),
|
||||
findServices(this),
|
||||
findPlugins(url),
|
||||
findSerializers(this),
|
||||
findCustomSchemas(this),
|
||||
findAllFlows(this),
|
||||
url.url,
|
||||
getJarHash(url.url)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getJarHash(url: URL): SecureHash.SHA256 = url.openStream().readFully().sha256()
|
||||
@ -290,11 +187,12 @@ class CordappLoader private constructor(private val cordappJarPaths: List<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<*>>> {
|
||||
@ -368,3 +266,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)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
package net.corda.node.internal.cordapp
|
||||
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.internal.cordapp.CordappImpl
|
||||
import java.util.*
|
||||
import java.util.jar.Attributes
|
||||
import java.util.jar.Manifest
|
||||
|
||||
fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest {
|
||||
|
||||
val manifest = Manifest()
|
||||
|
||||
// Mandatory manifest attribute. If not present, all other entries are silently skipped.
|
||||
manifest.mainAttributes[Attributes.Name.MANIFEST_VERSION] = "1.0"
|
||||
|
||||
manifest["Name"] = name
|
||||
|
||||
manifest["Specification-Title"] = title
|
||||
manifest["Specification-Version"] = version
|
||||
manifest["Specification-Vendor"] = vendor
|
||||
|
||||
manifest["Implementation-Title"] = title
|
||||
manifest["Implementation-Version"] = version
|
||||
manifest["Implementation-Vendor"] = vendor
|
||||
|
||||
return manifest
|
||||
}
|
||||
|
||||
operator fun Manifest.set(key: String, value: String) {
|
||||
|
||||
mainAttributes.putValue(key, value)
|
||||
}
|
||||
|
||||
internal fun Manifest?.toCordappInfo(defaultShortName: String): Cordapp.Info {
|
||||
|
||||
var unknown = CordappImpl.Info.UNKNOWN
|
||||
(this?.mainAttributes?.getValue("Name") ?: defaultShortName).let { shortName ->
|
||||
unknown = unknown.copy(shortName = shortName)
|
||||
}
|
||||
this?.mainAttributes?.getValue("Implementation-Vendor")?.let { vendor ->
|
||||
unknown = unknown.copy(vendor = vendor)
|
||||
}
|
||||
this?.mainAttributes?.getValue("Implementation-Version")?.let { version ->
|
||||
unknown = unknown.copy(version = version)
|
||||
}
|
||||
return unknown
|
||||
}
|
@ -5,6 +5,7 @@ import com.typesafe.config.ConfigException
|
||||
import net.corda.core.context.AuthServiceId
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.TimedFlow
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.seconds
|
||||
@ -27,6 +28,7 @@ val Int.MB: Long get() = this * 1024L * 1024L
|
||||
|
||||
private val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1)
|
||||
private val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1)
|
||||
private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps"
|
||||
|
||||
interface NodeConfiguration : NodeSSLConfiguration {
|
||||
val myLegalName: CordaX500Name
|
||||
@ -67,6 +69,7 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
||||
val effectiveH2Settings: NodeH2Settings?
|
||||
val flowMonitorPeriodMillis: Duration get() = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS
|
||||
val flowMonitorSuspensionLoggingThresholdMillis: Duration get() = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS
|
||||
val cordappDirectories: List<Path> get() = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT)
|
||||
|
||||
fun validate(): List<String>
|
||||
|
||||
@ -81,6 +84,8 @@ interface NodeConfiguration : NodeSSLConfiguration {
|
||||
|
||||
val defaultAttachmentContentCacheSize: Long = 10.MB
|
||||
const val defaultAttachmentCacheBound = 1024L
|
||||
|
||||
const val cordappDirectoriesKey = "cordappDirectories"
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,7 +207,8 @@ data class NodeConfigurationImpl(
|
||||
// do not use or remove (used by Capsule)
|
||||
private val jarDirs: List<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>()
|
||||
|
@ -0,0 +1 @@
|
||||
key=value
|
@ -39,6 +39,7 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.expect
|
||||
import net.corda.testing.core.expectEvents
|
||||
import net.corda.testing.core.sequence
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
@ -81,7 +82,7 @@ class CordaRPCOpsImplTest {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset"))
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset"))
|
||||
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
||||
rpc = aliceNode.rpcOps
|
||||
CURRENT_RPC_CONTEXT.set(RpcAuthContext(InvocationContext.rpc(testActor()), buildSubject("TEST_USER", emptySet())))
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.node.services.CordaService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
@ -18,7 +19,7 @@ class NodeUnloadHandlerTests {
|
||||
val shutdownLatch = CountDownLatch(1)
|
||||
}
|
||||
|
||||
private val mockNet = InternalMockNetwork(cordappPackages = listOf(javaClass.packageName), notarySpecs = emptyList())
|
||||
private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(javaClass.packageName), notarySpecs = emptyList())
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
|
@ -75,7 +75,7 @@ class CordappProviderImplTests {
|
||||
fun `test cordapp configuration`() {
|
||||
val configProvider = MockCordappConfigProvider()
|
||||
configProvider.cordappConfigs[isolatedCordappName] = validConfig
|
||||
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
|
||||
val provider = CordappProviderImpl(loader, configProvider, attachmentStore, whitelistedContractImplementations)
|
||||
|
||||
val expected = provider.getAppContext(provider.cordapps.first()).config
|
||||
@ -84,7 +84,7 @@ class CordappProviderImplTests {
|
||||
}
|
||||
|
||||
private fun newCordappProvider(vararg urls: URL): CordappProviderImpl {
|
||||
val loader = CordappLoader.createDevMode(urls.toList())
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(urls.toList())
|
||||
return CordappProviderImpl(loader, stubConfigProvider, attachmentStore, whitelistedContractImplementations)
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,13 @@ package net.corda.node.internal.cordapp
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.node.cordapp.CordappLoader
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.getTimestampAsDirectoryName
|
||||
import net.corda.testing.node.internal.packageInDirectory
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
@InitiatingFlow
|
||||
@ -30,7 +35,7 @@ class DummyRPCFlow : FlowLogic<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"
|
||||
@ -40,19 +45,19 @@ class CordappLoaderTest {
|
||||
@Test
|
||||
fun `test that classes that aren't in cordapps aren't loaded`() {
|
||||
// Basedir will not be a corda node directory so the dummy flow shouldn't be recognised as a part of a cordapp
|
||||
val loader = CordappLoader.createDefault(Paths.get("."))
|
||||
assertThat(loader.cordapps).containsOnly(CordappLoader.coreCordapp)
|
||||
val loader = JarScanningCordappLoader.fromDirectories(listOf(Paths.get(".")))
|
||||
assertThat(loader.cordapps).containsOnly(JarScanningCordappLoader.coreCordapp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isolated JAR contains a CorDapp with a contract and plugin`() {
|
||||
val isolatedJAR = CordappLoaderTest::class.java.getResource("isolated.jar")!!
|
||||
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("isolated.jar")!!
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
|
||||
|
||||
val actual = loader.cordapps.toTypedArray()
|
||||
assertThat(actual).hasSize(2)
|
||||
|
||||
val actualCordapp = actual.single { it != CordappLoader.coreCordapp }
|
||||
val actualCordapp = actual.single { it != JarScanningCordappLoader.coreCordapp }
|
||||
assertThat(actualCordapp.contractClassNames).isEqualTo(listOf(isolatedContractId))
|
||||
assertThat(actualCordapp.initiatedFlows.single().name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Acceptor")
|
||||
assertThat(actualCordapp.rpcFlows).isEmpty()
|
||||
@ -65,13 +70,11 @@ class CordappLoaderTest {
|
||||
|
||||
@Test
|
||||
fun `flows are loaded by loader`() {
|
||||
val loader = CordappLoader.createWithTestPackages(listOf(testScanPackage))
|
||||
val loader = cordappLoaderForPackages(listOf(testScanPackage))
|
||||
|
||||
val actual = loader.cordapps.toTypedArray()
|
||||
// One core cordapp, one cordapp from this source tree, and two others due to identically named locations
|
||||
// in resources and the non-test part of node. This is okay due to this being test code. In production this
|
||||
// cannot happen. In gradle it will also pick up the node jar.
|
||||
assertThat(actual.size == 4 || actual.size == 5).isTrue()
|
||||
// One core cordapp, one cordapp from this source tree. In gradle it will also pick up the node jar.
|
||||
assertThat(actual.size == 2 || actual.size == 3).isTrue()
|
||||
|
||||
val actualCordapp = actual.single { !it.initiatedFlows.isEmpty() }
|
||||
assertThat(actualCordapp.initiatedFlows).first().hasSameClassAs(DummyFlow::class.java)
|
||||
@ -81,14 +84,14 @@ class CordappLoaderTest {
|
||||
|
||||
@Test
|
||||
fun `duplicate packages are ignored`() {
|
||||
val loader = CordappLoader.createWithTestPackages(listOf(testScanPackage, testScanPackage))
|
||||
val loader = cordappLoaderForPackages(listOf(testScanPackage, testScanPackage))
|
||||
val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows }
|
||||
assertThat(cordapps).hasSize(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sub-packages are ignored`() {
|
||||
val loader = CordappLoader.createWithTestPackages(listOf("net.corda", testScanPackage))
|
||||
val loader = cordappLoaderForPackages(listOf("net.corda", testScanPackage))
|
||||
val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows }
|
||||
assertThat(cordapps).hasSize(1)
|
||||
}
|
||||
@ -97,10 +100,24 @@ class CordappLoaderTest {
|
||||
// being used internally. Later iterations will use a classloader per cordapp and this test can be retired.
|
||||
@Test
|
||||
fun `cordapp classloader can load cordapp classes`() {
|
||||
val isolatedJAR = CordappLoaderTest::class.java.getResource("isolated.jar")!!
|
||||
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
||||
val isolatedJAR = JarScanningCordappLoaderTest::class.java.getResource("isolated.jar")!!
|
||||
val loader = JarScanningCordappLoader.fromJarUrls(listOf(isolatedJAR))
|
||||
|
||||
loader.appClassLoader.loadClass(isolatedContractId)
|
||||
loader.appClassLoader.loadClass(isolatedFlowName)
|
||||
}
|
||||
|
||||
private fun cordappLoaderForPackages(packages: Iterable<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())
|
||||
}
|
||||
}
|
@ -50,6 +50,7 @@ import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.internal.vault.VaultFiller
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
import net.corda.testing.node.internal.pumpReceive
|
||||
@ -79,7 +80,7 @@ import kotlin.test.assertTrue
|
||||
@RunWith(Parameterized::class)
|
||||
class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
companion object {
|
||||
private val cordappPackages = listOf("net.corda.finance.contracts")
|
||||
private val cordappPackages = setOf("net.corda.finance.contracts")
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "Anonymous = {0}")
|
||||
fun data(): Collection<Boolean> = listOf(true, false)
|
||||
@ -107,7 +108,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
// We run this in parallel threads to help catch any race conditions that may exist. The other tests
|
||||
// we run in the unit test thread exclusively to speed things up, ensure deterministic results and
|
||||
// allow interruption half way through.
|
||||
mockNet = InternalMockNetwork(cordappPackages = cordappPackages, threadPerNode = true)
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages), threadPerNode = true)
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
@ -159,7 +160,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
|
||||
@Test(expected = InsufficientBalanceException::class)
|
||||
fun `trade cash for commercial paper fails using soft locking`() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = cordappPackages, threadPerNode = true)
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages), threadPerNode = true)
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
@ -217,7 +218,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
|
||||
@Test
|
||||
fun `shutdown and restore`() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
@ -316,11 +317,20 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
// of gets and puts.
|
||||
private fun makeNodeWithTracking(name: CordaX500Name): StartedNode<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))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -328,7 +338,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
|
||||
@Test
|
||||
fun `check dependencies of sale asset are resolved`() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
val aliceNode = makeNodeWithTracking(ALICE_NAME)
|
||||
val bobNode = makeNodeWithTracking(BOB_NAME)
|
||||
@ -432,7 +442,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
|
||||
@Test
|
||||
fun `track works`() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
|
||||
val notaryNode = mockNet.defaultNotaryNode
|
||||
val aliceNode = makeNodeWithTracking(ALICE_NAME)
|
||||
val bobNode = makeNodeWithTracking(BOB_NAME)
|
||||
@ -510,7 +520,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
|
||||
@Test
|
||||
fun `dependency with error on buyer side`() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
|
||||
runWithError(ledgerIdentityService, true, false, "at least one cash input")
|
||||
@ -519,7 +529,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) {
|
||||
|
||||
@Test
|
||||
fun `dependency with error on seller side`() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = cordappPackages)
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(cordappPackages))
|
||||
val ledgerIdentityService = rigorousMock<IdentityServiceInternal>()
|
||||
MockServices(cordappPackages, MEGA_CORP.name, ledgerIdentityService).ledger(DUMMY_NOTARY) {
|
||||
runWithError(ledgerIdentityService, false, true, "Issuances have a time-window")
|
||||
|
@ -1,11 +1,7 @@
|
||||
package net.corda.node.modes.draining
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.LinearState
|
||||
import net.corda.core.contracts.SchedulableState
|
||||
import net.corda.core.contracts.ScheduledActivity
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
@ -20,6 +16,7 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.dummyCommand
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
@ -51,7 +48,7 @@ class ScheduledFlowsDrainingModeTest {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"), threadPerNode = true)
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), threadPerNode = true)
|
||||
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
||||
bobNode = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME))
|
||||
notary = mockNet.defaultNotaryIdentity
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -35,6 +35,7 @@ import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.internal.LogHelper
|
||||
import net.corda.testing.node.InMemoryMessagingNetwork
|
||||
import net.corda.testing.node.MockNetworkParameters
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
@ -67,8 +68,8 @@ class TimedFlowTests {
|
||||
@JvmStatic
|
||||
fun setup() {
|
||||
mockNet = InternalMockNetwork(
|
||||
listOf("net.corda.testing.contracts", "net.corda.node.services"),
|
||||
MockNetworkParameters().withServicePeerAllocationStrategy(InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin()),
|
||||
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts", "net.corda.node.services"),
|
||||
defaultParameters = MockNetworkParameters().withServicePeerAllocationStrategy(InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin()),
|
||||
threadPerNode = true
|
||||
)
|
||||
val started = startClusterAndNode(mockNet)
|
||||
|
@ -23,6 +23,7 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.dummyCommand
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
@ -101,7 +102,7 @@ class ScheduledFlowTests {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"), threadPerNode = true)
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), threadPerNode = true)
|
||||
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
||||
bobNode = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME))
|
||||
notary = mockNet.defaultNotaryIdentity
|
||||
|
@ -15,6 +15,7 @@ import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.InProcessImpl
|
||||
import net.corda.testing.internal.vault.DummyLinearStateSchemaV1
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import org.hibernate.annotations.Cascade
|
||||
import org.hibernate.annotations.CascadeType
|
||||
@ -31,7 +32,7 @@ class NodeSchemaServiceTest {
|
||||
*/
|
||||
@Test
|
||||
fun `registering custom schemas for testing with MockNode`() {
|
||||
val mockNet = InternalMockNetwork(cordappPackages = listOf(DummyLinearStateSchemaV1::class.packageName))
|
||||
val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(DummyLinearStateSchemaV1::class.packageName))
|
||||
val mockNode = mockNet.createNode()
|
||||
val schemaService = mockNode.services.schemaService
|
||||
assertTrue(schemaService.schemaOptions.containsKey(DummyLinearStateSchemaV1))
|
||||
@ -64,8 +65,6 @@ class NodeSchemaServiceTest {
|
||||
|
||||
/**
|
||||
* Note: this test verifies auto-scanning to register identified [MappedSchema] schemas.
|
||||
* By default, Driver uses the caller package for auto-scanning:
|
||||
* System.setProperty("net.corda.node.cordapp.scan.packages", callerPackage)
|
||||
*/
|
||||
@Test
|
||||
fun `auto scanning of custom schemas for testing with Driver`() {
|
||||
|
@ -68,7 +68,7 @@ class FlowFrameworkTests {
|
||||
@JvmStatic
|
||||
fun beforeClass() {
|
||||
mockNet = InternalMockNetwork(
|
||||
cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"),
|
||||
cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts", "net.corda.testing.contracts"),
|
||||
servicePeerAllocationStrategy = RoundRobin()
|
||||
)
|
||||
|
||||
@ -481,7 +481,7 @@ class FlowFrameworkTripartyTests {
|
||||
@JvmStatic
|
||||
fun beforeClass() {
|
||||
mockNet = InternalMockNetwork(
|
||||
cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"),
|
||||
cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts", "net.corda.testing.contracts"),
|
||||
servicePeerAllocationStrategy = RoundRobin()
|
||||
)
|
||||
|
||||
@ -645,7 +645,7 @@ class FlowFrameworkPersistenceTests {
|
||||
@Before
|
||||
fun start() {
|
||||
mockNet = InternalMockNetwork(
|
||||
cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts"),
|
||||
cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts", "net.corda.testing.contracts"),
|
||||
servicePeerAllocationStrategy = RoundRobin()
|
||||
)
|
||||
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
||||
|
@ -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 = {
|
||||
|
@ -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()
|
||||
|
@ -15,6 +15,7 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.MockNetworkNotarySpec
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
@ -33,7 +34,7 @@ class NotaryServiceTests {
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = InternalMockNetwork(
|
||||
cordappPackages = listOf("net.corda.testing.contracts"),
|
||||
cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"),
|
||||
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME, validating = false))
|
||||
)
|
||||
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
||||
|
@ -36,6 +36,7 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.dummyCommand
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.node.TestClock
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InMemoryMessage
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
@ -62,7 +63,7 @@ class ValidatingNotaryServiceTests {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"))
|
||||
aliceNode = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME))
|
||||
notaryNode = mockNet.defaultNotaryNode
|
||||
notary = mockNet.defaultNotaryIdentity
|
||||
|
@ -29,6 +29,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
import org.junit.After
|
||||
@ -80,7 +81,7 @@ class VaultSoftLockManagerTest {
|
||||
private val mockVault = rigorousMock<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
|
||||
|
@ -11,6 +11,7 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.concurrent.fork
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.node.services.queryBy
|
||||
|
Reference in New Issue
Block a user