Automatically load contract attachments into the attachment store (#1500)

Attachments for contracts are now loaded into the attachment store by the cordapp provider.
This commit is contained in:
Clinton 2017-09-13 18:43:37 +01:00 committed by GitHub
parent eeb51527a1
commit b102900c90
6 changed files with 96 additions and 16 deletions

View File

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

View File

@ -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<Unit>()
/** 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<CordaPluginRegistry> 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() {

View File

@ -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<SecureHash, Cordapp>
/**
* 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<SecureHash, Cordapp> {
val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }
val attachmentIds = cordappsWithAttachments.map { it.jarPath.openStream().use { attachmentStorage.importAttachment(it) } }
return attachmentIds.zip(cordappsWithAttachments).toMap()
}
}

View File

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

View File

@ -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<CordaPluginRegistry>
get() = testPluginRegistries