From b102900c90724397c95ac2afdfd1f467043e4e91 Mon Sep 17 00:00:00 2001 From: Clinton Date: Wed, 13 Sep 2017 18:43:37 +0100 Subject: [PATCH] Automatically load contract attachments into the attachment store (#1500) Attachments for contracts are now loaded into the attachment store by the cordapp provider. --- docs/source/changelog.rst | 4 ++ .../net/corda/node/internal/AbstractNode.kt | 33 ++++++++------- .../node/internal/cordapp/CordappProvider.kt | 38 ++++++++++++++++++ .../node/cordapp/CordappProviderTests.kt | 35 ++++++++++++++++ .../net/corda/node/cordapp/empty.jar | Bin 0 -> 22 bytes .../kotlin/net/corda/testing/node/MockNode.kt | 2 +- 6 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProvider.kt create mode 100644 node/src/test/kotlin/net/corda/node/cordapp/CordappProviderTests.kt create mode 100644 node/src/test/resources/net/corda/node/cordapp/empty.jar diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 81406ccf1c..c2addf4265 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -9,6 +9,10 @@ UNRELEASED * ``ContractState::contract`` has been moved ``TransactionState::contract`` and it's type has changed to ``String`` in order to support dynamic classloading of contract and contract constraints. +* CorDapps that contain contracts are now automatically loaded into the attachment storage - for CorDapp developers this + now means that contracts should be stored in separate JARs to flows, services and utilities to avoid large JARs being + auto imported to the attachment store. + * About half of the code in test-utils has been moved to a new module ``node-driver``, and the test scope modules are now located in a ``testing`` directory. 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 8c5f7d5521..859985d5a1 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -33,6 +33,7 @@ import net.corda.core.utilities.cert import net.corda.core.utilities.debug import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.cordapp.CordappProvider import net.corda.node.services.NotaryChangeHandler import net.corda.node.services.NotifyTransactionHandler import net.corda.node.services.SwapIdentitiesHandler @@ -142,6 +143,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, protected val runOnStop = ArrayList<() -> Any?>() protected lateinit var database: CordaPersistence protected var dbCloser: (() -> Any?)? = null + lateinit var cordappProvider: CordappProvider protected val _nodeReadyFuture = openFuture() /** Completes once the node has successfully registered with the network map service @@ -154,18 +156,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, CordaX500Name.build(cert.subject).copy(commonName = null) } - private val cordappLoader: CordappLoader by lazy { - val scanPackage = System.getProperty("net.corda.node.cordapp.scan.package") - if (scanPackage != null) { - check(configuration.devMode) { "Package scanning can only occur in dev mode" } - CordappLoader.createDevMode(scanPackage) - } else { - CordappLoader.createDefault(configuration.baseDirectory) - } - } - open val pluginRegistries: List by lazy { - cordappLoader.cordapps.flatMap { it.plugins } + DefaultWhitelist() + cordappProvider.cordapps.flatMap { it.plugins } + DefaultWhitelist() } /** Set to non-null once [start] has been successfully called. */ @@ -216,8 +208,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, installCordaServices() registerCordappFlows() - _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } - registerCustomSchemas(cordappLoader.cordapps.flatMap { it.customSchemas }.toSet()) + _services.rpcFlows += cordappProvider.cordapps.flatMap { it.rpcFlows } + registerCustomSchemas(cordappProvider.cordapps.flatMap { it.customSchemas }.toSet()) runOnStop += network::stop StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps) @@ -238,7 +230,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, private class ServiceInstantiationException(cause: Throwable?) : Exception(cause) private fun installCordaServices() { - cordappLoader.cordapps.flatMap { it.services }.forEach { + cordappProvider.cordapps.flatMap { it.services }.forEach { try { installCordaService(it) } catch (e: NoSuchMethodException) { @@ -280,7 +272,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, } private fun registerCordappFlows() { - cordappLoader.cordapps.flatMap { it.initiatedFlows } + cordappProvider.cordapps.flatMap { it.initiatedFlows } .forEach { try { registerInitiatedFlowInternal(it, track = false) @@ -359,6 +351,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, checkpointStorage = DBCheckpointStorage() _services = ServiceHubInternalImpl() attachments = NodeAttachmentService(services.monitoringService.metrics) + cordappProvider = CordappProvider(attachments, makeCordappLoader()) val legalIdentity = obtainIdentity() network = makeMessagingService(legalIdentity) info = makeInfo(legalIdentity) @@ -369,6 +362,16 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, return tokenizableServices } + private fun makeCordappLoader(): CordappLoader { + val scanPackage = System.getProperty("net.corda.node.cordapp.scan.package") + return if (scanPackage != null) { + check(configuration.devMode) { "Package scanning can only occur in dev mode" } + CordappLoader.createDevMode(scanPackage) + } else { + CordappLoader.createDefault(configuration.baseDirectory) + } + } + protected open fun makeTransactionStorage(): WritableTransactionStorage = DBTransactionStorage() private fun makeVaultObservers() { diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProvider.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProvider.kt new file mode 100644 index 0000000000..ea182bc75c --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProvider.kt @@ -0,0 +1,38 @@ +package net.corda.node.internal.cordapp + +import com.google.common.collect.HashBiMap +import net.corda.core.crypto.SecureHash +import net.corda.core.node.services.AttachmentStorage +import java.util.* + +/** + * Cordapp provider and store. For querying CorDapps for their attachment and vice versa. + */ +class CordappProvider(private val attachmentStorage: AttachmentStorage, private val cordappLoader: CordappLoader) { + /** + * Current known CorDapps loaded on this node + */ + val cordapps get() = cordappLoader.cordapps + private lateinit var cordappAttachments: HashBiMap + + /** + * Should only be called once from the initialisation routine of the node or tests + */ + fun start() { + cordappAttachments = HashBiMap.create(loadContractsIntoAttachmentStore()) + } + + /** + * Gets the attachment ID of this CorDapp. Only CorDapps with contracts have an attachment ID + * + * @param cordapp The cordapp to get the attachment ID + * @return An attachment ID if it exists, otherwise nothing + */ + fun getCordappAttachmentId(cordapp: Cordapp): SecureHash? = cordappAttachments.inverse().get(cordapp) + + private fun loadContractsIntoAttachmentStore(): Map { + val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() } + val attachmentIds = cordappsWithAttachments.map { it.jarPath.openStream().use { attachmentStorage.importAttachment(it) } } + return attachmentIds.zip(cordappsWithAttachments).toMap() + } +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderTests.kt b/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderTests.kt new file mode 100644 index 0000000000..4729118d6f --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/cordapp/CordappProviderTests.kt @@ -0,0 +1,35 @@ +package net.corda.node.cordapp + +import net.corda.node.internal.cordapp.CordappLoader +import net.corda.node.internal.cordapp.CordappProvider +import net.corda.testing.node.MockAttachmentStorage +import org.junit.Assert +import org.junit.Test + +class CordappProviderTests { + @Test + fun `isolated jar is loaded into the attachment store`() { + val attachmentStore = MockAttachmentStorage() + val isolatedJAR = this::class.java.getResource("isolated.jar")!! + val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) + val provider = CordappProvider(attachmentStore, loader) + + provider.start() + val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first()) + + Assert.assertNotNull(maybeAttachmentId) + Assert.assertNotNull(attachmentStore.openAttachment(maybeAttachmentId!!)) + } + + @Test + fun `empty jar is not loaded into the attachment store`() { + val attachmentStore = MockAttachmentStorage() + val isolatedJAR = this::class.java.getResource("empty.jar")!! + val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) + val provider = CordappProvider(attachmentStore, loader) + + provider.start() + + Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first())) + } +} \ No newline at end of file diff --git a/node/src/test/resources/net/corda/node/cordapp/empty.jar b/node/src/test/resources/net/corda/node/cordapp/empty.jar new file mode 100644 index 0000000000000000000000000000000000000000..15cb0ecb3e219d1701294bfdf0fe3f5cb5d208e7 GIT binary patch literal 22 NcmWIWW@Tf*000g10H*)| literal 0 HcmV?d00001 diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index cdfe1ea007..a8904066fd 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -233,7 +233,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, // Allow unit tests to modify the plugin list before the node start, // so they don't have to ServiceLoad test plugins into all unit tests. - val testPluginRegistries = super.pluginRegistries.toMutableList() + val testPluginRegistries by lazy { super.pluginRegistries.toMutableList() } override val pluginRegistries: List get() = testPluginRegistries