mirror of
https://github.com/corda/corda.git
synced 2024-12-19 13:08:04 +00:00
Fixes bugs with contract constraints (#1696)
* Added schedulable flows to cordapp scanning Fixed a bug where the core flows are included in every cordapp. Added a test to prove the scheduled flows are loaded correctly. Added scheduled flow support to cordapp. Renabled broken test. Fixed test to prove cordapps aren't retreived from network. Review fixes. Fixed a test issue caused by gradle having slightly different paths to IntelliJ * Fixed test for real this time.
This commit is contained in:
parent
73e3690596
commit
ca0090420b
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -41,6 +41,8 @@
|
||||
<module name="explorer_test" target="1.8" />
|
||||
<module name="finance_main" target="1.8" />
|
||||
<module name="finance_test" target="1.8" />
|
||||
<module name="gradle-plugins-cordform-common_main" target="1.8" />
|
||||
<module name="gradle-plugins-cordform-common_test" target="1.8" />
|
||||
<module name="graphs_main" target="1.8" />
|
||||
<module name="graphs_test" target="1.8" />
|
||||
<module name="irs-demo_integrationTest" target="1.8" />
|
||||
|
@ -16,6 +16,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
|
||||
@ -25,6 +26,7 @@ interface Cordapp {
|
||||
val contractClassNames: List<String>
|
||||
val initiatedFlows: List<Class<out FlowLogic<*>>>
|
||||
val rpcFlows: List<Class<out FlowLogic<*>>>
|
||||
val schedulableFlows: List<Class<out FlowLogic<*>>>
|
||||
val services: List<Class<out SerializeAsToken>>
|
||||
val plugins: List<CordaPluginRegistry>
|
||||
val customSchemas: Set<MappedSchema>
|
||||
|
@ -11,6 +11,7 @@ data class CordappImpl(
|
||||
override val contractClassNames: List<String>,
|
||||
override val initiatedFlows: List<Class<out FlowLogic<*>>>,
|
||||
override val rpcFlows: List<Class<out FlowLogic<*>>>,
|
||||
override val schedulableFlows: List<Class<out FlowLogic<*>>>,
|
||||
override val services: List<Class<out SerializeAsToken>>,
|
||||
override val plugins: List<CordaPluginRegistry>,
|
||||
override val customSchemas: Set<MappedSchema>,
|
||||
|
@ -114,7 +114,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, 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)
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import net.corda.core.identity.Party
|
||||
* loaded or blocked.
|
||||
*/
|
||||
class IsolatedDummyFlow {
|
||||
@StartableByRPC
|
||||
@InitiatingFlow
|
||||
class Initiator(val toWhom: Party) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
|
@ -523,7 +523,7 @@ object LoggerSerializer : Serializer<Logger>() {
|
||||
object ClassSerializer : Serializer<Class<*>>() {
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<Class<*>>): Class<*> {
|
||||
val className = input.readString()
|
||||
return Class.forName(className)
|
||||
return Class.forName(className, true, kryo.classLoader)
|
||||
}
|
||||
|
||||
override fun write(kryo: Kryo, output: Output, clazz: Class<*>) {
|
||||
|
@ -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
|
||||
@ -14,6 +15,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
|
||||
@ -22,6 +24,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
|
||||
@ -38,9 +41,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<AttachmentLoadingTests>()
|
||||
val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!!
|
||||
val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
||||
}
|
||||
|
||||
private lateinit var services: Services
|
||||
@ -68,19 +72,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)),
|
||||
@ -96,7 +101,7 @@ class AttachmentLoadingTests : TestDependencyInjectionBase() {
|
||||
val proxy = rpc.proxy
|
||||
val party = proxy.wellKnownPartyFromX500Name(bankBName)!!
|
||||
|
||||
assertFailsWith<Exception>("xxx") {
|
||||
assertFailsWith<RPCException>("net.corda.client.rpc.RPCException: net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator") {
|
||||
proxy.startFlowDynamic(clazz, party).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@ -146,6 +146,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<Unit>()
|
||||
/** Completes once the node has successfully registered with the network map service
|
||||
@ -379,7 +380,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
*/
|
||||
private fun makeServices(): MutableList<Any> {
|
||||
checkpointStorage = DBCheckpointStorage()
|
||||
cordappProvider = CordappProviderImpl(makeCordappLoader())
|
||||
cordappProvider = CordappProviderImpl(cordappLoader)
|
||||
_services = ServiceHubInternalImpl()
|
||||
attachments = NodeAttachmentService(services.monitoringService.metrics)
|
||||
cordappProvider.start(attachments)
|
||||
@ -400,10 +401,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)
|
||||
}
|
||||
|
@ -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. */
|
||||
|
@ -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<URL>) {
|
||||
val cordapps: List<Cordapp> by lazy { loadCordapps() }
|
||||
val cordapps: List<Cordapp> 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<CordappLoader>()
|
||||
@ -54,19 +58,31 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
|
||||
* @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<URL>() 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<String> = CordappLoader.testPackages) = CordappLoader(testPackages.flatMap(this::createScanPackage))
|
||||
@VisibleForTesting
|
||||
@JvmOverloads
|
||||
fun createDefaultWithTestPackages(baseDir: Path, testPackages: List<String> = 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<String> = 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<URL>)
|
||||
@VisibleForTesting
|
||||
fun createDevMode(scanJars: List<URL>) = CordappLoader(scanJars)
|
||||
|
||||
private fun getPluginsPath(baseDir: Path): Path = baseDir / "plugins"
|
||||
|
||||
private fun createScanPackage(scanPackage: String): List<URL> {
|
||||
val resource = scanPackage.replace('.', '/')
|
||||
return this::class.java.classLoader.getResources(resource)
|
||||
@ -90,7 +108,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
|
||||
.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<URL>)
|
||||
return generatedCordapps[path]!!
|
||||
}
|
||||
|
||||
private fun getCordappsInDirectory(pluginsDir: Path): List<URL> {
|
||||
return if (!pluginsDir.exists()) {
|
||||
emptyList<URL>()
|
||||
} 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<String> = emptyList()
|
||||
private val generatedCordapps = mutableMapOf<URL, URI>()
|
||||
|
||||
/** 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<Cordapp> {
|
||||
@ -132,6 +179,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
|
||||
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<URL>)
|
||||
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<Class<out FlowLogic<*>>> {
|
||||
return scanResult.getClassesWithAnnotation(FlowLogic::class, SchedulableFlow::class)
|
||||
}
|
||||
|
||||
private fun findContractClassNames(scanResult: ScanResult): List<String> {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<Unit>() {
|
||||
override fun call() { }
|
||||
}
|
||||
|
||||
@InitiatedBy(DummyFlow::class)
|
||||
class LoaderTestFlow : FlowLogic<Unit>() {
|
||||
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)
|
||||
}
|
||||
}
|
@ -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<Unit>() {
|
||||
override fun call() { }
|
||||
}
|
||||
|
||||
@InitiatedBy(DummyFlow::class)
|
||||
class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic<Unit>() {
|
||||
override fun call() { }
|
||||
}
|
||||
|
||||
@SchedulableFlow
|
||||
class DummySchedulableFlow : FlowLogic<Unit>() {
|
||||
override fun call() { }
|
||||
}
|
||||
|
||||
@StartableByRPC
|
||||
class DummyRPCFlow : FlowLogic<Unit>() {
|
||||
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)
|
||||
}
|
||||
}
|
@ -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
|
@ -14,7 +14,7 @@ class MockCordappProvider(cordappLoader: CordappLoader) : CordappProviderImpl(co
|
||||
val cordappRegistry = mutableListOf<Pair<Cordapp, AttachmentId>>()
|
||||
|
||||
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)))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user