diff --git a/.idea/compiler.xml b/.idea/compiler.xml index f0b65c5a84..2d6467076b 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -41,6 +41,8 @@ + + diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index 2df896cbbe..ea9557d7c9 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -17,6 +17,7 @@ import java.net.URL * @property contractClassNames List of contracts * @property initiatedFlows List of initiatable flow classes * @property rpcFlows List of RPC initiable flows classes + * @property schedulableFlows List of flows startable by the scheduler * @property servies List of RPC services * @property plugins List of Corda plugin registries * @property customSchemas List of custom schemas @@ -27,6 +28,7 @@ interface Cordapp { val contractClassNames: List val initiatedFlows: List>> val rpcFlows: List>> + val schedulableFlows: List>> val services: List> val plugins: List val customSchemas: Set diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt index 38a5a0ecf4..8cfda77d42 100644 --- a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt @@ -12,6 +12,7 @@ data class CordappImpl( override val contractClassNames: List, override val initiatedFlows: List>>, override val rpcFlows: List>>, + override val schedulableFlows: List>>, override val services: List>, override val plugins: List, override val customSchemas: Set, diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index 39de4816a9..3bba0b8ae6 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -114,7 +114,8 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr } // Open attachments specified in this transaction. If we haven't downloaded them, we fail. val contractAttachments = findAttachmentContracts(resolvedInputs, resolveContractAttachment, resolveAttachment) - val attachments = contractAttachments + (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) }).distinct() + // Order of attachments is important since contracts may refer to indexes so only append automatic attachments + val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct() return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt) } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 03b9a9435e..4a344aec4e 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -1,5 +1,6 @@ package net.corda.node.services +import net.corda.client.rpc.RPCException import net.corda.core.contracts.Contract import net.corda.core.contracts.PartyAndReference import net.corda.core.cordapp.CordappProvider @@ -13,6 +14,7 @@ import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.loggerFor import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.nodeapi.User @@ -21,6 +23,7 @@ import net.corda.testing.DUMMY_NOTARY import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.driver.driver import net.corda.testing.node.MockServices +import net.corda.testing.resetTestSerialization import org.junit.Assert.* import org.junit.Before import org.junit.Test @@ -37,9 +40,10 @@ class AttachmentLoadingTests : TestDependencyInjectionBase() { override val cordappProvider: CordappProvider = provider } - companion object { - private val isolatedJAR = this::class.java.getResource("isolated.jar")!! - private val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract" + private companion object { + val logger = loggerFor() + val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!! + val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract" } private lateinit var services: Services @@ -67,19 +71,20 @@ class AttachmentLoadingTests : TestDependencyInjectionBase() { assertEquals(expected, actual) } - // TODO - activate this test - // @Test + @Test fun `test that attachments retrieved over the network are not used for code`() { driver(initialiseSerialization = false) { val bankAName = CordaX500Name("BankA", "Zurich", "CH") val bankBName = CordaX500Name("BankB", "Zurich", "CH") // Copy the app jar to the first node. The second won't have it. val path = (baseDirectory(bankAName.toString()) / "plugins").createDirectories() / "isolated.jar" + logger.info("Installing isolated jar to $path") isolatedJAR.openStream().buffered().use { input -> Files.newOutputStream(path).buffered().use { output -> input.copyTo(output) } } + val admin = User("admin", "admin", permissions = setOf("ALL")) val (bankA, bankB) = listOf( startNode(providedName = bankAName, rpcUsers = listOf(admin)), @@ -95,7 +100,7 @@ class AttachmentLoadingTests : TestDependencyInjectionBase() { val proxy = rpc.proxy val party = proxy.wellKnownPartyFromX500Name(bankBName)!! - assertFailsWith("xxx") { + assertFailsWith("net.corda.client.rpc.RPCException: net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") { proxy.startFlowDynamic(clazz, party).returnValue.getOrThrow() } } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 6ba72a75d6..ec5034ccdb 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -147,6 +147,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, protected lateinit var database: CordaPersistence protected var dbCloser: (() -> Any?)? = null lateinit var cordappProvider: CordappProviderImpl + protected val cordappLoader by lazy { makeCordappLoader() } protected val _nodeReadyFuture = openFuture() /** Completes once the node has successfully registered with the network map service @@ -378,7 +379,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, */ private fun makeServices(): MutableList { checkpointStorage = DBCheckpointStorage() - cordappProvider = CordappProviderImpl(makeCordappLoader()) + cordappProvider = CordappProviderImpl(cordappLoader) _services = ServiceHubInternalImpl() attachments = NodeAttachmentService(services.monitoringService.metrics) cordappProvider.start(attachments) @@ -399,10 +400,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val scanPackages = System.getProperty("net.corda.node.cordapp.scan.packages") return if (CordappLoader.testPackages.isNotEmpty()) { check(configuration.devMode) { "Package scanning can only occur in dev mode" } - CordappLoader.createWithTestPackages(CordappLoader.testPackages) + CordappLoader.createDefaultWithTestPackages(configuration.baseDirectory, CordappLoader.testPackages) } else if (scanPackages != null) { check(configuration.devMode) { "Package scanning can only occur in dev mode" } - CordappLoader.createWithTestPackages(scanPackages.split(",")) + CordappLoader.createDefaultWithTestPackages(configuration.baseDirectory, scanPackages.split(",")) } else { CordappLoader.createDefault(configuration.baseDirectory) } diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index bc4dcb386f..17660cfe37 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -14,6 +14,7 @@ import net.corda.core.node.ServiceHub import net.corda.core.serialization.SerializationDefaults import net.corda.core.utilities.* import net.corda.node.VersionInfo +import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.serialization.NodeClock import net.corda.node.services.RPCUserService @@ -340,14 +341,15 @@ open class Node(override val configuration: FullNodeConfiguration, } private fun initialiseSerialization() { + val classloader = cordappLoader.appClassLoader SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) registerScheme(AMQPServerSerializationScheme()) } - SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT - SerializationDefaults.RPC_SERVER_CONTEXT = KRYO_RPC_SERVER_CONTEXT - SerializationDefaults.STORAGE_CONTEXT = KRYO_STORAGE_CONTEXT - SerializationDefaults.CHECKPOINT_CONTEXT = KRYO_CHECKPOINT_CONTEXT + SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT.withClassLoader(classloader) + SerializationDefaults.RPC_SERVER_CONTEXT = KRYO_RPC_SERVER_CONTEXT.withClassLoader(classloader) + SerializationDefaults.STORAGE_CONTEXT = KRYO_STORAGE_CONTEXT.withClassLoader(classloader) + SerializationDefaults.CHECKPOINT_CONTEXT = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader) } /** Starts a blocking event loop for message dispatch. */ diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index f29fe98ed4..bb98afbe43 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -5,10 +5,7 @@ import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult import net.corda.core.contracts.Contract import net.corda.core.contracts.UpgradedContract import net.corda.core.cordapp.Cordapp -import net.corda.core.flows.ContractUpgradeFlow -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.StartableByRPC +import net.corda.core.flows.* import net.corda.core.internal.* import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.node.CordaPluginRegistry @@ -40,10 +37,17 @@ import kotlin.streams.toList * @property cordappJarPaths The classpath of cordapp JARs */ class CordappLoader private constructor(private val cordappJarPaths: List) { - val cordapps: List by lazy { loadCordapps() } + val cordapps: List by lazy { loadCordapps() + coreCordapp } - @VisibleForTesting - internal val appClassLoader: ClassLoader = javaClass.classLoader + internal val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.toTypedArray(), javaClass.classLoader) + + init { + if (cordappJarPaths.isEmpty()) { + logger.info("No CorDapp paths provided") + } else { + logger.info("Loading CorDapps from ${cordappJarPaths.joinToString()}") + } + } companion object { private val logger = loggerFor() @@ -54,19 +58,31 @@ class CordappLoader private constructor(private val cordappJarPaths: List) * @param baseDir The directory that this node is running in. Will use this to resolve the plugins directory * for classpath scanning. */ - fun createDefault(baseDir: Path): CordappLoader { - val pluginsDir = getPluginsPath(baseDir) - return CordappLoader(if (!pluginsDir.exists()) emptyList() else pluginsDir.list { - it.filter { it.isRegularFile() && it.toString().endsWith(".jar") }.map { it.toUri().toURL() }.toList() - }) - } - - fun getPluginsPath(baseDir: Path): Path = baseDir / "plugins" + fun createDefault(baseDir: Path) = CordappLoader(getCordappsInDirectory(getPluginsPath(baseDir))) /** - * Create a dev mode CordappLoader for test environments + * Create a dev mode CordappLoader for test environments that creates and loads cordapps from the classpath + * and plugins directory. This is intended mostly for use by the driver. + * + * @param baseDir See [createDefault.baseDir] + * @param testPackages See [createWithTestPackages.testPackages] */ - fun createWithTestPackages(testPackages: List = CordappLoader.testPackages) = CordappLoader(testPackages.flatMap(this::createScanPackage)) + @VisibleForTesting + @JvmOverloads + fun createDefaultWithTestPackages(baseDir: Path, testPackages: List = CordappLoader.testPackages) + = CordappLoader(getCordappsInDirectory(getPluginsPath(baseDir)) + testPackages.flatMap(this::createScanPackage)) + + /** + * 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 + @JvmOverloads + fun createWithTestPackages(testPackages: List = CordappLoader.testPackages) + = CordappLoader(testPackages.flatMap(this::createScanPackage)) /** * Creates a dev mode CordappLoader intended only to be used in test environments @@ -76,6 +92,8 @@ class CordappLoader private constructor(private val cordappJarPaths: List) @VisibleForTesting fun createDevMode(scanJars: List) = CordappLoader(scanJars) + private fun getPluginsPath(baseDir: Path): Path = baseDir / "plugins" + private fun createScanPackage(scanPackage: String): List { val resource = scanPackage.replace('.', '/') return this::class.java.classLoader.getResources(resource) @@ -90,7 +108,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List) .toList() } - /** Takes a package of classes and creates a JAR from them - only use in tests */ + /** Takes a package of classes and creates a JAR from them - only use in tests. */ private fun createDevCordappJar(scanPackage: String, path: URL, jarPackageName: String): URI { if(!generatedCordapps.contains(path)) { val cordappDir = File("build/tmp/generated-test-cordapps") @@ -118,12 +136,41 @@ class CordappLoader private constructor(private val cordappJarPaths: List) return generatedCordapps[path]!! } + private fun getCordappsInDirectory(pluginsDir: Path): List { + return if (!pluginsDir.exists()) { + emptyList() + } else { + pluginsDir.list { + it.filter { it.isRegularFile() && it.toString().endsWith(".jar") }.map { it.toUri().toURL() }.toList() + } + } + } + /** - * A list of test packages that will be scanned as CorDapps and compiled into CorDapp JARs for use in tests only + * A list of test packages that will be scanned as CorDapps and compiled into CorDapp JARs for use in tests only. */ @VisibleForTesting var testPackages: List = emptyList() private val generatedCordapps = mutableMapOf() + + /** 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 + internal val coreCordapp = CordappImpl( + listOf(), + listOf(), + coreRPCFlows, + listOf(), + listOf(), + listOf(), + setOf(), + ContractUpgradeFlow.javaClass.protectionDomain.codeSource.location // Core JAR location + ) } private fun loadCordapps(): List { @@ -132,6 +179,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List) CordappImpl(findContractClassNames(scanResult), findInitiatedFlows(scanResult), findRPCFlows(scanResult), + findSchedulableFlows(scanResult), findServices(scanResult), findPlugins(it), findCustomSchemas(scanResult), @@ -163,13 +211,11 @@ class CordappLoader private constructor(private val cordappJarPaths: List) return Modifier.isPublic(modifiers) && !isLocalClass && !isAnonymousClass && (!isMemberClass || Modifier.isStatic(modifiers)) } - val found = scanResult.getClassesWithAnnotation(FlowLogic::class, StartableByRPC::class).filter { it.isUserInvokable() } - val coreFlows = listOf( - ContractUpgradeFlow.Initiate::class.java, - ContractUpgradeFlow.Authorise::class.java, - ContractUpgradeFlow.Deauthorise::class.java - ) - return found + coreFlows + return scanResult.getClassesWithAnnotation(FlowLogic::class, StartableByRPC::class).filter { it.isUserInvokable() } + } + + private fun findSchedulableFlows(scanResult: ScanResult): List>> { + return scanResult.getClassesWithAnnotation(FlowLogic::class, SchedulableFlow::class) } private fun findContractClassNames(scanResult: ScanResult): List { diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt index 3eb485787f..9c3f58293f 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt @@ -66,7 +66,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader) : Singl * @return A cordapp context for the given CorDapp */ fun getAppContext(cordapp: Cordapp): CordappContext { - return CordappContext(cordapp, getCordappAttachmentId(cordapp), URLClassLoader(arrayOf(cordapp.jarPath), cordappLoader.appClassLoader)) + return CordappContext(cordapp, getCordappAttachmentId(cordapp), cordappLoader.appClassLoader) } /** diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt index 70761cfc4e..ad2eb239fa 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/RPCServer.kt @@ -459,4 +459,4 @@ object RpcServerObservableSerializer : Serializer>() { observableContext.clientAddressToObservables.put(observableContext.clientAddress, observableId) observableContext.observableMap.put(observableId, observableWithSubscription) } -} \ No newline at end of file +} diff --git a/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt deleted file mode 100644 index aadf3ee066..0000000000 --- a/node/src/test/kotlin/net/corda/node/cordapp/CordappLoaderTest.kt +++ /dev/null @@ -1,54 +0,0 @@ -package net.corda.node.cordapp - -import net.corda.core.flows.FlowLogic -import net.corda.core.flows.InitiatedBy -import net.corda.core.flows.InitiatingFlow -import net.corda.node.internal.cordapp.CordappLoader -import org.assertj.core.api.Assertions.assertThat -import org.junit.Test -import java.nio.file.Paths - -@InitiatingFlow -class DummyFlow : FlowLogic() { - override fun call() { } -} - -@InitiatedBy(DummyFlow::class) -class LoaderTestFlow : FlowLogic() { - override fun call() { } -} - -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).isEmpty() - } - - @Test - fun `test that classes that are in a cordapp are loaded`() { - val loader = CordappLoader.createWithTestPackages(listOf("net.corda.node.cordapp")) - val initiatedFlows = loader.cordapps.first().initiatedFlows - val expectedClass = loader.appClassLoader.loadClass("net.corda.node.cordapp.LoaderTestFlow").asSubclass(FlowLogic::class.java) - assertThat(initiatedFlows).contains(expectedClass) - } - - @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 actual = loader.cordapps.toTypedArray() - assertThat(actual).hasSize(1) - - val actualCordapp = actual.first() - assertThat(actualCordapp.contractClassNames).isEqualTo(listOf("net.corda.finance.contracts.isolated.AnotherDummyContract")) - assertThat(actualCordapp.initiatedFlows).isEmpty() - assertThat(actualCordapp.rpcFlows).contains(loader.appClassLoader.loadClass("net.corda.core.flows.ContractUpgradeFlow\$Initiate").asSubclass(FlowLogic::class.java)) - assertThat(actualCordapp.services).isEmpty() - assertThat(actualCordapp.plugins).hasSize(1) - assertThat(actualCordapp.plugins.first().javaClass.name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedPlugin") - assertThat(actualCordapp.jarPath).isEqualTo(isolatedJAR) - } -} diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt new file mode 100644 index 0000000000..806b7ac929 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt @@ -0,0 +1,89 @@ +package net.corda.node.internal.cordapp + +import net.corda.core.flows.* +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.nio.file.Paths + +@InitiatingFlow +class DummyFlow : FlowLogic() { + override fun call() { } +} + +@InitiatedBy(DummyFlow::class) +class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic() { + override fun call() { } +} + +@SchedulableFlow +class DummySchedulableFlow : FlowLogic() { + override fun call() { } +} + +@StartableByRPC +class DummyRPCFlow : FlowLogic() { + override fun call() { } +} + +class CordappLoaderTest { + private companion object { + val testScanPackages = listOf("net.corda.node.internal.cordapp") + val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract" + val isolatedFlowName = "net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator" + } + + @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) + .hasSize(1) + .contains(CordappLoader.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 actual = loader.cordapps.toTypedArray() + assertThat(actual).hasSize(2) + + val actualCordapp = actual.single { it != CordappLoader.coreCordapp } + assertThat(actualCordapp.contractClassNames).isEqualTo(listOf(isolatedContractId)) + assertThat(actualCordapp.initiatedFlows.single().name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Acceptor") + assertThat(actualCordapp.rpcFlows).isEmpty() + assertThat(actualCordapp.schedulableFlows).isEmpty() + assertThat(actualCordapp.services).isEmpty() + assertThat(actualCordapp.plugins).hasSize(1) + assertThat(actualCordapp.plugins.first().javaClass.name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedPlugin") + assertThat(actualCordapp.jarPath).isEqualTo(isolatedJAR) + } + + @Test + fun `flows are loaded by loader`() { + val loader = CordappLoader.createWithTestPackages(testScanPackages) + + 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() + + val actualCordapp = actual.single { !it.initiatedFlows.isEmpty() } + assertThat(actualCordapp.initiatedFlows).first().hasSameClassAs(DummyFlow::class.java) + assertThat(actualCordapp.rpcFlows).first().hasSameClassAs(DummyRPCFlow::class.java) + assertThat(actualCordapp.schedulableFlows).first().hasSameClassAs(DummySchedulableFlow::class.java) + } + + // This test exists because the appClassLoader is used by serialisation and we need to ensure it is the classloader + // 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)) + + loader.appClassLoader.loadClass(isolatedContractId) + loader.appClassLoader.loadClass(isolatedFlowName) + } +} diff --git a/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderImplTests.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt similarity index 94% rename from node/src/test/kotlin/net/corda/node/cordapp/CordappProviderImplTests.kt rename to node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt index 762761d99e..10dcd4a3ad 100644 --- a/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderImplTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt @@ -1,8 +1,6 @@ -package net.corda.node.cordapp +package net.corda.node.internal.cordapp import net.corda.core.node.services.AttachmentStorage -import net.corda.node.internal.cordapp.CordappLoader -import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.testing.node.MockAttachmentStorage import org.junit.Assert import org.junit.Before diff --git a/node/src/test/resources/net/corda/node/cordapp/empty.jar b/node/src/test/resources/net/corda/node/internal/cordapp/empty.jar similarity index 100% rename from node/src/test/resources/net/corda/node/cordapp/empty.jar rename to node/src/test/resources/net/corda/node/internal/cordapp/empty.jar diff --git a/node/src/test/resources/net/corda/node/cordapp/isolated.jar b/node/src/test/resources/net/corda/node/internal/cordapp/isolated.jar similarity index 100% rename from node/src/test/resources/net/corda/node/cordapp/isolated.jar rename to node/src/test/resources/net/corda/node/internal/cordapp/isolated.jar diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt index a0ca53fe0e..f51d59d20d 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/node/MockCordappProvider.kt @@ -14,7 +14,7 @@ class MockCordappProvider(cordappLoader: CordappLoader) : CordappProviderImpl(co val cordappRegistry = mutableListOf>() fun addMockCordapp(contractClassName: ContractClassName, services: ServiceHub) { - val cordapp = CordappImpl(listOf(contractClassName), emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), Paths.get(".").toUri().toURL()) + val cordapp = CordappImpl(listOf(contractClassName), emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), Paths.get(".").toUri().toURL()) if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) { cordappRegistry.add(Pair(cordapp, findOrImportAttachment(contractClassName.toByteArray(), services))) }