mirror of
https://github.com/corda/corda.git
synced 2025-04-07 11:27:01 +00:00
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:
parent
eeb51527a1
commit
b102900c90
@ -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.
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()))
|
||||
}
|
||||
}
|
BIN
node/src/test/resources/net/corda/node/cordapp/empty.jar
Normal file
BIN
node/src/test/resources/net/corda/node/cordapp/empty.jar
Normal file
Binary file not shown.
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user