CORDA-676 Eager cordapp schemas (#1839)

* Retire customSchemas.
* Key cordapp-to-hash map by url as native equality too strict.
This commit is contained in:
Andrzej Cichocki 2017-10-16 11:35:29 +01:00 committed by GitHub
parent d3d87c2497
commit 38cf4a489e
46 changed files with 197 additions and 236 deletions

View File

@ -32,7 +32,7 @@ import static net.corda.testing.TestConstants.getALICE;
public class CordaRPCJavaClientTest extends NodeBasedTest {
public CordaRPCJavaClientTest() {
super(Collections.singletonList("net.corda.finance.contracts"));
super(Arrays.asList("net.corda.finance.contracts", CashSchemaV1.class.getPackage().getName()));
}
private List<String> perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class));
@ -53,7 +53,6 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
public void setUp() throws ExecutionException, InterruptedException {
CordaFuture<StartedNode<Node>> nodeFuture = startNotaryNode(getALICE().getName(), singletonList(rpcUser), true);
node = nodeFuture.get();
node.getInternals().registerCustomSchemas(Collections.singleton(CashSchemaV1.INSTANCE));
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()));
}

View File

@ -2,6 +2,7 @@ package net.corda.client.rpc
import net.corda.core.crypto.random63BitValue
import net.corda.core.flows.FlowInitiator
import net.corda.core.internal.packageName
import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow
@ -32,7 +33,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts")) {
class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) {
private val rpcUser = User("user1", "test", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()
@ -48,7 +49,6 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts"))
@Before
fun setUp() {
node = startNotaryNode(ALICE.name, rpcUsers = listOf(rpcUser)).getOrThrow()
node.internals.registerCustomSchemas(setOf(CashSchemaV1))
client = CordaRPCClient(node.internals.configuration.rpcAddress!!)
}

View File

@ -300,3 +300,6 @@ fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serial
* @suppress
*/
fun TransactionBuilder.toLedgerTransaction(services: ServiceHub, serializationContext: SerializationContext) = toLedgerTransactionWithContext(services, serializationContext)
/** Convenience method to get the package name of a class literal. */
val KClass<*>.packageName get() = java.`package`.name

View File

@ -61,6 +61,7 @@ class InternalUtilsTest {
assertArrayEquals(intArrayOf(1, 2, 3, 4), (1 until 5).stream().toArray())
assertArrayEquals(intArrayOf(1, 3), (1..4 step 2).stream().toArray())
assertArrayEquals(intArrayOf(1, 3), (1..3 step 2).stream().toArray())
@Suppress("EmptyRange") // It's supposed to be empty.
assertArrayEquals(intArrayOf(), (1..0).stream().toArray())
assertArrayEquals(intArrayOf(1, 0), (1 downTo 0).stream().toArray())
assertArrayEquals(intArrayOf(3, 1), (3 downTo 0 step 2).stream().toArray())

View File

@ -2,6 +2,7 @@ package net.corda.docs
import net.corda.core.contracts.Amount
import net.corda.core.identity.Party
import net.corda.core.internal.packageName
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.*
@ -26,15 +27,12 @@ class CustomVaultQueryTest {
@Before
fun setup() {
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset"))
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName))
mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
nodeA = mockNet.createPartyNode()
nodeB = mockNet.createPartyNode()
nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java)
nodeA.internals.installCordaService(CustomVaultQuery.Service::class.java)
nodeA.internals.registerCustomSchemas(setOf(CashSchemaV1))
nodeB.internals.registerCustomSchemas(setOf(CashSchemaV1))
notary = nodeA.services.getDefaultNotary()
}

View File

@ -1,6 +1,7 @@
package net.corda.docs
import net.corda.core.identity.Party
import net.corda.core.internal.packageName
import net.corda.core.toFuture
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
@ -24,12 +25,10 @@ class FxTransactionBuildTutorialTest {
@Before
fun setup() {
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset"))
mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName))
mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
nodeA = mockNet.createPartyNode()
nodeB = mockNet.createPartyNode()
nodeA.internals.registerCustomSchemas(setOf(CashSchemaV1))
nodeB.internals.registerCustomSchemas(setOf(CashSchemaV1))
nodeB.internals.registerInitiatedFlow(ForeignExchangeRemoteFlow::class.java)
notary = nodeA.services.getDefaultNotary()
}

View File

@ -55,8 +55,7 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
class DummyServiceHub : MockServices() {
override val cordappProvider: CordappProviderImpl
= CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH))).start(attachments)
= CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), attachments)
private val cordapp get() = cordappProvider.cordapps.first()
val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
val appContext get() = cordappProvider.getAppContext(cordapp)

View File

@ -37,7 +37,7 @@ import kotlin.test.assertFailsWith
class AttachmentLoadingTests : TestDependencyInjectionBase() {
private class Services : MockServices() {
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR))).start(attachments)
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), attachments)
private val cordapp get() = provider.cordapps.first()
val attachmentId get() = provider.getCordappAttachmentId(cordapp)!!
val appContext get() = provider.getAppContext(cordapp)

View File

@ -7,7 +7,6 @@ import net.corda.confidential.SwapIdentitiesFlow
import net.corda.confidential.SwapIdentitiesHandler
import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.*
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
@ -26,7 +25,6 @@ import net.corda.core.node.ServiceHub
import net.corda.core.node.StateLoader
import net.corda.core.node.services.*
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
@ -37,6 +35,7 @@ import net.corda.node.VersionInfo
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler
@ -148,7 +147,6 @@ abstract class AbstractNode(config: NodeConfiguration,
protected lateinit var network: MessagingService
protected val runOnStop = ArrayList<() -> Any?>()
protected lateinit var database: CordaPersistence
lateinit var cordappProvider: CordappProviderImpl
protected val _nodeReadyFuture = openFuture<Unit>()
/** Completes once the node has successfully registered with the network map service
* or has loaded network map data from local database */
@ -161,7 +159,7 @@ abstract class AbstractNode(config: NodeConfiguration,
}
open val serializationWhitelists: List<SerializationWhitelist> by lazy {
cordappProvider.cordapps.flatMap { it.serializationWhitelists }
cordappLoader.cordapps.flatMap { it.serializationWhitelists }
}
/** Set to non-null once [start] has been successfully called. */
@ -185,11 +183,12 @@ abstract class AbstractNode(config: NodeConfiguration,
validateKeystore()
}
private fun makeSchemaService() = NodeSchemaService(cordappLoader)
open fun generateNodeInfo() {
check(started == null) { "Node has already been started" }
initCertificate()
log.info("Generating nodeInfo ...")
val schemaService = NodeSchemaService()
val schemaService = makeSchemaService()
initialiseDatabasePersistence(schemaService) {
makeServices(schemaService)
saveOwnNodeInfo()
@ -200,7 +199,7 @@ abstract class AbstractNode(config: NodeConfiguration,
check(started == null) { "Node has already been started" }
initCertificate()
log.info("Node starting up ...")
val schemaService = NodeSchemaService()
val schemaService = makeSchemaService()
// Do all of this in a database transaction so anything that might need a connection has one.
val startedImpl = initialiseDatabasePersistence(schemaService) {
val tokenizableServices = makeServices(schemaService)
@ -231,8 +230,7 @@ abstract class AbstractNode(config: NodeConfiguration,
installCordaServices()
registerCordappFlows()
_services.rpcFlows += cordappProvider.cordapps.flatMap { it.rpcFlows }
registerCustomSchemas(cordappProvider.cordapps.flatMap { it.customSchemas }.toSet())
_services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows }
FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader
runOnStop += network::stop
@ -254,7 +252,7 @@ abstract class AbstractNode(config: NodeConfiguration,
private class ServiceInstantiationException(cause: Throwable?) : CordaException("Service Instantiation Error", cause)
private fun installCordaServices() {
val loadedServices = cordappProvider.cordapps.flatMap { it.services }
val loadedServices = cordappLoader.cordapps.flatMap { it.services }
filterServicesToInstall(loadedServices).forEach {
try {
installCordaService(it)
@ -374,7 +372,7 @@ abstract class AbstractNode(config: NodeConfiguration,
}
private fun registerCordappFlows() {
cordappProvider.cordapps.flatMap { it.initiatedFlows }
cordappLoader.cordapps.flatMap { it.initiatedFlows }
.forEach {
try {
registerInitiatedFlowInternal(it, track = false)
@ -471,11 +469,11 @@ abstract class AbstractNode(config: NodeConfiguration,
*/
private fun makeServices(schemaService: SchemaService): MutableList<Any> {
checkpointStorage = DBCheckpointStorage()
cordappProvider = CordappProviderImpl(cordappLoader)
val transactionStorage = makeTransactionStorage()
_services = ServiceHubInternalImpl(schemaService, transactionStorage, StateLoaderImpl(transactionStorage))
attachments = NodeAttachmentService(services.monitoringService.metrics)
cordappProvider.start(attachments)
val metrics = MetricRegistry()
attachments = NodeAttachmentService(metrics)
val cordappProvider = CordappProviderImpl(cordappLoader, attachments)
_services = ServiceHubInternalImpl(schemaService, transactionStorage, StateLoaderImpl(transactionStorage), MonitoringService(metrics), cordappProvider)
legalIdentity = obtainIdentity(notaryConfig = null)
network = makeMessagingService(legalIdentity)
info = makeInfo(legalIdentity)
@ -541,7 +539,7 @@ abstract class AbstractNode(config: NodeConfiguration,
protected open fun <T> initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: () -> T): T {
val props = configuration.dataSourceProperties
if (props.isNotEmpty()) {
this.database = configureDatabase(props, configuration.database, schemaService, { _services.identityService })
this.database = configureDatabase(props, configuration.database, { _services.identityService }, schemaService)
// Now log the vendor string as this will also cause a connection to be tested eagerly.
database.transaction {
log.info("Connected to ${database.dataSource.connection.metaData.databaseProductName} database.")
@ -764,12 +762,13 @@ abstract class AbstractNode(config: NodeConfiguration,
private inner class ServiceHubInternalImpl(
override val schemaService: SchemaService,
override val validatedTransactions: WritableTransactionStorage,
private val stateLoader: StateLoader
private val stateLoader: StateLoader,
override val monitoringService: MonitoringService,
override val cordappProvider: CordappProviderInternal
) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by stateLoader {
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
override val auditService = DummyAuditService()
override val monitoringService = MonitoringService(MetricRegistry())
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
override val networkMapCache by lazy { PersistentNetworkMapCache(this) }
override val vaultService by lazy { NodeVaultService(platformClock, keyManagementService, stateLoader, this@AbstractNode.database.hibernateConfig) }
@ -794,8 +793,6 @@ abstract class AbstractNode(config: NodeConfiguration,
override val myInfo: NodeInfo get() = info
override val database: CordaPersistence get() = this@AbstractNode.database
override val configuration: NodeConfiguration get() = this@AbstractNode.configuration
override val cordappProvider: CordappProvider = this@AbstractNode.cordappProvider
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
return cordappServices.getInstance(type) ?: throw IllegalArgumentException("Corda service ${type.name} does not exist")
@ -817,9 +814,4 @@ abstract class AbstractNode(config: NodeConfiguration,
override fun jdbcSession(): Connection = database.createSession()
}
fun registerCustomSchemas(schemas: Set<MappedSchema>) {
database.hibernateConfig.schemaService.registerCustomSchemas(schemas)
}
}

View File

@ -62,7 +62,7 @@ import kotlin.system.exitProcess
*
* @param configuration This is typically loaded from a TypeSafe HOCON configuration file.
*/
open class Node(override val configuration: FullNodeConfiguration,
open class Node(configuration: FullNodeConfiguration,
versionInfo: VersionInfo,
val initialiseSerialization: Boolean = true,
cordappLoader: CordappLoader = makeCordappLoader(configuration)
@ -99,6 +99,7 @@ open class Node(override val configuration: FullNodeConfiguration,
}
override val log: Logger get() = logger
override val configuration get() = super.configuration as FullNodeConfiguration // Necessary to avoid init order NPE.
override val networkMapAddress: NetworkMapAddress? get() = configuration.networkMapService?.address?.let(::NetworkMapAddress)
override fun makeTransactionVerifierService() = (network as NodeMessagingClient).verifierService

View File

@ -95,7 +95,7 @@ open class NodeStartup(val args: Array<String>) {
return
}
val startedNode = node.start()
Node.printBasicNodeInfo("Loaded CorDapps", startedNode.internals.cordappProvider.cordapps.joinToString { it.name })
Node.printBasicNodeInfo("Loaded CorDapps", startedNode.services.cordappProvider.cordapps.joinToString { it.name })
startedNode.internals.nodeReadyFuture.thenMatch({
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
val name = startedNode.info.legalIdentitiesAndCerts.first().name.organisation

View File

@ -38,10 +38,10 @@ import kotlin.streams.toList
*
* @property cordappJarPaths The classpath of cordapp JARs
*/
class CordappLoader private constructor(private val cordappJarPaths: List<URL>) {
class CordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>) {
val cordapps: List<Cordapp> by lazy { loadCordapps() + coreCordapp }
internal val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.toTypedArray(), javaClass.classLoader)
internal val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader)
init {
if (cordappJarPaths.isEmpty()) {
@ -97,20 +97,22 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection
*/
@VisibleForTesting
fun createDevMode(scanJars: List<URL>) = CordappLoader(scanJars)
fun createDevMode(scanJars: List<URL>) = CordappLoader(scanJars.map { RestrictedURL(it, null) })
private fun getCordappsPath(baseDir: Path): Path = baseDir / CORDAPPS_DIR_NAME
private fun createScanPackage(scanPackage: String): List<URL> {
private fun createScanPackage(scanPackage: String): List<RestrictedURL> {
val resource = scanPackage.replace('.', '/')
return this::class.java.classLoader.getResources(resource)
.asSequence()
.map { path ->
if (path.protocol == "jar") {
(path.openConnection() as JarURLConnection).jarFileURL.toURI()
// When running tests from gradle this may be a corda module jar, so restrict to scanPackage:
RestrictedURL((path.openConnection() as JarURLConnection).jarFileURL, scanPackage)
} else {
createDevCordappJar(scanPackage, path, resource)
}.toURL()
// No need to restrict as createDevCordappJar has already done that:
RestrictedURL(createDevCordappJar(scanPackage, path, resource).toURL(), null)
}
}
.toList()
}
@ -143,12 +145,12 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
return generatedCordapps[path]!!
}
private fun getCordappsInDirectory(cordappsDir: Path): List<URL> {
private fun getCordappsInDirectory(cordappsDir: Path): List<RestrictedURL> {
return if (!cordappsDir.exists()) {
emptyList<URL>()
emptyList()
} else {
cordappsDir.list {
it.filter { it.isRegularFile() && it.toString().endsWith(".jar") }.map { it.toUri().toURL() }.toList()
it.filter { it.isRegularFile() && it.toString().endsWith(".jar") }.map { RestrictedURL(it.toUri().toURL(), null) }.toList()
}
}
}
@ -187,15 +189,15 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
findServices(scanResult),
findPlugins(it),
findCustomSchemas(scanResult),
it)
it.url)
}
}
private fun findServices(scanResult: ScanResult): List<Class<out SerializeAsToken>> {
private fun findServices(scanResult: RestrictedScanResult): List<Class<out SerializeAsToken>> {
return scanResult.getClassesWithAnnotation(SerializeAsToken::class, CordaService::class)
}
private fun findInitiatedFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> {
private fun findInitiatedFlows(scanResult: RestrictedScanResult): List<Class<out FlowLogic<*>>> {
return scanResult.getClassesWithAnnotation(FlowLogic::class, InitiatedBy::class)
// First group by the initiating flow class in case there are multiple mappings
.groupBy { it.requireAnnotation<InitiatedBy>().value.java }
@ -214,35 +216,35 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
return Modifier.isPublic(modifiers) && !isLocalClass && !isAnonymousClass && (!isMemberClass || Modifier.isStatic(modifiers))
}
private fun findRPCFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> {
private fun findRPCFlows(scanResult: RestrictedScanResult): List<Class<out FlowLogic<*>>> {
return scanResult.getClassesWithAnnotation(FlowLogic::class, StartableByRPC::class).filter { it.isUserInvokable() }
}
private fun findServiceFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> {
private fun findServiceFlows(scanResult: RestrictedScanResult): List<Class<out FlowLogic<*>>> {
return scanResult.getClassesWithAnnotation(FlowLogic::class, StartableByService::class)
}
private fun findSchedulableFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> {
private fun findSchedulableFlows(scanResult: RestrictedScanResult): List<Class<out FlowLogic<*>>> {
return scanResult.getClassesWithAnnotation(FlowLogic::class, SchedulableFlow::class)
}
private fun findContractClassNames(scanResult: ScanResult): List<String> {
return (scanResult.getNamesOfClassesImplementing(Contract::class.java) + scanResult.getNamesOfClassesImplementing(UpgradedContract::class.java)).distinct()
private fun findContractClassNames(scanResult: RestrictedScanResult): List<String> {
return (scanResult.getNamesOfClassesImplementing(Contract::class) + scanResult.getNamesOfClassesImplementing(UpgradedContract::class)).distinct()
}
private fun findPlugins(cordappJarPath: URL): List<SerializationWhitelist> {
return ServiceLoader.load(SerializationWhitelist::class.java, URLClassLoader(arrayOf(cordappJarPath), appClassLoader)).toList().filter {
cordappJarPath == it.javaClass.protectionDomain.codeSource.location
private fun findPlugins(cordappJarPath: RestrictedURL): List<SerializationWhitelist> {
return ServiceLoader.load(SerializationWhitelist::class.java, URLClassLoader(arrayOf(cordappJarPath.url), appClassLoader)).toList().filter {
it.javaClass.protectionDomain.codeSource.location == cordappJarPath.url && it.javaClass.name.startsWith(cordappJarPath.qualifiedNamePrefix)
} + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
}
private fun findCustomSchemas(scanResult: ScanResult): Set<MappedSchema> {
private fun findCustomSchemas(scanResult: RestrictedScanResult): Set<MappedSchema> {
return scanResult.getClassesWithSuperclass(MappedSchema::class).toSet()
}
private fun scanCordapp(cordappJarPath: URL): ScanResult {
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
logger.info("Scanning CorDapp in $cordappJarPath")
return FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath).scan()
return RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix)
}
private class FlowTypeHierarchyComparator(val initiatingFlow: Class<out FlowLogic<*>>) : Comparator<Class<out FlowLogic<*>>> {
@ -269,16 +271,30 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
}
}
private fun <T : Any> ScanResult.getClassesWithSuperclass(type: KClass<T>): List<T> {
return getNamesOfSubclassesOf(type.java)
.mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) }
.map { it.kotlin.objectOrNewInstance() }
/** @param rootPackageName only this package and subpackages may be extracted from [url], or null to allow all packages. */
private class RestrictedURL(val url: URL, rootPackageName: String?) {
val qualifiedNamePrefix = rootPackageName?.let { it + '.' } ?: ""
}
private fun <T : Any> ScanResult.getClassesWithAnnotation(type: KClass<T>, annotation: KClass<out Annotation>): List<Class<out T>> {
return getNamesOfClassesWithAnnotation(annotation.java)
.mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) }
private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) {
fun getNamesOfClassesImplementing(type: KClass<*>): List<String> {
return scanResult.getNamesOfClassesImplementing(type.java)
.filter { it.startsWith(qualifiedNamePrefix) }
}
fun <T : Any> getClassesWithSuperclass(type: KClass<T>): List<T> {
return scanResult.getNamesOfSubclassesOf(type.java)
.filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) }
.map { it.kotlin.objectOrNewInstance() }
}
fun <T : Any> getClassesWithAnnotation(type: KClass<T>, annotation: KClass<out Annotation>): List<Class<out T>> {
return scanResult.getNamesOfClassesWithAnnotation(annotation.java)
.filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) }
}
}
}

View File

@ -6,15 +6,14 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappContext
import net.corda.core.cordapp.CordappProvider
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.SingletonSerializeAsToken
import java.net.URLClassLoader
import java.net.URL
/**
* Cordapp provider and store. For querying CorDapps for their attachment and vice versa.
*/
open class CordappProviderImpl(private val cordappLoader: CordappLoader) : SingletonSerializeAsToken(), CordappProvider {
open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal {
override fun getAppContext(): CordappContext {
// TODO: Use better supported APIs in Java 9
Exception().stackTrace.forEach { stackFrame ->
@ -34,28 +33,19 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader) : Singl
/**
* 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(attachmentStorage: AttachmentStorage): CordappProviderImpl {
cordappAttachments = HashBiMap.create(loadContractsIntoAttachmentStore(attachmentStorage))
return this
}
override val cordapps get() = cordappLoader.cordapps
private val cordappAttachments = HashBiMap.create(loadContractsIntoAttachmentStore(attachmentStorage))
/**
* 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)
fun getCordappAttachmentId(cordapp: Cordapp): SecureHash? = cordappAttachments.inverse().get(cordapp.jarPath)
private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, Cordapp> {
val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }
val attachmentIds = cordappsWithAttachments.map { it.jarPath.openStream().use { attachmentStorage.importAttachment(it) } }
private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, URL> {
val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }.map { it.jarPath }
val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importAttachment(it) } }
return attachmentIds.zip(cordappsWithAttachments).toMap()
}

View File

@ -0,0 +1,8 @@
package net.corda.node.internal.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappProvider
interface CordappProviderInternal : CordappProvider {
val cordapps: List<Cordapp>
}

View File

@ -30,11 +30,5 @@ interface SchemaService {
* or via custom logic in this service.
*/
fun generateMappedObject(state: ContractState, schema: MappedSchema): PersistentState
/**
* Registration mechanism to add custom contract schemas that extend the [MappedSchema] class.
*/
fun registerCustomSchemas(customSchemas: Set<MappedSchema>)
}
//DOCEND SchemaService

View File

@ -21,6 +21,7 @@ import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.loggerFor
import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
@ -89,7 +90,7 @@ interface ServiceHubInternal : ServiceHub {
val networkService: MessagingService
val database: CordaPersistence
val configuration: NodeConfiguration
override val cordappProvider: CordappProviderInternal
override fun recordTransactions(notifyVault: Boolean, txs: Iterable<SignedTransaction>) {
require(txs.any()) { "No transactions passed in for recording" }
val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) }

View File

@ -33,25 +33,13 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab
private val sessionFactories = ConcurrentHashMap<Set<MappedSchema>, SessionFactory>()
private val transactionIsolationLevel = parserTransactionIsolationLevel(databaseProperties.getProperty("transactionIsolationLevel") ?: "")
init {
logger.info("Init HibernateConfiguration for schemas: ${schemaService.schemaOptions.keys}")
sessionFactoryForRegisteredSchemas()
val sessionFactoryForRegisteredSchemas = schemaService.schemaOptions.keys.let {
logger.info("Init HibernateConfiguration for schemas: $it")
sessionFactoryForSchemas(it)
}
fun sessionFactoryForRegisteredSchemas(): SessionFactory {
return sessionFactoryForSchemas(*schemaService.schemaOptions.keys.toTypedArray())
}
fun sessionFactoryForSchema(schema: MappedSchema): SessionFactory {
return sessionFactoryForSchemas(schema)
}
//vararg to set conversions left to preserve method signature for now
fun sessionFactoryForSchemas(vararg schemas: MappedSchema): SessionFactory {
val schemaSet: Set<MappedSchema> = schemas.toSet()
return sessionFactories.computeIfAbsent(schemaSet, { makeSessionFactoryForSchemas(schemaSet) })
}
/** @param key must be immutable, not just read-only. */
fun sessionFactoryForSchemas(key: Set<MappedSchema>) = sessionFactories.computeIfAbsent(key, { makeSessionFactoryForSchemas(key) })
private fun makeSessionFactoryForSchemas(schemas: Set<MappedSchema>): SessionFactory {
logger.info("Creating session factory for schemas: $schemas")

View File

@ -38,7 +38,7 @@ class HibernateObserver(vaultUpdates: Observable<Vault.Update<ContractState>>, v
}
fun persistStateWithSchema(state: ContractState, stateRef: StateRef, schema: MappedSchema) {
val sessionFactory = config.sessionFactoryForSchema(schema)
val sessionFactory = config.sessionFactoryForSchemas(setOf(schema))
val session = sessionFactory.withOptions().
connection(DatabaseTransactionManager.current().connection).
flushMode(FlushMode.MANUAL).

View File

@ -9,6 +9,7 @@ import net.corda.core.schemas.NodeInfoSchemaV1
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.services.api.SchemaService
import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.identity.PersistentIdentityService
@ -27,13 +28,13 @@ import net.corda.node.services.vault.VaultSchemaV1
/**
* Most basic implementation of [SchemaService].
*
* @param cordappLoader if not null, custom schemas will be extracted from its cordapps.
* TODO: support loading schema options from node configuration.
* TODO: support configuring what schemas are to be selected for persistence.
* TODO: support plugins for schema version upgrading or custom mapping not supported by original [QueryableState].
* TODO: create whitelisted tables when a CorDapp is first installed
*/
class NodeSchemaService(customSchemas: Set<MappedSchema> = emptySet()) : SchemaService, SingletonSerializeAsToken() {
class NodeSchemaService(cordappLoader: CordappLoader?) : SchemaService, SingletonSerializeAsToken() {
// Entities for compulsory services
object NodeServices
@ -67,9 +68,12 @@ class NodeSchemaService(customSchemas: Set<MappedSchema> = emptySet()) : SchemaS
Pair(NodeInfoSchemaV1, SchemaService.SchemaOptions()),
Pair(NodeServicesV1, SchemaService.SchemaOptions()))
override var schemaOptions: Map<MappedSchema, SchemaService.SchemaOptions> = requiredSchemas.plus(customSchemas.map { mappedSchema ->
Pair(mappedSchema, SchemaService.SchemaOptions())
})
override val schemaOptions: Map<MappedSchema, SchemaService.SchemaOptions> = if (cordappLoader == null) {
requiredSchemas
} else {
val customSchemas = cordappLoader.cordapps.flatMap { it.customSchemas }.toSet()
requiredSchemas.plus(customSchemas.map { mappedSchema -> Pair(mappedSchema, SchemaService.SchemaOptions()) })
}
// Currently returns all schemas supported by the state, with no filtering or enrichment.
override fun selectSchemas(state: ContractState): Iterable<MappedSchema> {
@ -92,10 +96,4 @@ class NodeSchemaService(customSchemas: Set<MappedSchema> = emptySet()) : SchemaS
return VaultSchemaV1.VaultFungibleStates(state.owner, state.amount.quantity, state.amount.token.issuer.party, state.amount.token.issuer.reference, state.participants)
return (state as QueryableState).generateMappedObject(schema)
}
override fun registerCustomSchemas(_customSchemas: Set<MappedSchema>) {
schemaOptions = schemaOptions.plus(_customSchemas.map { mappedSchema ->
Pair(mappedSchema, SchemaService.SchemaOptions())
})
}
}

View File

@ -59,7 +59,7 @@ private fun CriteriaBuilder.executeUpdate(session: Session, configure: Root<*>.(
* TODO: keep an audit trail with time stamps of previously unconsumed states "as of" a particular point in time.
* TODO: have transaction storage do some caching.
*/
class NodeVaultService(private val clock: Clock, private val keyManagementService: KeyManagementService, private val stateLoader: StateLoader, private val hibernateConfig: HibernateConfiguration) : SingletonSerializeAsToken(), VaultServiceInternal {
class NodeVaultService(private val clock: Clock, private val keyManagementService: KeyManagementService, private val stateLoader: StateLoader, hibernateConfig: HibernateConfiguration) : SingletonSerializeAsToken(), VaultServiceInternal {
private companion object {
val log = loggerFor<NodeVaultService>()
@ -377,9 +377,8 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic
return keysToCheck.any { it in myKeys }
}
private var sessionFactory = hibernateConfig.sessionFactoryForRegisteredSchemas()
private var criteriaBuilder = sessionFactory.criteriaBuilder
private val sessionFactory = hibernateConfig.sessionFactoryForRegisteredSchemas
private val criteriaBuilder = sessionFactory.criteriaBuilder
/**
* Maintain a list of contract state interfaces to concrete types stored in the vault
* for usage in generic queries of type queryBy<LinearState> or queryBy<FungibleState<*>>
@ -406,11 +405,6 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic
@Throws(VaultQueryException::class)
override fun <T : ContractState> _queryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class<out T>): Vault.Page<T> {
log.info("Vault Query for contract type: $contractStateType, criteria: $criteria, pagination: $paging, sorting: $sorting")
// refresh to include any schemas registered after initial VQ service initialisation
sessionFactory = hibernateConfig.sessionFactoryForRegisteredSchemas()
criteriaBuilder = sessionFactory.criteriaBuilder
// calculate total results where a page specification has been defined
var totalStates = -1L
if (!paging.isDefault) {

View File

@ -6,8 +6,6 @@ import net.corda.core.node.services.IdentityService
import net.corda.node.services.api.SchemaService
import net.corda.node.services.persistence.HibernateConfiguration
import net.corda.node.services.schema.NodeSchemaService
import org.hibernate.SessionFactory
import rx.Observable
import rx.Subscriber
import rx.subjects.UnicastSubject
@ -32,12 +30,7 @@ class CordaPersistence(var dataSource: HikariDataSource, private val schemaServi
HibernateConfiguration(schemaService, databaseProperties, createIdentityService)
}
}
val entityManagerFactory: SessionFactory by lazy {
transaction {
hibernateConfig.sessionFactoryForRegisteredSchemas()
}
}
val entityManagerFactory get() = hibernateConfig.sessionFactoryForRegisteredSchemas
companion object {
fun connect(dataSource: HikariDataSource, schemaService: SchemaService, createIdentityService: () -> IdentityService, databaseProperties: Properties): CordaPersistence {
@ -103,7 +96,7 @@ class CordaPersistence(var dataSource: HikariDataSource, private val schemaServi
}
}
fun configureDatabase(dataSourceProperties: Properties, databaseProperties: Properties?, schemaService: SchemaService = NodeSchemaService(), createIdentityService: () -> IdentityService): CordaPersistence {
fun configureDatabase(dataSourceProperties: Properties, databaseProperties: Properties?, createIdentityService: () -> IdentityService, schemaService: SchemaService = NodeSchemaService(null)): CordaPersistence {
val config = HikariConfig(dataSourceProperties)
val dataSource = HikariDataSource(config)
val persistence = CordaPersistence.connect(dataSource, schemaService, createIdentityService, databaseProperties ?: Properties())

View File

@ -8,7 +8,6 @@ import net.corda.core.messaging.*;
import net.corda.core.node.services.*;
import net.corda.core.node.services.vault.*;
import net.corda.core.node.services.vault.QueryCriteria.*;
import net.corda.core.schemas.*;
import net.corda.core.utilities.*;
import net.corda.finance.contracts.*;
import net.corda.finance.contracts.asset.*;
@ -43,14 +42,13 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@Before
public void setUp() {
List<String> cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset");
List<String> cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName());
ArrayList<KeyPair> keys = new ArrayList<>();
keys.add(getMEGA_CORP_KEY());
keys.add(getDUMMY_NOTARY_KEY());
Set<MappedSchema> requiredSchemas = Collections.singleton(CashSchemaV1.INSTANCE);
IdentityService identitySvc = makeTestIdentityService();
@SuppressWarnings("unchecked")
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(requiredSchemas, keys, () -> identitySvc, cordappPackages);
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(keys, () -> identitySvc, cordappPackages);
issuerServices = new MockServices(cordappPackages, getDUMMY_CASH_ISSUER_KEY(), getBOC_KEY());
database = databaseAndServices.getFirst();
services = databaseAndServices.getSecond();

View File

@ -26,7 +26,7 @@ import kotlin.test.assertEquals
class InteractiveShellTest {
@Before
fun setup() {
InteractiveShell.database = configureDatabase(MockServices.makeTestDataSourceProperties(), MockServices.makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService)
InteractiveShell.database = configureDatabase(MockServices.makeTestDataSourceProperties(), MockServices.makeTestDatabaseProperties(), ::makeTestIdentityService)
}
@After

View File

@ -1,5 +1,6 @@
package net.corda.node.internal.cordapp
import com.nhaarman.mockito_kotlin.mock
import net.corda.core.node.services.AttachmentStorage
import net.corda.testing.node.MockAttachmentStorage
import org.junit.Assert
@ -22,9 +23,7 @@ class CordappProviderImplTests {
@Test
fun `isolated jar is loaded into the attachment store`() {
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val provider = CordappProviderImpl(loader)
provider.start(attachmentStore)
val provider = CordappProviderImpl(loader, attachmentStore)
val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first())
Assert.assertNotNull(maybeAttachmentId)
@ -34,17 +33,14 @@ class CordappProviderImplTests {
@Test
fun `empty jar is not loaded into the attachment store`() {
val loader = CordappLoader.createDevMode(listOf(emptyJAR))
val provider = CordappProviderImpl(loader)
provider.start(attachmentStore)
val provider = CordappProviderImpl(loader, attachmentStore)
Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first()))
}
@Test
fun `test that we find a cordapp class that is loaded into the store`() {
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val provider = CordappProviderImpl(loader)
val provider = CordappProviderImpl(loader, mock())
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
val expected = provider.cordapps.first()
@ -57,10 +53,8 @@ class CordappProviderImplTests {
@Test
fun `test that we find an attachment for a cordapp contrat class`() {
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
val provider = CordappProviderImpl(loader)
val provider = CordappProviderImpl(loader, attachmentStore)
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
provider.start(attachmentStore)
val expected = provider.getAppContext(provider.cordapps.first()).attachmentId
val actual = provider.getContractAttachmentID(className)

View File

@ -78,7 +78,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
calls = 0
val dataSourceProps = makeTestDataSourceProperties()
val databaseProperties = makeTestDatabaseProperties()
database = configureDatabase(dataSourceProps, databaseProperties, createIdentityService = ::makeTestIdentityService)
database = configureDatabase(dataSourceProps, databaseProperties, ::makeTestIdentityService)
val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT)
val kms = MockKeyManagementService(identityService, ALICE_KEY)
@ -97,7 +97,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
network = mockMessagingService), TestReference {
override val vaultService: VaultServiceInternal = NodeVaultService(testClock, kms, stateLoader, database.hibernateConfig)
override val testReference = this@NodeSchedulerServiceTest
override val cordappProvider = CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts"))).start(attachments)
override val cordappProvider = CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), attachments)
}
smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1)
scheduler = NodeSchedulerService(services, schedulerGatedExecutor, serverThread = smmExecutor)

View File

@ -71,7 +71,7 @@ class ArtemisMessagingTests : TestDependencyInjectionBase() {
baseDirectory = baseDirectory,
myLegalName = ALICE.name)
LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService)
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
networkMapRegistrationFuture = doneFuture(Unit)
networkMapCache = PersistentNetworkMapCache(serviceHub = object : MockServiceHubInternal(database, config) {})
}

View File

@ -33,7 +33,7 @@ class DBCheckpointStorageTests : TestDependencyInjectionBase() {
@Before
fun setUp() {
LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService)
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
newCheckpointStorage()
}

View File

@ -38,7 +38,7 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() {
fun setUp() {
LogHelper.setLevel(PersistentUniquenessProvider::class)
val dataSourceProps = makeTestDataSourceProperties()
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(), NodeSchemaService(), ::makeTestIdentityService)
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(), ::makeTestIdentityService)
database.transaction {
services = object : MockServices(BOB_KEY) {

View File

@ -9,6 +9,7 @@ import net.corda.core.utilities.toBase58String
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService
import net.corda.core.schemas.CommonSchemaV1
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.deserialize
@ -25,7 +26,6 @@ import net.corda.finance.schemas.SampleCashSchemaV2
import net.corda.finance.schemas.SampleCashSchemaV3
import net.corda.finance.utils.sumCash
import net.corda.node.services.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.vault.VaultSchemaV1
import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase
@ -77,7 +77,7 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_KEY, BOB_KEY, BOC_KEY)
val dataSourceProps = makeTestDataSourceProperties()
val defaultDatabaseProperties = makeTestDatabaseProperties()
database = configureDatabase(dataSourceProps, defaultDatabaseProperties, NodeSchemaService(), ::makeTestIdentityService)
database = configureDatabase(dataSourceProps, defaultDatabaseProperties, ::makeTestIdentityService)
database.transaction {
hibernateConfig = database.hibernateConfig
services = object : MockServices(cordappPackages, BOB_KEY, BOC_KEY, DUMMY_NOTARY_KEY) {
@ -95,13 +95,13 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
hibernatePersister = services.hibernatePersister
}
setUpDb()
val customSchemas = setOf(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3)
sessionFactory = hibernateConfig.sessionFactoryForSchemas(*customSchemas.toTypedArray())
sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3)
entityManager = sessionFactory.createEntityManager()
criteriaBuilder = sessionFactory.criteriaBuilder
}
private fun sessionFactoryForSchemas(vararg schemas: MappedSchema) = hibernateConfig.sessionFactoryForSchemas(schemas.toSet())
@After
fun cleanUp() {
database.close()
@ -536,8 +536,7 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
services.fillWithSomeTestLinearStates(2)
}
val sessionFactory = hibernateConfig.sessionFactoryForSchemas(VaultSchemaV1, DummyLinearStateSchemaV1)
val sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, DummyLinearStateSchemaV1)
val criteriaBuilder = sessionFactory.criteriaBuilder
val entityManager = sessionFactory.createEntityManager()
@ -568,8 +567,7 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
services.fillWithSomeTestLinearStates(2)
}
val sessionFactory = hibernateConfig.sessionFactoryForSchemas(VaultSchemaV1, DummyLinearStateSchemaV2)
val sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, DummyLinearStateSchemaV2)
val criteriaBuilder = sessionFactory.criteriaBuilder
val entityManager = sessionFactory.createEntityManager()
@ -635,8 +633,7 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3)
}
}
val sessionFactory = hibernateConfig.sessionFactoryForSchemas(VaultSchemaV1, CommonSchemaV1, SampleCashSchemaV3)
val sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, CommonSchemaV1, SampleCashSchemaV3)
val criteriaBuilder = sessionFactory.criteriaBuilder
val entityManager = sessionFactory.createEntityManager()
@ -764,8 +761,7 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
services.fillWithSomeTestLinearStates(2, externalId = "222")
services.fillWithSomeTestLinearStates(3, externalId = "333")
}
val sessionFactory = hibernateConfig.sessionFactoryForSchemas(VaultSchemaV1, DummyLinearStateSchemaV2)
val sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, DummyLinearStateSchemaV2)
val criteriaBuilder = sessionFactory.criteriaBuilder
val entityManager = sessionFactory.createEntityManager()
@ -817,8 +813,7 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
services.fillWithSomeTestLinearStates(2, externalId = "222")
services.fillWithSomeTestLinearStates(3, externalId = "333")
}
val sessionFactory = hibernateConfig.sessionFactoryForSchemas(VaultSchemaV1, DummyLinearStateSchemaV1)
val sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, DummyLinearStateSchemaV1)
val criteriaBuilder = sessionFactory.criteriaBuilder
val entityManager = sessionFactory.createEntityManager()

View File

@ -40,7 +40,7 @@ class NodeAttachmentStorageTest {
LogHelper.setLevel(PersistentUniquenessProvider::class)
val dataSourceProperties = makeTestDataSourceProperties()
database = configureDatabase(dataSourceProperties, makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService)
database = configureDatabase(dataSourceProperties, makeTestDatabaseProperties(), ::makeTestIdentityService)
fs = Jimfs.newFileSystem(Configuration.unix())
}

View File

@ -57,8 +57,6 @@ class HibernateObserverTests {
val testSchema = TestSchema
val rawUpdatesPublisher = PublishSubject.create<Vault.Update<ContractState>>()
val schemaService = object : SchemaService {
override fun registerCustomSchemas(customSchemas: Set<MappedSchema>) {}
override val schemaOptions: Map<MappedSchema, SchemaService.SchemaOptions> = emptyMap()
override fun selectSchemas(state: ContractState): Iterable<MappedSchema> = setOf(testSchema)
@ -70,7 +68,7 @@ class HibernateObserverTests {
return parent
}
}
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), schemaService, createIdentityService = ::makeTestIdentityService)
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService, schemaService)
@Suppress("UNUSED_VARIABLE")
val observer = HibernateObserver(rawUpdatesPublisher, database.hibernateConfig)
database.transaction {

View File

@ -3,11 +3,13 @@ package net.corda.node.services.schema
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.internal.packageName
import net.corda.core.messaging.startFlow
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.api.ServiceHubInternal
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.node.MockNetwork
import net.corda.testing.schemas.DummyLinearStateSchemaV1
@ -15,6 +17,7 @@ import org.hibernate.annotations.Cascade
import org.hibernate.annotations.CascadeType
import org.junit.Test
import javax.persistence.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class NodeSchemaServiceTest {
@ -23,11 +26,9 @@ class NodeSchemaServiceTest {
*/
@Test
fun `registering custom schemas for testing with MockNode`() {
val mockNet = MockNetwork()
val mockNet = MockNetwork(cordappPackages = listOf(DummyLinearStateSchemaV1::class.packageName))
val mockNode = mockNet.createNode()
mockNet.runNetwork()
mockNode.internals.registerCustomSchemas(setOf(DummyLinearStateSchemaV1))
val schemaService = mockNode.services.schemaService
assertTrue(schemaService.schemaOptions.containsKey(DummyLinearStateSchemaV1))
@ -50,6 +51,16 @@ class NodeSchemaServiceTest {
}
}
@Test
fun `custom schemas are loaded eagerly`() {
val expected = setOf("PARENTS", "CHILDREN")
assertEquals<Set<*>>(expected, driver {
(startNode(startInSameProcess = true).getOrThrow() as NodeHandle.InProcess).node.database.transaction {
session.createNativeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES").list()
}
}.toMutableSet().apply { retainAll(expected) })
}
@StartableByRPC
class MappedSchemasFlow : FlowLogic<List<String>>() {
@Suspendable

View File

@ -86,7 +86,7 @@ class DistributedImmutableMapTests : TestDependencyInjectionBase() {
private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> {
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
val address = Address(myAddress.host, myAddress.port)
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties("serverNameTablePrefix", "PORT_${myAddress.port}_"), createIdentityService = ::makeTestIdentityService)
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties("serverNameTablePrefix", "PORT_${myAddress.port}_"), ::makeTestIdentityService)
databases.add(database)
val stateMachineFactory = { DistributedImmutableMap(database, RaftUniquenessProvider.Companion::createMap) }

View File

@ -23,7 +23,7 @@ class PersistentUniquenessProviderTests : TestDependencyInjectionBase() {
@Before
fun setUp() {
LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService)
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
}
@After

View File

@ -9,6 +9,7 @@ import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.internal.packageName
import net.corda.core.node.services.*
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.QueryCriteria.*
@ -46,7 +47,7 @@ import kotlin.test.assertTrue
class NodeVaultServiceTest : TestDependencyInjectionBase() {
companion object {
private val cordappPackages = listOf("net.corda.finance.contracts.asset")
private val cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)
}
lateinit var services: MockServices
@ -58,7 +59,6 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
fun setUp() {
LogHelper.setLevel(NodeVaultService::class)
val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(BOC_KEY, DUMMY_CASH_ISSUER_KEY),
customSchemas = setOf(CashSchemaV1),
cordappPackages = cordappPackages)
database = databaseAndServices.first
services = databaseAndServices.second

View File

@ -6,6 +6,7 @@ import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.packageName
import net.corda.core.node.services.*
import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.QueryCriteria.*
@ -45,10 +46,9 @@ import java.time.temporal.ChronoUnit
import java.util.*
class VaultQueryTests : TestDependencyInjectionBase() {
companion object {
private val cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts")
}
private val cordappPackages = setOf(
"net.corda.testing.contracts", "net.corda.finance.contracts",
CashSchemaV1::class.packageName, CommercialPaperSchemaV1::class.packageName, DummyLinearStateSchemaV1::class.packageName).toMutableList()
private lateinit var services: MockServices
private lateinit var notaryServices: MockServices
private val vaultService: VaultService get() = services.vaultService
@ -67,7 +67,6 @@ class VaultQueryTests : TestDependencyInjectionBase() {
identitySvc.verifyAndRegisterIdentity(BOC_IDENTITY)
val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(MEGA_CORP_KEY, DUMMY_NOTARY_KEY),
createIdentityService = { identitySvc },
customSchemas = setOf(CashSchemaV1, CommercialPaperSchemaV1, DummyLinearStateSchemaV1),
cordappPackages = cordappPackages)
database = databaseAndServices.first
services = databaseAndServices.second
@ -85,8 +84,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
@Ignore
@Test
fun createPersistentTestDb() {
val database = configureDatabase(makePersistentDataSourceProperties(), makeTestDatabaseProperties(), createIdentityService = { identitySvc })
val database = configureDatabase(makePersistentDataSourceProperties(), makeTestDatabaseProperties(), { identitySvc })
setUpDb(database, 5000)
database.close()
@ -1753,6 +1751,9 @@ class VaultQueryTests : TestDependencyInjectionBase() {
@Test
fun `query attempting to use unregistered schema`() {
tearDown()
cordappPackages -= SampleCashSchemaV3::class.packageName
setUp()
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L))
services.fillWithSomeTestCash(100.POUNDS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L))

View File

@ -4,6 +4,7 @@ import net.corda.core.contracts.ContractState
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.AnonymousParty
import net.corda.core.internal.packageName
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService
import net.corda.core.node.services.queryBy
@ -35,7 +36,7 @@ import kotlin.test.assertEquals
class VaultWithCashTest : TestDependencyInjectionBase() {
companion object {
private val cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset")
private val cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)
}
lateinit var services: MockServices
@ -48,7 +49,6 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
fun setUp() {
LogHelper.setLevel(VaultWithCashTest::class)
val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(DUMMY_CASH_ISSUER_KEY, DUMMY_NOTARY_KEY),
customSchemas = setOf(CashSchemaV1),
cordappPackages = cordappPackages)
database = databaseAndServices.first
services = databaseAndServices.second

View File

@ -21,7 +21,7 @@ class ObservablesTests {
val toBeClosed = mutableListOf<Closeable>()
fun createDatabase(): CordaPersistence {
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService)
val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
toBeClosed += database
return database
}

View File

@ -62,7 +62,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() {
@Before
fun setUp() {
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), createIdentityService = ::makeTestIdentityService)
database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService)
database.transaction {
oracle = createMockCordaService(services, NodeInterestRates::Oracle)
oracle.knownFixes = TEST_DATA

View File

@ -12,7 +12,6 @@ import net.corda.node.internal.StartedNode
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.nodeapi.internal.ServiceType
import net.corda.testing.*
import net.corda.testing.node.InMemoryMessagingNetwork
import net.corda.testing.node.MockNetwork
@ -270,9 +269,3 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
mockNet.stopNodes()
}
}
/**
* Helper function for verifying that a service info contains the given type of advertised service. For non-simulation cases
* this is a configuration matter rather than implementation.
*/
fun Iterable<ServiceInfo>.containsType(type: ServiceType) = any { it.type == type }

View File

@ -1,6 +1,7 @@
package net.corda.traderdemo
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.internal.packageName
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis
import net.corda.finance.DOLLARS
@ -20,7 +21,9 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.util.concurrent.Executors
class TraderDemoTest : NodeBasedTest(listOf("net.corda.finance.contracts.asset", "net.corda.finance.contracts")) {
class TraderDemoTest : NodeBasedTest(listOf(
"net.corda.finance.contracts.asset", "net.corda.finance.contracts",
CashSchemaV1::class.packageName, CommercialPaperSchemaV1::class.packageName)) {
@Test
fun `runs trader demo`() {
val demoUser = User("demo", "demo", setOf(startFlowPermission<SellerFlow>()))
@ -35,9 +38,6 @@ class TraderDemoTest : NodeBasedTest(listOf("net.corda.finance.contracts.asset",
val (nodeA, nodeB, bankNode) = listOf(nodeAFuture, nodeBFuture, bankNodeFuture, notaryFuture).map { it.getOrThrow() }
nodeA.internals.registerInitiatedFlow(BuyerFlow::class.java)
nodeA.internals.registerCustomSchemas(setOf(CashSchemaV1))
nodeB.internals.registerCustomSchemas(setOf(CashSchemaV1, CommercialPaperSchemaV1))
val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map {
val client = CordaRPCClient(it.internals.configuration.rpcAddress!!)
client.start(demoUser.username, demoUser.password).proxy

View File

@ -1,7 +1,6 @@
package net.corda.node.testing
import com.codahale.metrics.MetricRegistry
import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
@ -13,11 +12,11 @@ import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.StateLoaderImpl
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.serialization.NodeClock
import net.corda.node.services.api.*
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
@ -46,10 +45,9 @@ open class MockServiceHubInternal(
val mapCache: NetworkMapCacheInternal? = null,
val scheduler: SchedulerService? = null,
val overrideClock: Clock? = NodeClock(),
val schemas: SchemaService? = NodeSchemaService(),
val customContractUpgradeService: ContractUpgradeService? = null,
val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2),
override val cordappProvider: CordappProvider = CordappProviderImpl(CordappLoader.createDefault(Paths.get("."))).start(attachments),
override val cordappProvider: CordappProviderInternal = CordappProviderImpl(CordappLoader.createDefault(Paths.get(".")), attachments),
protected val stateLoader: StateLoaderImpl = StateLoaderImpl(validatedTransactions)
) : ServiceHubInternal, StateLoader by stateLoader {
override val transactionVerifierService: TransactionVerifierService
@ -75,8 +73,7 @@ open class MockServiceHubInternal(
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
override val rpcFlows: List<Class<out FlowLogic<*>>>
get() = throw UnsupportedOperationException()
override val schemaService: SchemaService
get() = schemas ?: throw UnsupportedOperationException()
override val schemaService get() = throw UnsupportedOperationException()
override val auditService: AuditService = DummyAuditService()
lateinit var smm: StateMachineManager

View File

@ -14,7 +14,6 @@ import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.node.StateLoader
import net.corda.core.node.services.*
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction
@ -51,7 +50,7 @@ import java.util.*
* building chains of transactions and verifying them. It isn't sufficient for testing flows however.
*/
open class MockServices(
cordappPackages: List<String>,
cordappLoader: CordappLoader,
override val validatedTransactions: WritableTransactionStorage,
protected val stateLoader: StateLoaderImpl = StateLoaderImpl(validatedTransactions),
vararg val keys: KeyPair
@ -101,24 +100,22 @@ open class MockServices(
/**
* Makes database and mock services appropriate for unit tests.
*
* @param customSchemas a set of schemas being used by [NodeSchemaService]
* @param keys a lis of [KeyPair] instances to be used by [MockServices]. Defualts to [MEGA_CORP_KEY]
* @param keys a list of [KeyPair] instances to be used by [MockServices]. Defualts to [MEGA_CORP_KEY]
* @param createIdentityService a lambda function returning an instance of [IdentityService]. Defauts to [InMemoryIdentityService].
*
* @return a pair where the first element is the instance of [CordaPersistence] and the second is [MockServices].
*/
@JvmStatic
fun makeTestDatabaseAndMockServices(customSchemas: Set<MappedSchema> = emptySet(),
keys: List<KeyPair> = listOf(MEGA_CORP_KEY),
fun makeTestDatabaseAndMockServices(keys: List<KeyPair> = listOf(MEGA_CORP_KEY),
createIdentityService: () -> IdentityService = { makeTestIdentityService() },
cordappPackages: List<String> = emptyList()): Pair<CordaPersistence, MockServices> {
val cordappLoader = CordappLoader.createWithTestPackages(cordappPackages)
val dataSourceProps = makeTestDataSourceProperties()
val databaseProperties = makeTestDatabaseProperties()
val identityServiceRef: IdentityService by lazy { createIdentityService() }
val database = configureDatabase(dataSourceProps, databaseProperties, NodeSchemaService(customSchemas), { identityServiceRef })
val database = configureDatabase(dataSourceProps, databaseProperties, { identityServiceRef }, NodeSchemaService(cordappLoader))
val mockService = database.transaction {
object : MockServices(cordappPackages, *(keys.toTypedArray())) {
object : MockServices(cordappLoader, *(keys.toTypedArray())) {
override val identityService: IdentityService = database.transaction { identityServiceRef }
override val vaultService = makeVaultService(database.hibernateConfig)
@ -137,7 +134,8 @@ open class MockServices(
}
}
constructor(cordappPackages: List<String>, vararg keys: KeyPair) : this(cordappPackages, MockTransactionStorage(), keys = *keys)
private constructor(cordappLoader: CordappLoader, vararg keys: KeyPair) : this(cordappLoader, MockTransactionStorage(), keys = *keys)
constructor(cordappPackages: List<String>, vararg keys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), keys = *keys)
constructor(vararg keys: KeyPair) : this(emptyList(), *keys)
constructor() : this(generateKeyPair())
@ -167,11 +165,11 @@ open class MockServices(
return NodeInfo(emptyList(), listOf(identity), 1, serial = 1L)
}
override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2)
val mockCordappProvider = MockCordappProvider(CordappLoader.createWithTestPackages(cordappPackages)).start(attachments) as MockCordappProvider
val mockCordappProvider = MockCordappProvider(cordappLoader, attachments)
override val cordappProvider: CordappProvider get() = mockCordappProvider
lateinit var hibernatePersister: HibernateObserver
fun makeVaultService(hibernateConfig: HibernateConfiguration = HibernateConfiguration(NodeSchemaService(), makeTestDatabaseProperties(), { identityService })): VaultServiceInternal {
fun makeVaultService(hibernateConfig: HibernateConfiguration): VaultServiceInternal {
val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, stateLoader, hibernateConfig)
hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig)
return vaultService

View File

@ -37,7 +37,7 @@ class SimpleNode(val config: NodeConfiguration, val address: NetworkHostAndPort
val monitoringService = MonitoringService(MetricRegistry())
val identity: KeyPair = generateKeyPair()
val identityService: IdentityService = InMemoryIdentityService(trustRoot = trustRoot)
val database: CordaPersistence = configureDatabase(config.dataSourceProperties, config.database, NodeSchemaService(), { InMemoryIdentityService(trustRoot = trustRoot) })
val database: CordaPersistence = configureDatabase(config.dataSourceProperties, config.database, { InMemoryIdentityService(trustRoot = trustRoot) })
val keyService: KeyManagementService = E2ETestKeyManagementService(identityService, setOf(identity))
val executor = ServiceAffinityExecutor(config.myLegalName.organisation, 1)
// TODO: We should have a dummy service hub rather than change behaviour in tests

View File

@ -1,5 +1,6 @@
package net.corda.testing
import net.corda.core.internal.packageName
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.core.LoggerContext
@ -25,7 +26,7 @@ object LogHelper {
}
}
fun setLevel(vararg classes: KClass<*>) = setLevel(*classes.map { "+" + it.java.`package`.name }.toTypedArray())
fun setLevel(vararg classes: KClass<*>) = setLevel(*classes.map { "+" + it.packageName }.toTypedArray())
/** Removes custom configuration for the specified logger names */
fun reset(vararg names: String) {
@ -35,7 +36,7 @@ object LogHelper {
loggerContext.updateLoggers(config)
}
fun reset(vararg classes: KClass<*>) = reset(*classes.map { it.java.`package`.name }.toTypedArray())
fun reset(vararg classes: KClass<*>) = reset(*classes.map { it.packageName }.toTypedArray())
/** Updates logging level for the specified Log4j logger name */
private fun setLevel(name: String, level: Level) {

View File

@ -4,12 +4,13 @@ import net.corda.core.contracts.ContractClassName
import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import java.nio.file.Paths
import java.util.*
class MockCordappProvider(cordappLoader: CordappLoader) : CordappProviderImpl(cordappLoader) {
class MockCordappProvider(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : CordappProviderImpl(cordappLoader, attachmentStorage) {
val cordappRegistry = mutableListOf<Pair<Cordapp, AttachmentId>>()
fun addMockCordapp(contractClassName: ContractClassName, attachments: MockAttachmentStorage) {