From aced03df5400e05f0bb50833cae0dbe9865c1b5c Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Thu, 11 Oct 2018 19:50:26 +0100 Subject: [PATCH 1/8] CORDA-1274: Migrated usage of FastClasspathScanner to ClassGraph (#4060) FastClasspathScanner was renamed to ClassGraph for the version 4 release --- build.gradle | 2 +- djvm/build.gradle | 4 +- .../net/corda/djvm/tools/cli/Utilities.kt | 18 ++++----- .../net/corda/djvm/utilities/Discovery.kt | 26 ++++++------- experimental/behave/build.gradle | 4 +- .../corda/behave/scenarios/StepsContainer.kt | 16 ++++---- node-api/build.gradle | 4 +- .../nodeapi/internal/ContractsScanning.kt | 16 ++++---- .../cordapp/JarScanningCordappLoader.kt | 38 ++++++++++++------- serialization/build.gradle | 4 +- .../internal/amqp/AMQPSerializationScheme.kt | 18 ++++++--- .../node/internal/TestCordappsUtils.kt | 11 ++++-- 12 files changed, 89 insertions(+), 72 deletions(-) diff --git a/build.gradle b/build.gradle index f3cc3c97ca..500dc25229 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ buildscript { ext.commons_cli_version = '1.4' ext.protonj_version = '0.27.1' // This is now aligned with the Artemis version, but retaining in case we ever need to diverge again for a bug fix. ext.snappy_version = '0.4' - ext.fast_classpath_scanner_version = '2.12.3' + ext.class_graph_version = '4.2.12' ext.jcabi_manifests_version = '1.1' ext.picocli_version = '3.5.2' diff --git a/djvm/build.gradle b/djvm/build.gradle index eb41df17cc..d40a4d5523 100644 --- a/djvm/build.gradle +++ b/djvm/build.gradle @@ -36,8 +36,8 @@ dependencies { compile "org.ow2.asm:asm-tree:$asm_version" compile "org.ow2.asm:asm-commons:$asm_version" - // Classpath scanner - shadow "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" + // ClassGraph: classpath scanning + shadow "io.github.classgraph:classgraph:$class_graph_version" // Test utilities testCompile "junit:junit:$junit_version" diff --git a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt index 66d5d4d918..adc88a1423 100644 --- a/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt +++ b/djvm/cli/src/main/kotlin/net/corda/djvm/tools/cli/Utilities.kt @@ -1,8 +1,9 @@ @file:JvmName("Utilities") package net.corda.djvm.tools.cli -import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner -import java.lang.reflect.Modifier +import io.github.classgraph.ClassGraph +import java.lang.reflect.Modifier.isAbstract +import java.lang.reflect.Modifier.isStatic import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -92,13 +93,10 @@ val userClassPath: String = System.getProperty("java.class.path") * Get a reference of each concrete class that implements interface or class [T]. */ inline fun find(scanSpec: String = "net/corda/djvm"): List> { - val references = mutableListOf>() - FastClasspathScanner(scanSpec) - .matchClassesImplementing(T::class.java) { clazz -> - if (!Modifier.isAbstract(clazz.modifiers) && !Modifier.isStatic(clazz.modifiers)) { - references.add(clazz) - } - } + return ClassGraph() + .whitelistPaths(scanSpec) + .enableAllInfo() .scan() - return references + .use { it.getClassesImplementing(T::class.java.name).loadClasses(T::class.java) } + .filter { !isAbstract(it.modifiers) && !isStatic(it.modifiers) } } diff --git a/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt b/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt index 33886e76ae..a08261bb02 100644 --- a/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt +++ b/djvm/src/main/kotlin/net/corda/djvm/utilities/Discovery.kt @@ -1,6 +1,6 @@ package net.corda.djvm.utilities -import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner +import io.github.classgraph.ClassGraph import java.lang.reflect.Modifier /** @@ -13,19 +13,19 @@ object Discovery { * Get an instance of each concrete class that implements interface or class [T]. */ inline fun find(): List { - val instances = mutableListOf() - FastClasspathScanner("net/corda/djvm") - .matchClassesImplementing(T::class.java) { clazz -> - if (clazz.modifiers and FORBIDDEN_CLASS_MASK == 0) { - try { - instances.add(clazz.newInstance()) - } catch (exception: Throwable) { - throw Exception("Unable to instantiate ${clazz.name}", exception) - } + return ClassGraph() + .whitelistPaths("net/corda/djvm") + .enableAllInfo() + .scan() + .use { it.getClassesImplementing(T::class.java.name).loadClasses(T::class.java) } + .filter { it.modifiers and FORBIDDEN_CLASS_MASK == 0 } + .map { + try { + it.newInstance() + } catch (exception: Throwable) { + throw Exception("Unable to instantiate ${it.name}", exception) } } - .scan() - return instances - } + } } diff --git a/experimental/behave/build.gradle b/experimental/behave/build.gradle index c5cba5eb8a..8cf7b06b80 100644 --- a/experimental/behave/build.gradle +++ b/experimental/behave/build.gradle @@ -51,8 +51,8 @@ dependencies { // JOptSimple: command line option parsing compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" - // FastClasspathScanner: classpath scanning - compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" + // ClassGraph: classpath scanning + compile "io.github.classgraph:classgraph:$class_graph_version" compile "commons-io:commons-io:$commonsio_version" compile "com.spotify:docker-client:$docker_client_version" diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt index fbc06d59a9..dc5a0762e3 100644 --- a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/StepsContainer.kt @@ -1,27 +1,29 @@ package net.corda.behave.scenarios import cucumber.api.java8.En -import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner +import io.github.classgraph.ClassGraph import net.corda.behave.scenarios.api.StepsBlock import net.corda.behave.scenarios.api.StepsProvider import net.corda.behave.scenarios.steps.* import net.corda.core.internal.objectOrNewInstance -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger @Suppress("KDocMissingDocumentation") class StepsContainer(val state: ScenarioState) : En { companion object { + private val log = contextLogger() + val stepsProviders: List by lazy { - FastClasspathScanner().addClassLoader(this::class.java.classLoader).scan() - .getNamesOfClassesImplementing(StepsProvider::class.java) - .mapNotNull { this::class.java.classLoader.loadClass(it).asSubclass(StepsProvider::class.java) } + ClassGraph() + .addClassLoader(this::class.java.classLoader) + .enableAllInfo() + .scan() + .use { it.getClassesImplementing(StepsProvider::class.java.name).loadClasses(StepsProvider::class.java) } .map { it.kotlin.objectOrNewInstance() } } } - private val log = loggerFor() - private val stepDefinitions: List = listOf( CashSteps(), ConfigurationSteps(), diff --git a/node-api/build.gradle b/node-api/build.gradle index d70b5af596..0f2c1a8c3e 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -27,8 +27,8 @@ dependencies { compile "org.apache.qpid:proton-j:$protonj_version" - // FastClasspathScanner: classpath scanning - needed for the NetworkBootstrapper. - compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" + // ClassGraph: classpath scanning + compile "io.github.classgraph:classgraph:$class_graph_version" // For caches rather than guava compile "com.github.ben-manes.caffeine:caffeine:$caffeine_version" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ContractsScanning.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ContractsScanning.kt index 674868e909..3ec56d525b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/ContractsScanning.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/ContractsScanning.kt @@ -1,6 +1,6 @@ package net.corda.nodeapi.internal -import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner +import io.github.classgraph.ClassGraph import net.corda.core.contracts.Contract import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.UpgradedContract @@ -28,15 +28,13 @@ class ContractsJarFile(private val file: Path) : ContractsJar { override val hash: SecureHash by lazy(LazyThreadSafetyMode.NONE, file::hash) override fun scan(): List { - val scanResult = FastClasspathScanner() - // A set of a single element may look odd, but if this is removed "Path" which itself is an `Iterable` - // is getting broken into pieces to scan individually, which doesn't yield desired effect. - .overrideClasspath(singleton(file)) - .scan() + val scanResult = ClassGraph().overrideClasspath(singleton(file)).enableAllInfo().scan() - val contractClassNames = coreContractClasses - .flatMap { scanResult.getNamesOfClassesImplementing(it.qualifiedName) } - .toSet() + val contractClassNames = scanResult.use { + coreContractClasses + .flatMap { scanResult.getClassesImplementing(it.qualifiedName).names } + .toSet() + } return URLClassLoader(arrayOf(file.toUri().toURL()), Contract::class.java.classLoader).use { cl -> contractClassNames.mapNotNull { diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt index e47badd63e..4981ae7977 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt @@ -1,7 +1,7 @@ package net.corda.node.internal.cordapp -import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner -import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult +import io.github.classgraph.ClassGraph +import io.github.classgraph.ScanResult import net.corda.core.cordapp.Cordapp import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 @@ -95,7 +95,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: } private fun loadCordapps(): List { val cordapps = cordappJarPaths - .map { scanCordapp(it).toCordapp(it) } + .map { url -> scanCordapp(url).use { it.toCordapp(url) } } .filter { if (it.info.minimumPlatformVersion > versionInfo.platformVersion) { logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum " + @@ -202,7 +202,8 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult { logger.info("Scanning CorDapp in ${cordappJarPath.url}") return cachedScanResult.computeIfAbsent(cordappJarPath) { - RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix) + val scanResult = ClassGraph().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).enableAllInfo().scan() + RestrictedScanResult(scanResult, cordappJarPath.qualifiedNamePrefix) } } @@ -239,40 +240,49 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: return map { it.kotlin.objectOrNewInstance() } } - private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) { + private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) : AutoCloseable { fun getNamesOfClassesImplementing(type: KClass<*>): List { - return scanResult.getNamesOfClassesImplementing(type.java) - .filter { it.startsWith(qualifiedNamePrefix) } + return scanResult.getClassesImplementing(type.java.name).names.filter { it.startsWith(qualifiedNamePrefix) } } fun getClassesWithSuperclass(type: KClass): List> { - return scanResult.getNamesOfSubclassesOf(type.java) + return scanResult + .getSubclasses(type.java.name) + .names .filter { it.startsWith(qualifiedNamePrefix) } .mapNotNull { loadClass(it, type) } - .filterNot { Modifier.isAbstract(it.modifiers) } + .filterNot { it.isAbstractClass } } fun getClassesImplementing(type: KClass): List { - return scanResult.getNamesOfClassesImplementing(type.java) + return scanResult + .getClassesImplementing(type.java.name) + .names .filter { it.startsWith(qualifiedNamePrefix) } .mapNotNull { loadClass(it, type) } - .filterNot { Modifier.isAbstract(it.modifiers) } + .filterNot { it.isAbstractClass } .map { it.kotlin.objectOrNewInstance() } } fun getClassesWithAnnotation(type: KClass, annotation: KClass): List> { - return scanResult.getNamesOfClassesWithAnnotation(annotation.java) + return scanResult + .getClassesWithAnnotation(annotation.java.name) + .names .filter { it.startsWith(qualifiedNamePrefix) } .mapNotNull { loadClass(it, type) } .filterNot { Modifier.isAbstract(it.modifiers) } } fun getConcreteClassesOfType(type: KClass): List> { - return scanResult.getNamesOfSubclassesOf(type.java) + return scanResult + .getSubclasses(type.java.name) + .names .filter { it.startsWith(qualifiedNamePrefix) } .mapNotNull { loadClass(it, type) } - .filterNot { Modifier.isAbstract(it.modifiers) } + .filterNot { it.isAbstractClass } } + + override fun close() = scanResult.close() } } diff --git a/serialization/build.gradle b/serialization/build.gradle index eafc8cd155..fc1275c382 100644 --- a/serialization/build.gradle +++ b/serialization/build.gradle @@ -19,8 +19,8 @@ dependencies { // For AMQP serialisation. compile "org.apache.qpid:proton-j:$protonj_version" - // FastClasspathScanner: classpath scanning - compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version" + // ClassGraph: classpath scanning + compile "io.github.classgraph:classgraph:$class_graph_version" // Pure-Java Snappy compression compile "org.iq80.snappy:snappy:$snappy_version" diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt index 94c0d2223f..f6b5556f82 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializationScheme.kt @@ -2,11 +2,12 @@ package net.corda.serialization.internal.amqp -import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner +import io.github.classgraph.ClassGraph import net.corda.core.DeleteForDJVM import net.corda.core.KeepForDJVM import net.corda.core.StubOutForDJVM import net.corda.core.cordapp.Cordapp +import net.corda.core.internal.isAbstractClass import net.corda.core.internal.objectOrNewInstance import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.* @@ -16,7 +17,6 @@ import net.corda.serialization.internal.CordaSerializationMagic import net.corda.serialization.internal.DefaultWhitelist import net.corda.serialization.internal.MutableClassWhitelist import net.corda.serialization.internal.SerializationScheme -import java.lang.reflect.Modifier import java.util.* val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == amqpMagic @@ -72,10 +72,16 @@ abstract class AbstractAMQPSerializationScheme( @StubOutForDJVM private fun scanClasspathForSerializers(scanSpec: String): List> = this::class.java.classLoader.let { cl -> - FastClasspathScanner(scanSpec).addClassLoader(cl).scan() - .getNamesOfClassesImplementing(SerializationCustomSerializer::class.java) - .map { cl.loadClass(it).asSubclass(SerializationCustomSerializer::class.java) } - .filterNot { Modifier.isAbstract(it.modifiers) } + ClassGraph() + .whitelistPackages(scanSpec) + .addClassLoader(cl) + .enableAllInfo() + .scan() + .use { + val serializerClass = SerializationCustomSerializer::class.java + it.getClassesImplementing(serializerClass.name).loadClasses(serializerClass) + } + .filterNot { it.isAbstractClass } .map { it.kotlin.objectOrNewInstance() } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt index 6f755db697..6980e8eaca 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt @@ -1,6 +1,6 @@ package net.corda.testing.node.internal -import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner +import io.github.classgraph.ClassGraph import net.corda.core.internal.createDirectories import net.corda.core.internal.deleteIfExists import net.corda.core.internal.outputStream @@ -69,9 +69,12 @@ fun Iterable.packageInDirectory(directory: Path) { * Returns all classes within the [targetPackage]. */ fun allClassesForPackage(targetPackage: String): Set> { - - val scanResult = FastClasspathScanner(targetPackage).strictWhitelist().scan() - return scanResult.namesOfAllClasses.filter { className -> className.startsWith(targetPackage) }.map(scanResult::classNameToClassRef).toSet() + return ClassGraph() + .whitelistPackages(targetPackage) + .enableAllInfo() + .scan() + .use { it.allClasses.loadClasses() } + .toSet() } /** From b769ad80bd9b86cf795d3dda7364fa945ae39d8f Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Fri, 12 Oct 2018 16:54:39 +0100 Subject: [PATCH 2/8] CORDA-195 When collecting JAR Signatures allow META-INF/*.EC block signature to follow jarsinger tool capabilities (#4065) jarsigner can produce META-INF/*.EC block signature for EC algorithm (https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jarsigner.html) even if this is contrary to JAR File spec (https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html). Allow block signature be also in *.EC file. --- .../core/internal/JarSignatureCollector.kt | 7 +++++-- .../internal/JarSignatureCollectorTest.kt | 20 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt b/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt index 70d8c84873..78d5a15517 100644 --- a/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt +++ b/core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt @@ -11,8 +11,11 @@ import java.util.jar.JarInputStream */ object JarSignatureCollector { - /** @see */ - private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA)|SIG-.*)".toRegex() + /** + * @see + * also accepting *.EC as this can be created and accepted by jarsigner tool @see https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jarsigner.html + * and Java Security Manager. */ + private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA|EC)|SIG-.*)".toRegex() /** * Returns an ordered list of every [Party] which has signed every signable item in the given [JarInputStream]. diff --git a/core/src/test/kotlin/net/corda/core/internal/JarSignatureCollectorTest.kt b/core/src/test/kotlin/net/corda/core/internal/JarSignatureCollectorTest.kt index b8620082d8..fed904c1fa 100644 --- a/core/src/test/kotlin/net/corda/core/internal/JarSignatureCollectorTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/JarSignatureCollectorTest.kt @@ -4,6 +4,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME +import net.corda.testing.core.CHARLIE_NAME import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.AfterClass @@ -38,15 +39,18 @@ class JarSignatureCollectorTest { private const val ALICE_PASS = "alicepass" private const val BOB = "bob" private const val BOB_PASS = "bobpass" + private const val CHARLIE = "Charlie" + private const val CHARLIE_PASS = "charliepass" - private fun generateKey(alias: String, password: String, name: CordaX500Name) = - execute("keytool", "-genkey", "-keystore", "_teststore", "-storepass", "storepass", "-keyalg", "RSA", "-alias", alias, "-keypass", password, "-dname", name.toString()) + private fun generateKey(alias: String, password: String, name: CordaX500Name, keyalg: String = "RSA") = + execute("keytool", "-genkey", "-keystore", "_teststore", "-storepass", "storepass", "-keyalg", keyalg, "-alias", alias, "-keypass", password, "-dname", name.toString()) @BeforeClass @JvmStatic fun beforeClass() { generateKey(ALICE, ALICE_PASS, ALICE_NAME) generateKey(BOB, BOB_PASS, BOB_NAME) + generateKey(CHARLIE, CHARLIE_PASS, CHARLIE_NAME, "EC") (dir / "_signable1").writeLines(listOf("signable1")) (dir / "_signable2").writeLines(listOf("signable2")) @@ -141,6 +145,18 @@ class JarSignatureCollectorTest { assertFailsWith { getJarSigners() } } + // Signing using EC algorithm produced JAR File spec incompatible signature block (META-INF/*.EC) which is anyway accepted by jarsiner, see [JarSignatureCollector] + @Test + fun `one signer with EC sign algorithm`() { + createJar("_signable1", "_signable2") + signJar(CHARLIE, CHARLIE_PASS) + assertEquals(listOf(CHARLIE_NAME), getJarSigners().names) // We only reused CHARLIE's distinguished name, so the keys will be different. + + (dir / "my-dir").createDirectory() + updateJar("my-dir") + assertEquals(listOf(CHARLIE_NAME), getJarSigners().names) // Unsigned directory is irrelevant. + } + //region Helper functions private fun createJar(vararg contents: String) = execute(*(arrayOf("jar", "cvf", FILENAME) + contents)) From 2c9a942e1a15d4d7c6f403915229b187f1d93de2 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 15 Oct 2018 10:11:18 +0100 Subject: [PATCH 3/8] CORDA-2088: Simplified the TestCordapp public API (#4064) The entry point to the API has been simplified to just requireing a list of packages to scan, with sensible defaults provided for the metadata. Because of the wither methods, having parameters for the metadata (with default values) seems unnecessary. Also the ability to scan just individual classes has been made internal, as it seems unlikely app developers would need that level of control when testing their apps. TestCordappImpl is a data class and thus acts as a natural key for the Jar caching, where previously the key was the package names. This fixes an issue where it was not possible to create two CorDapp Jars of the same package but different metadata. --- .ci/api-current.txt | 89 +------- ...tachmentsClassLoaderStaticContractTests.kt | 29 +-- .../node/flows/AsymmetricCorDappsTests.kt | 29 +-- ...owCheckpointVersionNodeStartupCheckTest.kt | 201 ++++++++--------- .../node/services/AttachmentLoadingTests.kt | 20 +- .../cordapp/JarScanningCordappLoader.kt | 10 +- .../node/internal/cordapp/ManifestUtils.kt | 3 +- .../net/corda/node/flows/cordapp.properties | 1 - .../cordapp/JarScanningCordappLoaderTest.kt | 43 +--- .../node/services/FinalityHandlerTest.kt | 9 +- .../kotlin/net/corda/testing/driver/Driver.kt | 29 +-- .../net/corda/testing/driver/DriverDSL.kt | 7 +- .../net/corda/testing/driver/TestCorDapp.kt | 85 ------- .../net/corda/testing/node/MockNetwork.kt | 44 ++-- .../net/corda/testing/node/MockServices.kt | 8 +- .../net/corda/testing/node/TestCordapp.kt | 69 ++++++ .../testing/node/internal/DriverDSLImpl.kt | 59 +++-- .../node/internal/InternalMockNetwork.kt | 14 +- .../node/internal/MutableTestCorDapp.kt | 73 ------ .../testing/node/internal/NodeBasedTest.kt | 5 +- .../corda/testing/node/internal/RPCDriver.kt | 4 +- .../node/internal/TestCordappDirectories.kt | 70 ++---- .../testing/node/internal/TestCordappImpl.kt | 26 +++ .../node/internal/TestCordappsUtils.kt | 208 +++--------------- .../node/internal/TestCordappsUtilsTest.kt | 93 ++++++++ .../corda/testing/node/internal/resource.txt | 0 26 files changed, 475 insertions(+), 753 deletions(-) delete mode 100644 node/src/main/resources/net/corda/node/flows/cordapp.properties delete mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/driver/TestCorDapp.kt create mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt delete mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/MutableTestCorDapp.kt create mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt create mode 100644 testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/TestCordappsUtilsTest.kt create mode 100644 testing/node-driver/src/test/resources/net/corda/testing/node/internal/resource.txt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 13ae84905c..8d3efd6638 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -5822,8 +5822,6 @@ public interface net.corda.testing.driver.DriverDSL @NotNull public abstract net.corda.core.concurrent.CordaFuture startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String) @NotNull - public abstract net.corda.core.concurrent.CordaFuture startNode(net.corda.testing.driver.NodeParameters, net.corda.core.identity.CordaX500Name, java.util.List, net.corda.testing.driver.VerifierType, java.util.Map, Boolean, String, java.util.Set, boolean) - @NotNull public abstract net.corda.core.concurrent.CordaFuture startWebserver(net.corda.testing.driver.NodeHandle) @NotNull public abstract net.corda.core.concurrent.CordaFuture startWebserver(net.corda.testing.driver.NodeHandle, String) @@ -5832,10 +5830,7 @@ public final class net.corda.testing.driver.DriverParameters extends java.lang.O public () public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters) public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map, boolean, boolean) - public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Map, boolean, boolean, java.util.Set) - public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, java.util.Set) public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean) - public (boolean, java.nio.file.Path, net.corda.testing.driver.PortAllocation, net.corda.testing.driver.PortAllocation, java.util.Map, boolean, boolean, boolean, java.util.List, java.util.List, net.corda.testing.driver.JmxPolicy, net.corda.core.node.NetworkParameters, boolean, boolean, java.util.Set) public final boolean component1() @NotNull public final java.util.List component10() @@ -6028,75 +6023,6 @@ public static final class net.corda.testing.driver.PortAllocation$Incremental ex public final java.util.concurrent.atomic.AtomicInteger getPortCounter() public int nextPort() ## -@DoNotImplement -public interface net.corda.testing.driver.TestCorDapp - @NotNull - public abstract java.util.Set> getClasses() - @NotNull - public abstract String getName() - @NotNull - public abstract java.util.Set getResources() - @NotNull - public abstract String getTitle() - @NotNull - public abstract String getVendor() - @NotNull - public abstract String getVersion() - @NotNull - public abstract java.nio.file.Path packageAsJarInDirectory(java.nio.file.Path) - public abstract void packageAsJarWithPath(java.nio.file.Path) -## -public static final class net.corda.testing.driver.TestCorDapp$Factory extends java.lang.Object - public () - @NotNull - public static final net.corda.testing.driver.TestCorDapp$Mutable create(String, String, String, String, java.util.Set>, kotlin.jvm.functions.Function2) - public static final net.corda.testing.driver.TestCorDapp$Factory$Companion Companion -## -public static final class net.corda.testing.driver.TestCorDapp$Factory$Companion extends java.lang.Object - @NotNull - public final net.corda.testing.driver.TestCorDapp$Mutable create(String, String, String, String, java.util.Set>, kotlin.jvm.functions.Function2) -## -@DoNotImplement -public static interface net.corda.testing.driver.TestCorDapp$Mutable extends net.corda.testing.driver.TestCorDapp - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable minus(Class) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackage(Package) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackage(String) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(Package, Package...) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(String, String...) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable minusPackages(java.util.Set) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable minusResource(String, java.net.URL) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable plus(Class) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackage(Package) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackage(String) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(Package, Package...) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(String, String...) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable plusPackages(java.util.Set) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable plusResource(String, java.net.URL) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable withClasses(java.util.Set>) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable withName(String) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable withTitle(String) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable withVendor(String) - @NotNull - public abstract net.corda.testing.driver.TestCorDapp$Mutable withVersion(String) -## public final class net.corda.testing.driver.VerifierType extends java.lang.Enum protected () public static net.corda.testing.driver.VerifierType valueOf(String) @@ -6238,9 +6164,9 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object @NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1) @NotNull - public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.List) + public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Collection) @NotNull - public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Set) + public final net.corda.testing.node.StartedMockNode createNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.List) @NotNull public final net.corda.testing.node.StartedMockNode createNode(net.corda.testing.node.MockNodeParameters) @NotNull @@ -6256,9 +6182,9 @@ public class net.corda.testing.node.MockNetwork extends java.lang.Object @NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1) @NotNull - public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.List) + public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Collection) @NotNull - public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Set) + public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.core.identity.CordaX500Name, Integer, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.List) @NotNull public final net.corda.testing.node.UnstartedMockNode createUnstartedNode(net.corda.testing.node.MockNodeParameters) @NotNull @@ -6339,8 +6265,7 @@ public final class net.corda.testing.node.MockNetworkParameters extends java.lan public final class net.corda.testing.node.MockNodeParameters extends java.lang.Object public () public (Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1) - public (Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.List) - public (Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Set) + public (Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Collection) @Nullable public final Integer component1() @Nullable @@ -6352,9 +6277,7 @@ public final class net.corda.testing.node.MockNodeParameters extends java.lang.O @NotNull public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1) @NotNull - public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.List) - @NotNull - public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Set) + public final net.corda.testing.node.MockNodeParameters copy(Integer, net.corda.core.identity.CordaX500Name, java.math.BigInteger, kotlin.jvm.functions.Function1, java.util.Collection) public boolean equals(Object) @NotNull public final kotlin.jvm.functions.Function1 getConfigOverrides() diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt index c699439e19..f7b7f49cfe 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderStaticContractTests.kt @@ -15,7 +15,6 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.node.VersionInfo import net.corda.node.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.JarScanningCordappLoader @@ -25,16 +24,13 @@ import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.internal.rigorousMock +import net.corda.testing.node.internal.TestCordappDirectories import net.corda.testing.node.internal.cordappsForPackages -import net.corda.testing.node.internal.getTimestampAsDirectoryName -import net.corda.testing.node.internal.packageInDirectory import net.corda.testing.services.MockAttachmentStorage +import org.assertj.core.api.Assertions.assertThat import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull import org.junit.Rule import org.junit.Test -import java.nio.file.Path -import java.nio.file.Paths class AttachmentsClassLoaderStaticContractTests { private companion object { @@ -101,22 +97,11 @@ class AttachmentsClassLoaderStaticContractTests { @Test fun `verify that contract DummyContract is in classPath`() { val contractClass = Class.forName("net.corda.nodeapi.internal.AttachmentsClassLoaderStaticContractTests\$AttachmentDummyContract") - val contract = contractClass.newInstance() as Contract - - assertNotNull(contract) + assertThat(contractClass.newInstance()).isInstanceOf(Contract::class.java) } - private fun cordappLoaderForPackages(packages: Iterable): CordappLoader { - - val cordapps = cordappsForPackages(packages) - return testDirectory().let { directory -> - cordapps.packageInDirectory(directory) - JarScanningCordappLoader.fromDirectories(listOf(directory), VersionInfo.UNKNOWN) - } + private fun cordappLoaderForPackages(packages: Collection): CordappLoader { + val dirs = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) } + return JarScanningCordappLoader.fromDirectories(dirs) } - - private fun testDirectory(): Path { - - return Paths.get("build", getTimestampAsDirectoryName()) - } -} \ No newline at end of file +} diff --git a/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt b/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt index 2875c21527..6e1e07d8ee 100644 --- a/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/flows/AsymmetricCorDappsTests.kt @@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.internal.concurrent.transpose -import net.corda.core.internal.packageName import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap @@ -12,8 +11,8 @@ import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity import net.corda.testing.driver.DriverParameters -import net.corda.testing.driver.TestCorDapp import net.corda.testing.driver.driver +import net.corda.testing.node.internal.cordappForClasses import org.junit.Test import kotlin.test.assertEquals @@ -46,23 +45,18 @@ class AsymmetricCorDappsTests { } @Test - fun noSharedCorDappsWithAsymmetricSpecificClasses() { - + fun `no shared cordapps with asymmetric specific classes`() { driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = emptySet())) { - - val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java)))).getOrThrow() - val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java, Pong::class.java)))).getOrThrow() + val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(cordappForClasses(Ping::class.java))).getOrThrow() + val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForClasses(Ping::class.java, Pong::class.java))).getOrThrow() nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow() } } @Test - fun sharedCorDappsWithAsymmetricSpecificClasses() { - - val resourceName = "cordapp.properties" - val cordappPropertiesResource = this::class.java.getResource(resourceName) - val sharedCordapp = TestCorDapp.Factory.create("shared", "1.0", classes = setOf(Ping::class.java)).plusResource("${AsymmetricCorDappsTests::class.java.packageName}.$resourceName", cordappPropertiesResource) - val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java)) + fun `shared cordapps with asymmetric specific classes`() { + val sharedCordapp = cordappForClasses(Ping::class.java) + val cordappForNodeB = cordappForClasses(Pong::class.java) driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = setOf(sharedCordapp))) { val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow() @@ -71,12 +65,9 @@ class AsymmetricCorDappsTests { } @Test - fun sharedCorDappsWithAsymmetricSpecificClassesInProcess() { - - val resourceName = "cordapp.properties" - val cordappPropertiesResource = this::class.java.getResource(resourceName) - val sharedCordapp = TestCorDapp.Factory.create("shared", "1.0", classes = setOf(Ping::class.java)).plusResource("${AsymmetricCorDappsTests::class.java.packageName}.$resourceName", cordappPropertiesResource) - val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java)) + fun `shared cordapps with asymmetric specific classes in process`() { + val sharedCordapp = cordappForClasses(Ping::class.java) + val cordappForNodeB = cordappForClasses(Pong::class.java) driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = setOf(sharedCordapp))) { val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow() diff --git a/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt b/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt index ab2d706403..169ba207ce 100644 --- a/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/flows/FlowCheckpointVersionNodeStartupCheckTest.kt @@ -1,27 +1,30 @@ package net.corda.node.flows -import net.corda.client.rpc.CordaRPCClient +import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.div import net.corda.core.internal.list +import net.corda.core.internal.moveTo import net.corda.core.internal.readLines import net.corda.core.messaging.startTrackedFlow import net.corda.core.utilities.getOrThrow import net.corda.node.internal.CheckpointIncompatibleException import net.corda.node.internal.NodeStartup -import net.corda.node.services.Permissions.Companion.invokeRpc -import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.testMessage.Message -import net.corda.testMessage.MessageState +import net.corda.testMessage.* import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity +import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.DriverParameters -import net.corda.testing.driver.TestCorDapp import net.corda.testing.driver.driver -import net.corda.testing.node.User +import net.corda.testing.node.TestCordapp import net.corda.testing.node.internal.ListenProcessDeathException +import net.corda.testing.node.internal.TestCordappDirectories +import net.corda.testing.node.internal.cordappForClasses +import net.test.cordapp.v1.Record import net.test.cordapp.v1.SendMessageFlow import org.junit.Test +import java.nio.file.StandardCopyOption.REPLACE_EXISTING +import java.util.* import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -30,125 +33,111 @@ import kotlin.test.assertNotNull class FlowCheckpointVersionNodeStartupCheckTest { companion object { val message = Message("Hello world!") - val classes = setOf(net.corda.testMessage.MessageState::class.java, - net.corda.testMessage.MessageContract::class.java, - net.test.cordapp.v1.SendMessageFlow::class.java, - net.corda.testMessage.MessageSchema::class.java, - net.corda.testMessage.MessageSchemaV1::class.java, - net.test.cordapp.v1.Record::class.java) - val user = User("mark", "dadada", setOf(startFlow(), invokeRpc("vaultQuery"), invokeRpc("vaultTrack"))) + val defaultCordapp = cordappForClasses( + MessageState::class.java, + MessageContract::class.java, + SendMessageFlow::class.java, + MessageSchema::class.java, + MessageSchemaV1::class.java, + Record::class.java + ) } @Test fun `restart node successfully with suspended flow`() { - - val cordapps = setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)) - - return driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, cordappsForAllNodes = cordapps)) { - { - val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME).getOrThrow() - val bob = startNode(rpcUsers = listOf(user), providedName = BOB_NAME).getOrThrow() - alice.stop() - CordaRPCClient(bob.rpcAddress).start(user.username, user.password).use { - val flowTracker = it.proxy.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress - //wait until Bob progresses as far as possible because alice node is off - flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single() - } - bob.stop() - }() - val result = { - //Bob will resume the flow - val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow() - startNode(providedName = BOB_NAME, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to false)).getOrThrow() - CordaRPCClient(alice.rpcAddress).start(user.username, user.password).use { - val page = it.proxy.vaultTrack(MessageState::class.java) - if (page.snapshot.states.isNotEmpty()) { - page.snapshot.states.first() - } else { - val r = page.updates.timeout(5, TimeUnit.SECONDS).take(1).toBlocking().single() - if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first() - } - } - }() + return driver(parametersForRestartingNodes(listOf(defaultCordapp))) { + createSuspendedFlowInBob(cordapps = emptySet()) + // Bob will resume the flow + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + startNode(providedName = BOB_NAME).getOrThrow() + val page = alice.rpc.vaultTrack(MessageState::class.java) + val result = if (page.snapshot.states.isNotEmpty()) { + page.snapshot.states.first() + } else { + val r = page.updates.timeout(5, TimeUnit.SECONDS).take(1).toBlocking().single() + if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first() + } assertNotNull(result) assertEquals(message, result.state.data.message) } } - private fun assertNodeRestartFailure( - cordapps: Set?, - cordappsVersionAtStartup: Set, - cordappsVersionAtRestart: Set, - reuseAdditionalCordappsAtRestart: Boolean, - assertNodeLogs: String - ) { + @Test + fun `restart node with incompatible version of suspended flow due to different jar name`() { + driver(parametersForRestartingNodes()) { + val cordapp = defaultCordapp.withName("different-jar-name-test-${UUID.randomUUID()}") + // Create the CorDapp jar file manually first to get hold of the directory that will contain it so that we can + // rename the filename later. The cordappDir, which acts as pointer to the jar file, does not get renamed. + val cordappDir = TestCordappDirectories.getJarDirectory(cordapp) + val cordappJar = cordappDir.list().single() - return driver(DriverParameters( - startNodesInProcess = false, // start nodes in separate processes to ensure CordappLoader is not shared between restarts - inMemoryDB = false, // ensure database is persisted between node restarts so we can keep suspended flow in Bob's node - cordappsForAllNodes = cordapps) - ) { - val bobLogFolder = { - val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, additionalCordapps = cordappsVersionAtStartup).getOrThrow() - val bob = startNode(rpcUsers = listOf(user), providedName = BOB_NAME, additionalCordapps = cordappsVersionAtStartup).getOrThrow() - alice.stop() - CordaRPCClient(bob.rpcAddress).start(user.username, user.password).use { - val flowTracker = it.proxy.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress - // wait until Bob progresses as far as possible because Alice node is offline - flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single() - } - val logFolder = bob.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME - // SendMessageFlow suspends in Bob node - bob.stop() - logFolder - }() + createSuspendedFlowInBob(setOf(cordapp)) - startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false), - additionalCordapps = cordappsVersionAtRestart, regenerateCordappsOnStart = !reuseAdditionalCordappsAtRestart).getOrThrow() + // Rename the jar file. TestCordappDirectories caches the location of the jar file but the use of the random + // UUID in the name means there's zero chance of contaminating another test. + cordappJar.moveTo(cordappDir / "renamed-${cordappJar.fileName}") - assertFailsWith(ListenProcessDeathException::class) { - startNode(providedName = BOB_NAME, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to false), - additionalCordapps = cordappsVersionAtRestart, regenerateCordappsOnStart = !reuseAdditionalCordappsAtRestart).getOrThrow() - } - - val logFile = bobLogFolder.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() } - val numberOfNodesThatLogged = logFile.readLines { it.filter { assertNodeLogs in it }.count() } - assertEquals(1, numberOfNodesThatLogged) + assertBobFailsToStartWithLogMessage( + setOf(cordapp), + CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message + ) } } @Test - fun `restart nodes with incompatible version of suspended flow due to different jar name`() { + fun `restart node with incompatible version of suspended flow due to different jar hash`() { + driver(parametersForRestartingNodes()) { + val originalCordapp = defaultCordapp.withName("different-jar-hash-test-${UUID.randomUUID()}") + val originalCordappJar = TestCordappDirectories.getJarDirectory(originalCordapp).list().single() - assertNodeRestartFailure( - emptySet(), - setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)), - setOf(TestCorDapp.Factory.create("testJar2", "1.0", classes = classes)), - false, - CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message) + createSuspendedFlowInBob(setOf(originalCordapp)) + + // The vendor is part of the MANIFEST so changing it is sufficient to change the jar hash + val modifiedCordapp = originalCordapp.withVendor("${originalCordapp.vendor}-modified") + val modifiedCordappJar = TestCordappDirectories.getJarDirectory(modifiedCordapp).list().single() + modifiedCordappJar.moveTo(originalCordappJar, REPLACE_EXISTING) + + assertBobFailsToStartWithLogMessage( + setOf(originalCordapp), + // The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException + "that is incompatible with the current installed version of" + ) + } } - @Test - fun `restart nodes with incompatible version of suspended flow`() { - - assertNodeRestartFailure( - emptySet(), - setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)), - setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes + net.test.cordapp.v1.SendMessageFlow::class.java)), - false, - // the part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException - "that is incompatible with the current installed version of") + private fun DriverDSL.createSuspendedFlowInBob(cordapps: Set) { + val (alice, bob) = listOf(ALICE_NAME, BOB_NAME) + .map { startNode(providedName = it, additionalCordapps = cordapps) } + .transpose() + .getOrThrow() + alice.stop() + val flowTracker = bob.rpc.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress + // Wait until Bob progresses as far as possible because Alice node is offline + flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single() + bob.stop() } - @Test - fun `restart nodes with incompatible version of suspended flow due to different timestamps only`() { + private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection, logMessage: String) { + assertFailsWith(ListenProcessDeathException::class) { + startNode( + providedName = BOB_NAME, + customOverrides = mapOf("devMode" to false), + additionalCordapps = cordapps, + regenerateCordappsOnStart = true + ).getOrThrow() + } - assertNodeRestartFailure( - emptySet(), - setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)), - setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)), - false, - // the part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException - "that is incompatible with the current installed version of") + val logDir = baseDirectory(BOB_NAME) / NodeStartup.LOGS_DIRECTORY_NAME + val logFile = logDir.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() } + val matchingLineCount = logFile.readLines { it.filter { line -> logMessage in line }.count() } + assertEquals(1, matchingLineCount) } -} \ No newline at end of file + + private fun parametersForRestartingNodes(cordappsForAllNodes: List = emptyList()): DriverParameters { + return DriverParameters( + startNodesInProcess = false, // Start nodes in separate processes to ensure CordappLoader is not shared between restarts + inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows + cordappsForAllNodes = cordappsForAllNodes + ) + } +} diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index b9e5029fc9..5f77275446 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -3,17 +3,11 @@ package net.corda.node.services import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.CordaRuntimeException -import net.corda.core.contracts.Contract -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.PartyAndReference -import net.corda.core.contracts.StateAndRef -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TransactionState +import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party -import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.toLedgerTransaction import net.corda.core.node.NetworkParameters import net.corda.core.node.ServicesForResolution @@ -21,19 +15,16 @@ import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.IdentityService import net.corda.core.serialization.SerializationFactory import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow import net.corda.node.VersionInfo -import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl +import net.corda.node.internal.cordapp.JarScanningCordappLoader import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity -import net.corda.testing.driver.DriverDSL import net.corda.testing.driver.DriverParameters -import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.internal.rigorousMock @@ -59,7 +50,6 @@ class AttachmentLoadingTests { private val appContext get() = provider.getAppContext(cordapp) private companion object { - private val logger = contextLogger() val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!! const val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract" @@ -70,12 +60,6 @@ class AttachmentLoadingTests { .asSubclass(FlowLogic::class.java) val DUMMY_BANK_A = TestIdentity(DUMMY_BANK_A_NAME, 40).party val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party - private fun DriverDSL.createTwoNodes(): List { - return listOf( - startNode(providedName = bankAName), - startNode(providedName = bankBName) - ).transpose().getOrThrow() - } } private val services = object : ServicesForResolution { diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt index 4981ae7977..ac1badeafe 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt @@ -61,11 +61,13 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: /** * Creates a CordappLoader from multiple directories. * - * @param corDappDirectories Directories used to scan for CorDapp JARs. + * @param cordappDirs Directories used to scan for CorDapp JARs. */ - fun fromDirectories(corDappDirectories: Iterable, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List = emptyList()): JarScanningCordappLoader { - logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}") - val paths = corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() } + fun fromDirectories(cordappDirs: Collection, + versionInfo: VersionInfo = VersionInfo.UNKNOWN, + extraCordapps: List = emptyList()): JarScanningCordappLoader { + logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}") + val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() } return JarScanningCordappLoader(paths, versionInfo, extraCordapps) } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt index 6e10661c86..fbca1b7eec 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/ManifestUtils.kt @@ -5,7 +5,7 @@ import net.corda.core.internal.cordapp.CordappImpl.Info.Companion.UNKNOWN_VALUE import java.util.jar.Attributes import java.util.jar.Manifest -fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest { +fun createTestManifest(name: String, title: String, version: String, vendor: String, targetVersion: Int): Manifest { val manifest = Manifest() // Mandatory manifest attribute. If not present, all other entries are silently skipped. @@ -20,6 +20,7 @@ fun createTestManifest(name: String, title: String, version: String, vendor: Str manifest["Implementation-Title"] = title manifest["Implementation-Version"] = version manifest["Implementation-Vendor"] = vendor + manifest["Target-Platform-Version"] = targetVersion.toString() return manifest } diff --git a/node/src/main/resources/net/corda/node/flows/cordapp.properties b/node/src/main/resources/net/corda/node/flows/cordapp.properties deleted file mode 100644 index 4b10332984..0000000000 --- a/node/src/main/resources/net/corda/node/flows/cordapp.properties +++ /dev/null @@ -1 +0,0 @@ -key=value \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt index 4dd255f414..176bbfb5d8 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt @@ -2,14 +2,12 @@ package net.corda.node.internal.cordapp import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.* +import net.corda.core.internal.packageName import net.corda.node.VersionInfo -import net.corda.node.cordapp.CordappLoader -import net.corda.testing.node.internal.cordappsForPackages -import net.corda.testing.node.internal.getTimestampAsDirectoryName -import net.corda.testing.node.internal.packageInDirectory +import net.corda.testing.node.internal.TestCordappDirectories +import net.corda.testing.node.internal.cordappForPackages import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import java.nio.file.Path import java.nio.file.Paths @InitiatingFlow @@ -38,7 +36,6 @@ class DummyRPCFlow : FlowLogic() { class JarScanningCordappLoaderTest { private companion object { - const val testScanPackage = "net.corda.node.internal.cordapp" const val isolatedContractId = "net.corda.finance.contracts.isolated.AnotherDummyContract" const val isolatedFlowName = "net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Initiator" } @@ -70,32 +67,18 @@ class JarScanningCordappLoaderTest { @Test fun `flows are loaded by loader`() { - val loader = cordappLoaderForPackages(listOf(testScanPackage)) + val dir = TestCordappDirectories.getJarDirectory(cordappForPackages(javaClass.packageName)) + val loader = JarScanningCordappLoader.fromDirectories(listOf(dir)) - val actual = loader.cordapps.toTypedArray() // One cordapp from this source tree. In gradle it will also pick up the node jar. - assertThat(actual.size == 0 || actual.size == 1).isTrue() + assertThat(loader.cordapps).isNotEmpty - val actualCordapp = actual.single { !it.initiatedFlows.isEmpty() } + val actualCordapp = loader.cordapps.single { !it.initiatedFlows.isEmpty() } assertThat(actualCordapp.initiatedFlows).first().hasSameClassAs(DummyFlow::class.java) assertThat(actualCordapp.rpcFlows).first().hasSameClassAs(DummyRPCFlow::class.java) assertThat(actualCordapp.schedulableFlows).first().hasSameClassAs(DummySchedulableFlow::class.java) } - @Test - fun `duplicate packages are ignored`() { - val loader = cordappLoaderForPackages(listOf(testScanPackage, testScanPackage)) - val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows } - assertThat(cordapps).hasSize(1) - } - - @Test - fun `sub-packages are ignored`() { - val loader = cordappLoaderForPackages(listOf("net.corda.core", testScanPackage)) - val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows } - assertThat(cordapps).hasSize(1) - } - // This test exists because the appClassLoader is used by serialisation and we need to ensure it is the classloader // being used internally. Later iterations will use a classloader per cordapp and this test can be retired. @Test @@ -159,16 +142,4 @@ class JarScanningCordappLoaderTest { val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN.copy(platformVersion = 2)) assertThat(loader.cordapps).hasSize(1) } - - private fun cordappLoaderForPackages(packages: Iterable): CordappLoader { - val cordapps = cordappsForPackages(packages) - return testDirectory().let { directory -> - cordapps.packageInDirectory(directory) - JarScanningCordappLoader.fromDirectories(listOf(directory)) - } - } - - private fun testDirectory(): Path { - return Paths.get("build", getTimestampAsDirectoryName()) - } } diff --git a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt index 0f1e21be0e..f055b468fb 100644 --- a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt @@ -15,11 +15,7 @@ import net.corda.node.services.statemachine.StaffedFlowHospital.MedicalRecord.Ke import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity -import net.corda.testing.driver.TestCorDapp -import net.corda.testing.node.internal.InternalMockNetwork -import net.corda.testing.node.internal.InternalMockNodeParameters -import net.corda.testing.node.internal.TestStartedNode -import net.corda.testing.node.internal.startFlow +import net.corda.testing.node.internal.* import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Test @@ -38,8 +34,7 @@ class FinalityHandlerTest { // CorDapp. Bob's FinalityHandler will error when validating the tx. mockNet = InternalMockNetwork() - val assertCordapp = TestCorDapp.Factory.create("net.corda.finance.contracts.asset", "1.0").plusPackage("net.corda.finance.contracts.asset") - val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = setOf(assertCordapp))) + val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = setOf(FINANCE_CORDAPP))) var bob = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME)) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index e413f5d90c..793c6fd8bb 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -19,6 +19,7 @@ import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.driver.PortAllocation.Incremental import net.corda.testing.driver.internal.internalServices import net.corda.testing.node.NotarySpec +import net.corda.testing.node.TestCordapp import net.corda.testing.node.User import net.corda.testing.node.internal.* import rx.Observable @@ -134,8 +135,8 @@ abstract class PortAllocation { * in. If null the Driver-level value will be used. * @property maximumHeapSize The maximum JVM heap size to use for the node. * @property logLevel Logging level threshold. - * @property additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. - * @property regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. + * @property additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. + * @property regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. */ @Suppress("unused") data class NodeParameters( @@ -146,7 +147,7 @@ data class NodeParameters( val startInSameProcess: Boolean? = null, val maximumHeapSize: String = "512m", val logLevel: String? = null, - val additionalCordapps: Set = emptySet(), + val additionalCordapps: Collection = emptySet(), val regenerateCordappsOnStart: Boolean = false ) { /** @@ -226,8 +227,8 @@ data class NodeParameters( * @param startInSameProcess Determines if the node should be started inside the same process the Driver is running * in. If null the Driver-level value will be used. * @param maximumHeapSize The maximum JVM heap size to use for the node. - * @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. - * @param regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. + * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. + * @param regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. */ constructor( providedName: CordaX500Name?, @@ -236,7 +237,7 @@ data class NodeParameters( customOverrides: Map, startInSameProcess: Boolean?, maximumHeapSize: String, - additionalCordapps: Set = emptySet(), + additionalCordapps: Set = emptySet(), regenerateCordappsOnStart: Boolean = false ) : this( providedName, @@ -291,7 +292,7 @@ data class NodeParameters( fun withStartInSameProcess(startInSameProcess: Boolean?): NodeParameters = copy(startInSameProcess = startInSameProcess) fun withMaximumHeapSize(maximumHeapSize: String): NodeParameters = copy(maximumHeapSize = maximumHeapSize) fun withLogLevel(logLevel: String?): NodeParameters = copy(logLevel = logLevel) - fun withAdditionalCordapps(additionalCordapps: Set): NodeParameters = copy(additionalCordapps = additionalCordapps) + fun withAdditionalCordapps(additionalCordapps: Set): NodeParameters = copy(additionalCordapps = additionalCordapps) fun withDeleteExistingCordappsDirectory(regenerateCordappsOnStart: Boolean): NodeParameters = copy(regenerateCordappsOnStart = regenerateCordappsOnStart) } @@ -379,7 +380,7 @@ fun driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr * @property inMemoryDB Whether to use in-memory H2 for new nodes rather then on-disk (the node starts quicker, however * the data is not persisted between node restarts). Has no effect if node is configured * in any way to use database other than H2. - * @property cordappsForAllNodes [TestCorDapp]s that will be added to each node started by the [DriverDSL]. + * @property cordappsForAllNodes [TestCordapp]s that will be added to each node started by the [DriverDSL]. */ @Suppress("unused") data class DriverParameters( @@ -398,7 +399,7 @@ data class DriverParameters( val notaryCustomOverrides: Map = emptyMap(), val initialiseSerialization: Boolean = true, val inMemoryDB: Boolean = true, - val cordappsForAllNodes: Set? = null + val cordappsForAllNodes: Collection? = null ) { constructor( isDebug: Boolean = false, @@ -480,7 +481,7 @@ data class DriverParameters( extraCordappPackagesToScan: List, jmxPolicy: JmxPolicy, networkParameters: NetworkParameters, - cordappsForAllNodes: Set? = null + cordappsForAllNodes: Collection? = null ) : this( isDebug, driverDirectory, @@ -549,7 +550,7 @@ data class DriverParameters( networkParameters: NetworkParameters, initialiseSerialization: Boolean, inMemoryDB: Boolean, - cordappsForAllNodes: Set? = null + cordappsForAllNodes: Set? = null ) : this( isDebug, driverDirectory, @@ -584,7 +585,7 @@ data class DriverParameters( fun withNetworkParameters(networkParameters: NetworkParameters): DriverParameters = copy(networkParameters = networkParameters) fun withNotaryCustomOverrides(notaryCustomOverrides: Map): DriverParameters = copy(notaryCustomOverrides = notaryCustomOverrides) fun withInMemoryDB(inMemoryDB: Boolean): DriverParameters = copy(inMemoryDB = inMemoryDB) - fun withCordappsForAllNodes(cordappsForAllNodes: Set?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes) + fun withCordappsForAllNodes(cordappsForAllNodes: Set?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes) fun copy( isDebug: Boolean, @@ -660,7 +661,7 @@ data class DriverParameters( extraCordappPackagesToScan: List, jmxPolicy: JmxPolicy, networkParameters: NetworkParameters, - cordappsForAllNodes: Set? + cordappsForAllNodes: Set? ) = this.copy( isDebug = isDebug, driverDirectory = driverDirectory, @@ -693,7 +694,7 @@ data class DriverParameters( jmxPolicy: JmxPolicy, networkParameters: NetworkParameters, initialiseSerialization: Boolean, - cordappsForAllNodes: Set? + cordappsForAllNodes: Set? ) = this.copy( isDebug = isDebug, driverDirectory = driverDirectory, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt index 1c4c01c3c0..bd1bcb464a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/DriverDSL.kt @@ -6,6 +6,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.internal.concurrent.map import net.corda.node.internal.Node +import net.corda.testing.node.TestCordapp import net.corda.testing.node.User import net.corda.testing.node.NotarySpec import java.nio.file.Path @@ -95,8 +96,8 @@ interface DriverDSL { * @param maximumHeapSize The maximum JVM heap size to use for the node as a [String]. By default a number is interpreted * as being in bytes. Append the letter 'k' or 'K' to the value to indicate Kilobytes, 'm' or 'M' to indicate * megabytes, and 'g' or 'G' to indicate gigabytes. The default value is "512m" = 512 megabytes. - * @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. - * @param regenerateCordappsOnStart Whether existing [TestCorDapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. + * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [DriverDSL]. + * @param regenerateCordappsOnStart Whether existing [TestCordapp]s unique to this node will be re-generated on start. Useful when stopping and restarting the same node. * @return A [CordaFuture] on the [NodeHandle] to the node. The future will complete when the node is available and * it sees all previously started nodes, including the notaries. */ @@ -108,7 +109,7 @@ interface DriverDSL { customOverrides: Map = defaultParameters.customOverrides, startInSameProcess: Boolean? = defaultParameters.startInSameProcess, maximumHeapSize: String = defaultParameters.maximumHeapSize, - additionalCordapps: Set = defaultParameters.additionalCordapps, + additionalCordapps: Collection = defaultParameters.additionalCordapps, regenerateCordappsOnStart: Boolean = defaultParameters.regenerateCordappsOnStart ): CordaFuture diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/TestCorDapp.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/TestCorDapp.kt deleted file mode 100644 index 8dd209c34f..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/TestCorDapp.kt +++ /dev/null @@ -1,85 +0,0 @@ -package net.corda.testing.driver - -import net.corda.core.DoNotImplement -import net.corda.testing.node.internal.MutableTestCorDapp -import java.net.URL -import java.nio.file.Path - -/** - * Represents information about a CorDapp. Used to generate CorDapp JARs in tests. - */ -@DoNotImplement -interface TestCorDapp { - - val name: String - val title: String - val version: String - val vendor: String - - val classes: Set> - - val resources: Set - - fun packageAsJarInDirectory(parentDirectory: Path): Path - - fun packageAsJarWithPath(jarFilePath: Path) - - /** - * Responsible of creating [TestCorDapp]s. - */ - class Factory { - companion object { - - /** - * Returns a builder-style [TestCorDapp] to easily generate different [TestCorDapp]s that have something in common. - */ - @JvmStatic - fun create(name: String, version: String, vendor: String = "R3", title: String = name, classes: Set> = emptySet(), willResourceBeAddedBeToCorDapp: (fullyQualifiedName: String, url: URL) -> Boolean = MutableTestCorDapp.Companion::filterTestCorDappClass): TestCorDapp.Mutable { - - return MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedBeToCorDapp) - } - } - } - - @DoNotImplement - interface Mutable : TestCorDapp { - - fun withName(name: String): TestCorDapp.Mutable - - fun withTitle(title: String): TestCorDapp.Mutable - - fun withVersion(version: String): TestCorDapp.Mutable - - fun withVendor(vendor: String): TestCorDapp.Mutable - - fun withClasses(classes: Set>): TestCorDapp.Mutable - - fun plusPackages(pckgs: Set): TestCorDapp.Mutable - - fun minusPackages(pckgs: Set): TestCorDapp.Mutable - - fun plusPackage(pckg: String): TestCorDapp.Mutable = plusPackages(setOf(pckg)) - - fun minusPackage(pckg: String): TestCorDapp.Mutable = minusPackages(setOf(pckg)) - - fun plusPackage(pckg: Package): TestCorDapp.Mutable = plusPackages(pckg.name) - - fun minusPackage(pckg: Package): TestCorDapp.Mutable = minusPackages(pckg.name) - - operator fun plus(clazz: Class<*>): TestCorDapp.Mutable = withClasses(classes + clazz) - - operator fun minus(clazz: Class<*>): TestCorDapp.Mutable = withClasses(classes - clazz) - - fun plusPackages(pckg: String, vararg pckgs: String): TestCorDapp.Mutable = plusPackages(setOf(pckg, *pckgs)) - - fun plusPackages(pckg: Package, vararg pckgs: Package): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs).map { it.name }.toSet()) - - fun minusPackages(pckg: String, vararg pckgs: String): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs)) - - fun minusPackages(pckg: Package, vararg pckgs: Package): TestCorDapp.Mutable = minusPackages(setOf(pckg, *pckgs).map { it.name }.toSet()) - - fun plusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable - - fun minusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable - } -} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt index 61b0198c2a..3c3ba57aa5 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetwork.kt @@ -16,7 +16,6 @@ import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.config.NodeConfiguration import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME -import net.corda.testing.driver.TestCorDapp import net.corda.testing.node.internal.* import rx.Observable import java.math.BigInteger @@ -34,36 +33,29 @@ import java.util.concurrent.Future * @property entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, * but can be overridden to cause nodes to have stable or colliding identity/service keys. * @property configOverrides Add/override behaviour of the [NodeConfiguration] mock object. - * @property additionalCordapps [TestCorDapp]s that will be added to this node in addition to the ones shared by all nodes, which get specified at [MockNetwork] level. + * @property additionalCordapps [TestCordapp]s that will be added to this node in addition to the ones shared by all nodes, which get specified at [MockNetwork] level. */ @Suppress("unused") -data class MockNodeParameters constructor( +data class MockNodeParameters( val forcedID: Int? = null, val legalName: CordaX500Name? = null, val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val configOverrides: (NodeConfiguration) -> Any? = {}, - val additionalCordapps: Set) { + val additionalCordapps: Collection = emptyList()) { - @JvmOverloads - constructor( - forcedID: Int? = null, - legalName: CordaX500Name? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}, - extraCordappPackages: List = emptyList() - ) : this(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps = cordappsForPackages(extraCordappPackages)) + constructor(forcedID: Int? = null, + legalName: CordaX500Name? = null, + entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), + configOverrides: (NodeConfiguration) -> Any? = {} + ) : this(forcedID, legalName, entropyRoot, configOverrides, emptyList()) fun withForcedID(forcedID: Int?): MockNodeParameters = copy(forcedID = forcedID) fun withLegalName(legalName: CordaX500Name?): MockNodeParameters = copy(legalName = legalName) fun withEntropyRoot(entropyRoot: BigInteger): MockNodeParameters = copy(entropyRoot = entropyRoot) fun withConfigOverrides(configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters = copy(configOverrides = configOverrides) - fun withExtraCordappPackages(extraCordappPackages: List): MockNodeParameters = copy(forcedID = forcedID, legalName = legalName, entropyRoot = entropyRoot, configOverrides = configOverrides, extraCordappPackages = extraCordappPackages) - fun withAdditionalCordapps(additionalCordapps: Set): MockNodeParameters = copy(additionalCordapps = additionalCordapps) + fun withAdditionalCordapps(additionalCordapps: Collection): MockNodeParameters = copy(additionalCordapps = additionalCordapps) fun copy(forcedID: Int?, legalName: CordaX500Name?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?): MockNodeParameters { - return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps = emptySet()) - } - fun copy(forcedID: Int?, legalName: CordaX500Name?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?, extraCordappPackages: List = emptyList()): MockNodeParameters { - return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, extraCordappPackages) + return MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides) } } @@ -293,7 +285,7 @@ inline fun > StartedMockNode.registerResponderFlow( * @property notarySpecs The notaries to use in the mock network. By default you get one mock notary and that is usually sufficient. * @property networkParameters The network parameters to be used by all the nodes. [NetworkParameters.notaries] must be * empty as notaries are defined by [notarySpecs]. - * @property cordappsForAllNodes [TestCorDapp]s that will be added to each node started by the [MockNetwork]. + * @property cordappsForAllNodes [TestCordapp]s that will be added to each node started by the [MockNetwork]. */ @Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") open class MockNetwork( @@ -304,7 +296,7 @@ open class MockNetwork( val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, val notarySpecs: List = defaultParameters.notarySpecs, val networkParameters: NetworkParameters = defaultParameters.networkParameters, - val cordappsForAllNodes: Set = cordappsForPackages(cordappPackages)) { + val cordappsForAllNodes: Collection = cordappsForPackages(cordappPackages)) { @JvmOverloads constructor(cordappPackages: List, parameters: MockNetworkParameters = MockNetworkParameters()) : this(cordappPackages, defaultParameters = parameters) @@ -345,7 +337,9 @@ open class MockNetwork( fun createPartyNode(legalName: CordaX500Name? = null): StartedMockNode = StartedMockNode.create(internalMockNetwork.createPartyNode(legalName)) /** Create a started node with the given parameters. **/ - fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode = StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters))) + fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode { + return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters))) + } /** * Create a started node with the given parameters. @@ -375,13 +369,13 @@ open class MockNetwork( * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, * but can be overridden to cause nodes to have stable or colliding identity/service keys. * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. - * @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork]. + * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork]. */ fun createNode(legalName: CordaX500Name? = null, forcedID: Int? = null, entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), configOverrides: (NodeConfiguration) -> Any? = {}, - additionalCordapps: Set): StartedMockNode { + additionalCordapps: Collection): StartedMockNode { val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps) return StartedMockNode.create(internalMockNetwork.createNode(InternalMockNodeParameters(parameters))) } @@ -417,13 +411,13 @@ open class MockNetwork( * @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value, * but can be overridden to cause nodes to have stable or colliding identity/service keys. * @param configOverrides Add/override behaviour of the [NodeConfiguration] mock object. - * @param additionalCordapps Additional [TestCorDapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork]. + * @param additionalCordapps Additional [TestCordapp]s that this node will have available, in addition to the ones common to all nodes managed by the [MockNetwork]. */ fun createUnstartedNode(legalName: CordaX500Name? = null, forcedID: Int? = null, entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), configOverrides: (NodeConfiguration) -> Any? = {}, - additionalCordapps: Set): UnstartedMockNode { + additionalCordapps: Collection): UnstartedMockNode { val parameters = MockNodeParameters(forcedID, legalName, entropyRoot, configOverrides, additionalCordapps) return UnstartedMockNode.create(internalMockNetwork.createUnstartedNode(InternalMockNodeParameters(parameters))) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index 112cf3f19f..e78d0c12a3 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -34,10 +34,7 @@ import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.TestIdentity import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.MockCordappProvider -import net.corda.testing.node.internal.MockKeyManagementService -import net.corda.testing.node.internal.MockTransactionStorage -import net.corda.testing.node.internal.TestCordappDirectories -import net.corda.testing.node.internal.getCallerPackage +import net.corda.testing.node.internal.* import net.corda.testing.services.MockAttachmentStorage import java.security.KeyPair import java.sql.Connection @@ -70,8 +67,7 @@ open class MockServices private constructor( companion object { private fun cordappLoaderForPackages(packages: Iterable, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader { - - val cordappPaths = TestCordappDirectories.forPackages(packages) + val cordappPaths = cordappsForPackages(packages).map { TestCordappDirectories.getJarDirectory(it) } return JarScanningCordappLoader.fromDirectories(cordappPaths, versionInfo) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt new file mode 100644 index 0000000000..6f881660c1 --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/TestCordapp.kt @@ -0,0 +1,69 @@ +package net.corda.testing.node + +import net.corda.core.DoNotImplement +import net.corda.core.internal.PLATFORM_VERSION +import net.corda.testing.node.internal.TestCordappImpl +import net.corda.testing.node.internal.simplifyScanPackages + +/** + * Represents information about a CorDapp. Used to generate CorDapp JARs in tests. + */ +@DoNotImplement +interface TestCordapp { + /** Returns the name, defaults to "test-cordapp" if not specified. */ + val name: String + + /** Returns the title, defaults to "test-title" if not specified. */ + val title: String + + /** Returns the version string, defaults to "1.0" if not specified. */ + val version: String + + /** Returns the vendor string, defaults to "Corda" if not specified. */ + val vendor: String + + /** Returns the target platform version, defaults to the current platform version if not specified. */ + val targetVersion: Int + + /** Returns the set of package names scanned for this test CorDapp. */ + val packages: Set + + /** Return a copy of this [TestCordapp] but with the specified name. */ + fun withName(name: String): TestCordapp + + /** Return a copy of this [TestCordapp] but with the specified title. */ + fun withTitle(title: String): TestCordapp + + /** Return a copy of this [TestCordapp] but with the specified version string. */ + fun withVersion(version: String): TestCordapp + + /** Return a copy of this [TestCordapp] but with the specified vendor string. */ + fun withVendor(vendor: String): TestCordapp + + /** Return a copy of this [TestCordapp] but with the specified target platform version. */ + fun withTargetVersion(targetVersion: Int): TestCordapp + + class Factory { + companion object { + @JvmStatic + fun fromPackages(vararg packageNames: String): TestCordapp = fromPackages(packageNames.asList()) + + /** + * Create a [TestCordapp] object by scanning the given packages. The meta data on the CorDapp will be the + * default values, which can be specified with the wither methods. + */ + @JvmStatic + fun fromPackages(packageNames: Collection): TestCordapp { + return TestCordappImpl( + name = "test-cordapp", + version = "1.0", + vendor = "Corda", + title = "test-title", + targetVersion = PLATFORM_VERSION, + packages = simplifyScanPackages(packageNames), + classes = emptySet() + ) + } + } + } +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index bd562e41f5..d952d07414 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -28,7 +28,6 @@ import net.corda.node.services.Permissions import net.corda.node.services.config.* import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NodeRegistrationHelper -import net.corda.core.internal.PLATFORM_VERSION import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.addShutdownHook @@ -38,6 +37,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NodeInfoFilesCopier import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme +import net.corda.testing.node.TestCordapp import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.DUMMY_BANK_A_NAME @@ -91,7 +91,7 @@ class DriverDSLImpl( val networkParameters: NetworkParameters, val notaryCustomOverrides: Map, val inMemoryDB: Boolean, - val cordappsForAllNodes: Set + val cordappsForAllNodes: Collection ) : InternalDriverDSL { private var _executorService: ScheduledExecutorService? = null @@ -184,7 +184,25 @@ class DriverDSLImpl( } } - override fun startNode(defaultParameters: NodeParameters, providedName: CordaX500Name?, rpcUsers: List, verifierType: VerifierType, customOverrides: Map, startInSameProcess: Boolean?, maximumHeapSize: String) = startNode(defaultParameters, providedName, rpcUsers, verifierType, customOverrides, startInSameProcess, maximumHeapSize, defaultParameters.additionalCordapps, defaultParameters.regenerateCordappsOnStart) + override fun startNode(defaultParameters: NodeParameters, + providedName: CordaX500Name?, + rpcUsers: List, + verifierType: VerifierType, + customOverrides: Map, + startInSameProcess: Boolean?, + maximumHeapSize: String): CordaFuture { + return startNode( + defaultParameters, + providedName, + rpcUsers, + verifierType, + customOverrides, + startInSameProcess, + maximumHeapSize, + defaultParameters.additionalCordapps, + defaultParameters.regenerateCordappsOnStart + ) + } override fun startNode( defaultParameters: NodeParameters, @@ -194,7 +212,7 @@ class DriverDSLImpl( customOverrides: Map, startInSameProcess: Boolean?, maximumHeapSize: String, - additionalCordapps: Set, + additionalCordapps: Collection, regenerateCordappsOnStart: Boolean ): CordaFuture { val p2pAddress = portAllocation.nextHostAndPort() @@ -225,7 +243,7 @@ class DriverDSLImpl( startInSameProcess: Boolean? = null, maximumHeapSize: String = "512m", p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(), - additionalCordapps: Set = emptySet(), + additionalCordapps: Collection = emptySet(), regenerateCordappsOnStart: Boolean = false): CordaFuture { val rpcAddress = portAllocation.nextHostAndPort() val rpcAdminAddress = portAllocation.nextHostAndPort() @@ -535,16 +553,12 @@ class DriverDSLImpl( } } - private val sharedCordappsDirectories: Iterable by lazy { - TestCordappDirectories.cached(cordappsForAllNodes) - } - private fun startNodeInternal(specifiedConfig: NodeConfig, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String, localNetworkMap: LocalNetworkMap?, - additionalCordapps: Set, + additionalCordapps: Collection, regenerateCordappsOnStart: Boolean = false): CordaFuture { val visibilityHandle = networkVisibilityController.register(specifiedConfig.corda.myLegalName) val baseDirectory = specifiedConfig.corda.baseDirectory.createDirectories() @@ -558,11 +572,17 @@ class DriverDSLImpl( val useHTTPS = specifiedConfig.typesafe.run { hasPath("useHTTPS") && getBoolean("useHTTPS") } - val existingCorDappDirectoriesOption = if (regenerateCordappsOnStart) emptyList() else if (specifiedConfig.typesafe.hasPath(NodeConfiguration.cordappDirectoriesKey)) specifiedConfig.typesafe.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList() + val existingCorDappDirectoriesOption = if (regenerateCordappsOnStart) { + emptyList() + } else if (specifiedConfig.typesafe.hasPath(NodeConfiguration.cordappDirectoriesKey)) { + specifiedConfig.typesafe.getStringList(NodeConfiguration.cordappDirectoriesKey) + } else { + emptyList() + } - val cordappDirectories = existingCorDappDirectoriesOption + sharedCordappsDirectories.map { it.toString() } + TestCordappDirectories.cached(additionalCordapps, regenerateCordappsOnStart).map { it.toString() } + val cordappDirectories = existingCorDappDirectoriesOption + (cordappsForAllNodes + additionalCordapps).map { TestCordappDirectories.getJarDirectory(it).toString() } - val config = NodeConfig(specifiedConfig.typesafe.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories))) + val config = NodeConfig(specifiedConfig.typesafe.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet()))) if (startInProcess ?: startNodesInProcess) { val nodeAndThreadFuture = startInProcessNode(executorService, config) @@ -687,8 +707,13 @@ class DriverDSLImpl( private fun oneOf(array: Array) = array[Random().nextInt(array.size)] - fun cordappsInCurrentAndAdditionalPackages(packagesToScan: Iterable = emptySet()): Set = cordappsForPackages(getCallerPackage() + packagesToScan) - fun cordappsInCurrentAndAdditionalPackages(firstPackage: String, vararg otherPackages: String): Set = cordappsInCurrentAndAdditionalPackages(otherPackages.toList() + firstPackage) + fun cordappsInCurrentAndAdditionalPackages(packagesToScan: Collection = emptySet()): List { + return cordappsForPackages(getCallerPackage() + packagesToScan) + } + + fun cordappsInCurrentAndAdditionalPackages(firstPackage: String, vararg otherPackages: String): List { + return cordappsInCurrentAndAdditionalPackages(otherPackages.asList() + firstPackage) + } private fun startInProcessNode( executorService: ScheduledExecutorService, @@ -1085,7 +1110,7 @@ fun internalDriver( compatibilityZone: CompatibilityZoneParams? = null, notaryCustomOverrides: Map = DriverParameters().notaryCustomOverrides, inMemoryDB: Boolean = DriverParameters().inMemoryDB, - cordappsForAllNodes: Set = DriverParameters().cordappsForAllNodes(), + cordappsForAllNodes: Collection = DriverParameters().cordappsForAllNodes(), dsl: DriverDSLImpl.() -> A ): A { return genericDriver( @@ -1125,7 +1150,7 @@ private fun Config.toNodeOnly(): Config { return if (hasPath("webAddress")) withoutPath("webAddress").withoutPath("useHTTPS") else this } -internal fun DriverParameters.cordappsForAllNodes(): Set = cordappsForAllNodes +internal fun DriverParameters.cordappsForAllNodes(): Collection = cordappsForAllNodes ?: cordappsInCurrentAndAdditionalPackages(extraCordappPackagesToScan) fun DriverDSL.startNode(providedName: CordaX500Name, devMode: Boolean, parameters: NodeParameters = NodeParameters()): CordaFuture { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt index 05ebe55eae..d60f9f6ee1 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt @@ -51,8 +51,8 @@ import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.testing.node.TestCordapp import net.corda.testing.common.internal.testNetworkParameters -import net.corda.testing.driver.TestCorDapp import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.setGlobalSerialization import net.corda.testing.internal.stubs.CertificateStoreStubs @@ -89,7 +89,7 @@ data class InternalMockNodeParameters( val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), val configOverrides: (NodeConfiguration) -> Any? = {}, val version: VersionInfo = MOCK_VERSION_INFO, - val additionalCordapps: Set? = null) { + val additionalCordapps: Collection? = null) { constructor(mockNodeParameters: MockNodeParameters) : this( mockNodeParameters.forcedID, mockNodeParameters.legalName, @@ -148,7 +148,7 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe val testDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), val networkParameters: NetworkParameters = testNetworkParameters(), val defaultFactory: (MockNodeArgs) -> MockNode = { args -> MockNode(args) }, - val cordappsForAllNodes: Set = emptySet(), + val cordappsForAllNodes: Collection = emptySet(), val autoVisibleNodes: Boolean = true) : AutoCloseable { init { // Apache SSHD for whatever reason registers a SFTP FileSystemProvider - which gets loaded by JimFS. @@ -174,10 +174,6 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe } private val sharedUserCount = AtomicInteger(0) - private val sharedCorDappsDirectories: Iterable by lazy { - TestCordappDirectories.cached(cordappsForAllNodes) - } - /** A read only view of the current set of nodes. */ val nodes: List get() = _nodes @@ -453,8 +449,8 @@ open class InternalMockNetwork(defaultParameters: MockNetworkParameters = MockNe parameters.configOverrides(it) } - val cordapps: Set = parameters.additionalCordapps ?: emptySet() - val cordappDirectories = sharedCorDappsDirectories + TestCordappDirectories.cached(cordapps) + val cordapps = (parameters.additionalCordapps ?: emptySet()) + cordappsForAllNodes + val cordappDirectories = cordapps.map { TestCordappDirectories.getJarDirectory(it) }.distinct() doReturn(cordappDirectories).whenever(config).cordappDirectories val node = nodeFactory(MockNodeArgs(config, this, id, parameters.entropyRoot, parameters.version)) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/MutableTestCorDapp.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/MutableTestCorDapp.kt deleted file mode 100644 index d06270c2cd..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/MutableTestCorDapp.kt +++ /dev/null @@ -1,73 +0,0 @@ -package net.corda.testing.node.internal - -import net.corda.core.internal.div -import net.corda.testing.driver.TestCorDapp -import java.io.File -import java.net.URL -import java.nio.file.Path - -internal class MutableTestCorDapp private constructor(override val name: String, override val version: String, override val vendor: String, override val title: String, private val willResourceBeAddedToCorDapp: (String, URL) -> Boolean, private val jarEntries: Set) : TestCorDapp.Mutable { - - constructor(name: String, version: String, vendor: String, title: String, classes: Set>, willResourceBeAddedToCorDapp: (String, URL) -> Boolean) : this(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntriesFromClasses(classes)) - - companion object { - - private const val jarExtension = ".jar" - private const val whitespace = " " - private const val whitespaceReplacement = "_" - private val productionPathSegments = setOf<(String) -> String>({ "out${File.separator}production${File.separator}classes" }, { fullyQualifiedName -> "main${File.separator}${fullyQualifiedName.packageToJarPath()}" }) - private val excludedCordaPackages = setOf("net.corda.core", "net.corda.node") - - fun filterTestCorDappClass(fullyQualifiedName: String, url: URL): Boolean { - - return isTestResource(fullyQualifiedName, url) || !isInExcludedCordaPackage(fullyQualifiedName) - } - - private fun isTestResource(fullyQualifiedName: String, url: URL): Boolean { - - return productionPathSegments.map { it.invoke(fullyQualifiedName) }.none { url.toString().contains(it) } - } - - private fun isInExcludedCordaPackage(packageName: String): Boolean { - - return excludedCordaPackages.any { packageName.startsWith(it) } - } - - private fun jarEntriesFromClasses(classes: Set>): Set { - - val illegal = classes.filter { it.protectionDomain?.codeSource?.location == null } - if (illegal.isNotEmpty()) { - throw IllegalArgumentException("Some classes do not have a location, typically because they are part of Java or Kotlin. Offending types were: ${illegal.joinToString(", ", "[", "]") { it.simpleName }}") - } - return classes.map(Class<*>::jarEntryInfo).toSet() - } - } - - override val classes: Set> = jarEntries.filterIsInstance(JarEntryInfo.ClassJarEntryInfo::class.java).map(JarEntryInfo.ClassJarEntryInfo::clazz).toSet() - - override val resources: Set = jarEntries.map(JarEntryInfo::url).toSet() - - override fun withName(name: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp) - - override fun withTitle(title: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp) - - override fun withVersion(version: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp) - - override fun withVendor(vendor: String) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp) - - override fun withClasses(classes: Set>) = MutableTestCorDapp(name, version, vendor, title, classes, willResourceBeAddedToCorDapp) - - override fun plusPackages(pckgs: Set) = withClasses(pckgs.map { allClassesForPackage(it) }.fold(classes) { all, packageClasses -> all + packageClasses }) - - override fun minusPackages(pckgs: Set) = withClasses(pckgs.map { allClassesForPackage(it) }.fold(classes) { all, packageClasses -> all - packageClasses }) - - override fun plusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable = MutableTestCorDapp(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntries + JarEntryInfo.ResourceJarEntryInfo(fullyQualifiedName, url)) - - override fun minusResource(fullyQualifiedName: String, url: URL): TestCorDapp.Mutable = MutableTestCorDapp(name, version, vendor, title, willResourceBeAddedToCorDapp, jarEntries - JarEntryInfo.ResourceJarEntryInfo(fullyQualifiedName, url)) - - override fun packageAsJarWithPath(jarFilePath: Path) = jarEntries.packageToCorDapp(jarFilePath, name, version, vendor, title, willResourceBeAddedToCorDapp) - - override fun packageAsJarInDirectory(parentDirectory: Path): Path = (parentDirectory / defaultJarName()).also { packageAsJarWithPath(it) } - - private fun defaultJarName(): String = "${name}_$version$jarExtension".replace(whitespace, whitespaceReplacement) -} \ No newline at end of file diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt index 66a4f2408d..dae33f8c4b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt @@ -9,7 +9,6 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.node.NodeInfo import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.node.VersionInfo import net.corda.node.internal.Node import net.corda.node.internal.NodeWithInfo @@ -108,9 +107,9 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi val existingCorDappDirectoriesOption = if (config.hasPath(NodeConfiguration.cordappDirectoriesKey)) config.getStringList(NodeConfiguration.cordappDirectoriesKey) else emptyList() - val cordappDirectories = existingCorDappDirectoriesOption + TestCordappDirectories.cached(cordapps).map { it.toString() } + val cordappDirectories = existingCorDappDirectoriesOption + cordapps.map { TestCordappDirectories.getJarDirectory(it).toString() } - val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories)) + val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet())) val parsedConfig = specificConfig.parseAsNodeConfiguration().also { nodeConfiguration -> val errors = nodeConfiguration.validate() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt index 05b5662a83..38109bf114 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/RPCDriver.kt @@ -24,11 +24,11 @@ import net.corda.node.services.messaging.RPCServerConfiguration import net.corda.nodeapi.RPCApi import net.corda.nodeapi.internal.ArtemisTcpTransport import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT +import net.corda.testing.node.TestCordapp import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.MAX_MESSAGE_SIZE import net.corda.testing.driver.JmxPolicy import net.corda.testing.driver.PortAllocation -import net.corda.testing.driver.TestCorDapp import net.corda.testing.node.NotarySpec import net.corda.testing.node.User import net.corda.testing.node.internal.DriverDSLImpl.Companion.cordappsInCurrentAndAdditionalPackages @@ -118,7 +118,7 @@ fun rpcDriver( networkParameters: NetworkParameters = testNetworkParameters(), notaryCustomOverrides: Map = emptyMap(), inMemoryDB: Boolean = true, - cordappsForAllNodes: Set = cordappsInCurrentAndAdditionalPackages(), + cordappsForAllNodes: Collection = cordappsInCurrentAndAdditionalPackages(), dsl: RPCDriverDSL.() -> A ): A { return genericDriver( diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappDirectories.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappDirectories.kt index 6f261817d2..3cd553de39 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappDirectories.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappDirectories.kt @@ -1,70 +1,46 @@ package net.corda.testing.node.internal +import net.corda.core.crypto.sha256 import net.corda.core.internal.createDirectories import net.corda.core.internal.deleteRecursively import net.corda.core.internal.div -import net.corda.core.internal.exists +import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor -import net.corda.testing.driver.TestCorDapp +import net.corda.testing.node.TestCordapp import java.nio.file.Path import java.nio.file.Paths +import java.util.* import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap - -internal object TestCordappDirectories { +object TestCordappDirectories { private val logger = loggerFor() - private const val whitespace = " " - private const val whitespaceReplacement = "_" + private val whitespace = "\\s".toRegex() - private val cordappsCache: ConcurrentMap, Path> = ConcurrentHashMap, Path>() + private val testCordappsCache = ConcurrentHashMap() - internal fun cached(cordapps: Iterable, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Iterable { - - cordappsDirectory.toFile().deleteOnExit() - return cordapps.map { cached(it, replaceExistingOnes, cordappsDirectory) } - } - - internal fun cached(cordapp: TestCorDapp, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Path { - - cordappsDirectory.toFile().deleteOnExit() - val cacheKey = cordapp.resources.map { it.toExternalForm() }.sorted() - return if (replaceExistingOnes) { - cordappsCache.remove(cacheKey) - cordappsCache.getOrPut(cacheKey) { - - val cordappDirectory = (cordappsDirectory / "${cordapp.name}_${cordapp.version}".replace(whitespace, whitespaceReplacement)).toAbsolutePath() - cordappDirectory.createDirectories() - cordapp.packageAsJarInDirectory(cordappDirectory) - cordappDirectory - } - } else { - cordappsCache.getOrPut(cacheKey) { - - val cordappDirectory = (cordappsDirectory / "${cordapp.name}_${cordapp.version}".replace(whitespace, whitespaceReplacement)).toAbsolutePath() - cordappDirectory.createDirectories() - cordapp.packageAsJarInDirectory(cordappDirectory) - cordappDirectory + fun getJarDirectory(cordapp: TestCordapp, cordappsDirectory: Path = defaultCordappsDirectory): Path { + cordapp as TestCordappImpl + return testCordappsCache.computeIfAbsent(cordapp) { + val cordappDir = (cordappsDirectory / UUID.randomUUID().toString()).createDirectories() + val uniqueScanString = if (cordapp.packages.size == 1 && cordapp.classes.isEmpty()) { + cordapp.packages.first() + } else { + "${cordapp.packages}${cordapp.classes.joinToString { it.name }}".toByteArray().sha256().toString() } + val jarFileName = cordapp.run { "${name}_${vendor}_${title}_${version}_${targetVersion}_$uniqueScanString.jar".replace(whitespace, "-") } + val jarFile = cordappDir / jarFileName + cordapp.packageAsJar(jarFile) + logger.debug { "$cordapp packaged into $jarFile" } + cordappDir } } - internal fun forPackages(packages: Iterable, replaceExistingOnes: Boolean = false, cordappsDirectory: Path = defaultCordappsDirectory): Iterable { - - cordappsDirectory.toFile().deleteOnExit() - val cordapps = simplifyScanPackages(packages).distinct().fold(emptySet()) { all, packageName -> all + testCorDapp(packageName) } - return cached(cordapps, replaceExistingOnes, cordappsDirectory) - } - private val defaultCordappsDirectory: Path by lazy { - val cordappsDirectory = (Paths.get("build") / "tmp" / getTimestampAsDirectoryName() / "generated-test-cordapps").toAbsolutePath() logger.info("Initialising generated test CorDapps directory in $cordappsDirectory") - if (cordappsDirectory.exists()) { - cordappsDirectory.deleteRecursively() - } + cordappsDirectory.toFile().deleteOnExit() + cordappsDirectory.deleteRecursively() cordappsDirectory.createDirectories() - cordappsDirectory } -} \ No newline at end of file +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt new file mode 100644 index 0000000000..831198320c --- /dev/null +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappImpl.kt @@ -0,0 +1,26 @@ +package net.corda.testing.node.internal + +import net.corda.testing.node.TestCordapp + +data class TestCordappImpl(override val name: String, + override val version: String, + override val vendor: String, + override val title: String, + override val targetVersion: Int, + override val packages: Set, + val classes: Set>) : TestCordapp { + + override fun withName(name: String): TestCordappImpl = copy(name = name) + + override fun withVersion(version: String): TestCordappImpl = copy(version = version) + + override fun withVendor(vendor: String): TestCordappImpl = copy(vendor = vendor) + + override fun withTitle(title: String): TestCordappImpl = copy(title = title) + + override fun withTargetVersion(targetVersion: Int): TestCordappImpl = copy(targetVersion = targetVersion) + + fun withClasses(vararg classes: Class<*>): TestCordappImpl { + return copy(classes = classes.filter { clazz -> packages.none { clazz.name.startsWith("$it.") } }.toSet()) + } +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt index 6980e8eaca..5ab15cb556 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/TestCordappsUtils.kt @@ -1,100 +1,34 @@ package net.corda.testing.node.internal import io.github.classgraph.ClassGraph -import net.corda.core.internal.createDirectories -import net.corda.core.internal.deleteIfExists import net.corda.core.internal.outputStream import net.corda.node.internal.cordapp.createTestManifest -import net.corda.testing.driver.TestCorDapp -import org.apache.commons.io.IOUtils -import java.io.OutputStream -import java.net.URI -import java.net.URL +import net.corda.testing.node.TestCordapp import java.nio.file.Path import java.nio.file.attribute.FileTime import java.time.Instant -import java.util.* import java.util.jar.JarOutputStream import java.util.zip.ZipEntry -import java.util.zip.ZipOutputStream import kotlin.reflect.KClass -/** - * Packages some [JarEntryInfo] into a CorDapp JAR with specified [path]. - * @param path The path of the JAR. - * @param willResourceBeAddedBeToCorDapp A filter for the inclusion of [JarEntryInfo] in the JAR. - */ -internal fun Iterable.packageToCorDapp(path: Path, name: String, version: String, vendor: String, title: String = name, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean = { _, _ -> true }) { +@JvmField +val FINANCE_CORDAPP: TestCordappImpl = cordappForPackages("net.corda.finance") - var hasContent = false - try { - hasContent = packageToCorDapp(path.outputStream(), name, version, vendor, title, willResourceBeAddedBeToCorDapp) - } finally { - if (!hasContent) { - path.deleteIfExists() - } - } +/** Creates a [TestCordappImpl] for each package. */ +fun cordappsForPackages(vararg packageNames: String): List = cordappsForPackages(packageNames.asList()) + +fun cordappsForPackages(packageNames: Iterable): List { + return simplifyScanPackages(packageNames).map { cordappForPackages(it) } } -/** - * Packages some [JarEntryInfo] into a CorDapp JAR using specified [outputStream]. - * @param outputStream The [OutputStream] for the JAR. - * @param willResourceBeAddedBeToCorDapp A filter for the inclusion of [JarEntryInfo] in the JAR. - */ -internal fun Iterable.packageToCorDapp(outputStream: OutputStream, name: String, version: String, vendor: String, title: String = name, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean = { _, _ -> true }): Boolean { - - val manifest = createTestManifest(name, title, version, vendor) - return JarOutputStream(outputStream, manifest).use { jos -> zip(jos, willResourceBeAddedBeToCorDapp) } +/** Creates a single [TestCordappImpl] containing all the given packges. */ +fun cordappForPackages(vararg packageNames: String): TestCordappImpl { + return TestCordapp.Factory.fromPackages(*packageNames) as TestCordappImpl } -/** - * Transforms a [Class] into a [JarEntryInfo]. - */ -internal fun Class<*>.jarEntryInfo(): JarEntryInfo { - - return JarEntryInfo.ClassJarEntryInfo(this) -} - -/** - * Packages some [TestCorDapp]s under a root [directory], each with it's own JAR. - * @param directory The parent directory in which CorDapp JAR will be created. - */ -fun Iterable.packageInDirectory(directory: Path) { - - directory.createDirectories() - forEach { cordapp -> cordapp.packageAsJarInDirectory(directory) } -} - -/** - * Returns all classes within the [targetPackage]. - */ -fun allClassesForPackage(targetPackage: String): Set> { - return ClassGraph() - .whitelistPackages(targetPackage) - .enableAllInfo() - .scan() - .use { it.allClasses.loadClasses() } - .toSet() -} - -/** - * Maps each package to a [TestCorDapp] with resources found in that package. - */ -fun cordappsForPackages(packages: Iterable): Set { - - return simplifyScanPackages(packages).toSet().fold(emptySet()) { all, packageName -> all + testCorDapp(packageName) } -} - -/** - * Maps each package to a [TestCorDapp] with resources found in that package. - */ -fun cordappsForPackages(firstPackage: String, vararg otherPackages: String): Set { - - return cordappsForPackages(setOf(*otherPackages) + firstPackage) -} +fun cordappForClasses(vararg classes: Class<*>): TestCordappImpl = cordappForPackages().withClasses(*classes) fun getCallerClass(directCallerClass: KClass<*>): Class<*>? { - val stackTrace = Throwable().stackTrace val index = stackTrace.indexOfLast { it.className == directCallerClass.java.name } if (index == -1) return null @@ -105,112 +39,42 @@ fun getCallerClass(directCallerClass: KClass<*>): Class<*>? { } } -fun getCallerPackage(directCallerClass: KClass<*>): String? { - - return getCallerClass(directCallerClass)?.`package`?.name -} - -/** - * Returns a [TestCorDapp] containing resources found in [packageName]. - */ -internal fun testCorDapp(packageName: String): TestCorDapp { - - val uuid = UUID.randomUUID() - val name = "$packageName-$uuid" - val version = "$uuid" - return TestCorDapp.Factory.create(name, version).plusPackage(packageName) -} +fun getCallerPackage(directCallerClass: KClass<*>): String? = getCallerClass(directCallerClass)?.`package`?.name /** * Squashes child packages if the parent is present. Example: ["com.foo", "com.foo.bar"] into just ["com.foo"]. */ -fun simplifyScanPackages(scanPackages: Iterable): List { - - return scanPackages.sorted().fold(emptyList()) { listSoFar, packageName -> +fun simplifyScanPackages(scanPackages: Iterable): Set { + return scanPackages.sorted().fold(emptySet()) { soFar, packageName -> when { - listSoFar.isEmpty() -> listOf(packageName) - packageName.startsWith(listSoFar.last()) -> listSoFar - else -> listSoFar + packageName + soFar.isEmpty() -> setOf(packageName) + packageName.startsWith("${soFar.last()}.") -> soFar + else -> soFar + packageName } } } -/** - * Transforms a class or package name into a path segment. - */ -internal fun String.packageToJarPath() = replace(".", "/") +fun TestCordappImpl.packageAsJar(file: Path) { + // Don't mention "classes" in the error message as that feature is only available internally + require(packages.isNotEmpty() || classes.isNotEmpty()) { "At least one package must be specified" } -private fun Iterable.zip(outputStream: ZipOutputStream, willResourceBeAddedBeToCorDapp: (String, URL) -> Boolean): Boolean { + val scanResult = ClassGraph() + .whitelistPackages(*packages.toTypedArray()) + .whitelistClasses(*classes.map { it.name }.toTypedArray()) + .scan() - val entries = filter { (fullyQualifiedName, url) -> willResourceBeAddedBeToCorDapp(fullyQualifiedName, url) } - if (entries.isNotEmpty()) { - zip(outputStream, entries) - } - return entries.isNotEmpty() -} - -private fun zip(outputStream: ZipOutputStream, allInfo: Iterable) { - - val time = FileTime.from(Instant.EPOCH) - val classLoader = Thread.currentThread().contextClassLoader - allInfo.distinctBy { it.url }.sortedBy { it.url.toExternalForm() }.forEach { info -> - - try { - val entry = ZipEntry(info.entryName).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time) - outputStream.putNextEntry(entry) - classLoader.getResourceAsStream(info.entryName).use { - IOUtils.copy(it, outputStream) + scanResult.use { + val manifest = createTestManifest(name, title, version, vendor, targetVersion) + JarOutputStream(file.outputStream(), manifest).use { jos -> + val time = FileTime.from(Instant.now()) + // The same resource may be found in different locations (this will happen when running from gradle) so just + // pick the first one found. + scanResult.allResources.asMap().forEach { path, resourceList -> + val entry = ZipEntry(path).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time) + jos.putNextEntry(entry) + resourceList[0].open().use { it.copyTo(jos) } + jos.closeEntry() } - } finally { - outputStream.closeEntry() } } } - -/** - * Represents a single resource to be added to a CorDapp JAR. - */ -internal sealed class JarEntryInfo(val fullyQualifiedName: String, val url: URL) { - - abstract val entryName: String - - /** - * Represents a class to be added to a CorDapp JAR. - */ - class ClassJarEntryInfo(val clazz: Class<*>) : JarEntryInfo(clazz.name, clazz.classFileURL()) { - - override val entryName = "${fullyQualifiedName.packageToJarPath()}$fileExtensionSeparator$classFileExtension" - } - - /** - * Represents a resource file to be added to a CorDapp JAR. - */ - class ResourceJarEntryInfo(fullyQualifiedName: String, url: URL) : JarEntryInfo(fullyQualifiedName, url) { - - override val entryName: String - get() { - val extensionIndex = fullyQualifiedName.lastIndexOf(fileExtensionSeparator) - return "${fullyQualifiedName.substring(0 until extensionIndex).packageToJarPath()}${fullyQualifiedName.substring(extensionIndex)}" - } - } - - operator fun component1(): String = fullyQualifiedName - - operator fun component2(): URL = url - - private companion object { - - private const val classFileExtension = "class" - private const val fileExtensionSeparator = "." - private const val whitespace = " " - private const val whitespaceReplacement = "%20" - - private fun Class<*>.classFileURL(): URL { - - require(protectionDomain?.codeSource?.location != null) { "Invalid class $name for test CorDapp. Classes without protection domain cannot be referenced. This typically happens for Java / Kotlin types." } - return URI.create("${protectionDomain.codeSource.location}/${name.packageToJarPath()}$fileExtensionSeparator$classFileExtension".escaped()).toURL() - } - - private fun String.escaped(): String = this.replace(whitespace, whitespaceReplacement) - } -} \ No newline at end of file diff --git a/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/TestCordappsUtilsTest.kt b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/TestCordappsUtilsTest.kt new file mode 100644 index 0000000000..581195dfc8 --- /dev/null +++ b/testing/node-driver/src/test/kotlin/net/corda/testing/node/internal/TestCordappsUtilsTest.kt @@ -0,0 +1,93 @@ +package net.corda.testing.node.internal + +import net.corda.core.internal.inputStream +import net.corda.node.internal.cordapp.get +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import java.nio.file.Path +import java.util.jar.JarInputStream + +class TestCordappsUtilsTest { + @Rule + @JvmField + val tempFolder = TemporaryFolder() + + @Test + fun `test simplifyScanPackages`() { + assertThat(simplifyScanPackages(emptyList())).isEmpty() + assertThat(simplifyScanPackages(listOf("com.foo.bar"))).containsExactlyInAnyOrder("com.foo.bar") + assertThat(simplifyScanPackages(listOf("com.foo", "com.foo"))).containsExactlyInAnyOrder("com.foo") + assertThat(simplifyScanPackages(listOf("com.foo", "com.bar"))).containsExactlyInAnyOrder("com.foo", "com.bar") + assertThat(simplifyScanPackages(listOf("com.foo", "com.foo.bar"))).containsExactlyInAnyOrder("com.foo") + assertThat(simplifyScanPackages(listOf("com.foo.bar", "com.foo"))).containsExactlyInAnyOrder("com.foo") + assertThat(simplifyScanPackages(listOf("com.foobar", "com.foo.bar"))).containsExactlyInAnyOrder("com.foobar", "com.foo.bar") + assertThat(simplifyScanPackages(listOf("com.foobar", "com.foo"))).containsExactlyInAnyOrder("com.foobar", "com.foo") + } + + @Test + fun `packageAsJar writes out the CorDapp info into the manifest`() { + val cordapp = cordappForPackages("net.corda.testing.node.internal") + .withTargetVersion(123) + .withName("TestCordappsUtilsTest") + + val jarFile = packageAsJar(cordapp) + JarInputStream(jarFile.inputStream()).use { + assertThat(it.manifest["Target-Platform-Version"]).isEqualTo("123") + assertThat(it.manifest["Name"]).isEqualTo("TestCordappsUtilsTest") + } + } + + @Test + fun `packageAsJar on leaf package`() { + val entries = packageAsJarThenReadBack(cordappForPackages("net.corda.testing.node.internal")) + + assertThat(entries).contains( + "net/corda/testing/node/internal/TestCordappsUtilsTest.class", + "net/corda/testing/node/internal/resource.txt" // Make sure non-class resource files are also picked up + ).doesNotContain( + "net/corda/testing/node/MockNetworkTest.class" + ) + + // Make sure the MockNetworkTest class does actually exist to ensure the above is not a false-positive + assertThat(javaClass.classLoader.getResource("net/corda/testing/node/MockNetworkTest.class")).isNotNull() + } + + @Test + fun `packageAsJar on package with sub-packages`() { + val entries = packageAsJarThenReadBack(cordappForPackages("net.corda.testing.node")) + + assertThat(entries).contains( + "net/corda/testing/node/internal/TestCordappsUtilsTest.class", + "net/corda/testing/node/internal/resource.txt", + "net/corda/testing/node/MockNetworkTest.class" + ) + } + + @Test + fun `packageAsJar on single class`() { + val entries = packageAsJarThenReadBack(cordappForClasses(InternalMockNetwork::class.java)) + + assertThat(entries).containsOnly("${InternalMockNetwork::class.java.name.replace('.', '/')}.class") + } + + private fun packageAsJar(cordapp: TestCordappImpl): Path { + val jarFile = tempFolder.newFile().toPath() + cordapp.packageAsJar(jarFile) + return jarFile + } + + private fun packageAsJarThenReadBack(cordapp: TestCordappImpl): List { + val jarFile = packageAsJar(cordapp) + val entries = ArrayList() + JarInputStream(jarFile.inputStream()).use { + while (true) { + val e = it.nextJarEntry ?: break + entries += e.name + it.closeEntry() + } + } + return entries + } +} diff --git a/testing/node-driver/src/test/resources/net/corda/testing/node/internal/resource.txt b/testing/node-driver/src/test/resources/net/corda/testing/node/internal/resource.txt new file mode 100644 index 0000000000..e69de29bb2 From acd3490cde130568b6e4e486bd8b4be80b06af21 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Mon, 15 Oct 2018 11:40:10 +0200 Subject: [PATCH 4/8] Updates docs structure. (#4072) --- docs/source/building-a-cordapp-index.rst | 12 +++++++----- docs/source/getting-set-up.rst | 4 ++-- docs/source/quickstart-index.rst | 9 --------- docs/source/tutorial-cordapp.rst | 4 ++-- docs/source/writing-a-cordapp.rst | 4 ++-- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/docs/source/building-a-cordapp-index.rst b/docs/source/building-a-cordapp-index.rst index ad7b5457e5..1586941e93 100644 --- a/docs/source/building-a-cordapp-index.rst +++ b/docs/source/building-a-cordapp-index.rst @@ -5,16 +5,18 @@ CorDapps :maxdepth: 1 cordapp-overview + getting-set-up + tutorial-cordapp + building-a-cordapp-samples writing-a-cordapp + cordapp-build-systems + building-against-master debugging-a-cordapp upgrade-notes upgrading-cordapps - cordapp-build-systems - building-against-master - corda-api secure-coding-guidelines + corda-api flow-cookbook + cheat-sheet vault soft-locking - cheat-sheet - building-a-cordapp-samples diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst index fe0f0f62e8..e6c2c27d52 100644 --- a/docs/source/getting-set-up.rst +++ b/docs/source/getting-set-up.rst @@ -1,5 +1,5 @@ -Getting set up -============== +Getting set up for CorDapp development +====================================== Software requirements --------------------- diff --git a/docs/source/quickstart-index.rst b/docs/source/quickstart-index.rst index 40a2798807..c20b4ed10b 100644 --- a/docs/source/quickstart-index.rst +++ b/docs/source/quickstart-index.rst @@ -1,15 +1,6 @@ Quickstart ========== -.. only:: pdfmode - - .. toctree:: - :caption: Other docs - :maxdepth: 1 - - getting-set-up.rst - tutorial-cordapp.rst - Welcome to the Corda Quickstart Guide. Follow the links below to help get going quickly with Corda. I want to: diff --git a/docs/source/tutorial-cordapp.rst b/docs/source/tutorial-cordapp.rst index 06789ea9a0..805808b761 100644 --- a/docs/source/tutorial-cordapp.rst +++ b/docs/source/tutorial-cordapp.rst @@ -4,8 +4,8 @@ -The example CorDapp -=================== +Running the example CorDapp +=========================== .. contents:: diff --git a/docs/source/writing-a-cordapp.rst b/docs/source/writing-a-cordapp.rst index 02bca02c2b..9678e0f9e6 100644 --- a/docs/source/writing-a-cordapp.rst +++ b/docs/source/writing-a-cordapp.rst @@ -1,5 +1,5 @@ -CorDapp structure -================= +Structuring a CorDapp +===================== .. contents:: From af48612d46ca61254f081d795cb2f9d81784787c Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Mon, 15 Oct 2018 10:40:58 +0100 Subject: [PATCH 5/8] ENT-1906: Fix JavaDoc error for DJVM. (#4063) --- djvm/src/main/java/sandbox/java/lang/ThreadLocal.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/djvm/src/main/java/sandbox/java/lang/ThreadLocal.java b/djvm/src/main/java/sandbox/java/lang/ThreadLocal.java index f416d3db16..8a2853bf82 100644 --- a/djvm/src/main/java/sandbox/java/lang/ThreadLocal.java +++ b/djvm/src/main/java/sandbox/java/lang/ThreadLocal.java @@ -4,8 +4,8 @@ import sandbox.java.util.function.Supplier; /** * Everything inside the sandbox is single-threaded, so this - * implementation of ThreadLocal is sufficient. - * @param + * implementation of ThreadLocal is sufficient. + * @param Underlying type of this thread-local variable. */ @SuppressWarnings({"unused", "WeakerAccess"}) public class ThreadLocal extends Object { From 194969477714286a46f0d13cdf5989fa0da24f12 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 20 Aug 2018 17:58:36 +0200 Subject: [PATCH 6/8] Add design doc on package namespace ownership --- .../package-namespace-ownership.md | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 docs/source/design/data-model-upgrades/package-namespace-ownership.md diff --git a/docs/source/design/data-model-upgrades/package-namespace-ownership.md b/docs/source/design/data-model-upgrades/package-namespace-ownership.md new file mode 100644 index 0000000000..aaffcf7473 --- /dev/null +++ b/docs/source/design/data-model-upgrades/package-namespace-ownership.md @@ -0,0 +1,92 @@ +# Package namespace ownership + +This design document outlines a new Corda feature that allows a compatibility zone to give ownership of parts of the Java package namespace to certain users. + +"*There are only two hard problems in computer science: 1. Cache invalidation, 2. Naming things, 3. Off by one errors*" + + + +## Background + +Corda implements a decentralised database that can be unilaterally extended with new data types and logic by its users, without any involvement by the closest equivalent we have to administrators (the "zone operator"). Even informing them is not required. + +This design minimises the power zone operators have and ensures deploying new apps can be fast and cheap - it's limited only by the speed with which the users themselves can move. But it introduces problematic levels of namespace complexity which can make programming securely harder than in regular non-decentralised programming. + +#### Java namespaces + +A typical Java application, seen from the JVM level, has a flat namespace in which a single string name binds to a single class. In object oriented programming a class defines both a data structure and the code used to enforce various invariants like "a person's age may not be negative", so this allows a developer to reason about what the identifier `com.example.Person` really means throughout the lifetime of his program. + +More complex Java applications may have a nested namespace using classloaders, thus inside a JVM a class is actually a pair of (classloader pointer, class name) and this can be used to support tricks like having two different versions of the same class in use simultaneously. The downside is more complexity for the developer to deal with. When things get mixed up this can surface (in Java 8) as nonsensical error messages like "com.example.Person cannot be casted to com.example.Person". In Java 9 classloaders were finally given names so these errors make more sense. + +#### Corda namespaces + +Corda has an even more complex namespace. States are identified using ordinary Java class names because programmers have to be able to write source code that works with them. But because there's no centralised coordination point and Java package namespacing is just a convention, the true name of a class when loaded and run includes the hash of the JAR that defines it. These "attached JARs" are a property of a *transaction* not a state, thus a state only has specific meaning within the context of a transaction. In this way the code that defines a state/contract can evolve over time without needing to constantly edit the ledger when new versions are released. We call this type of just-in-time selection of final logic when building a transaction as "implicit upgrades". The assumption is that data may live for much longer than code. + +The node maps attachments to class paths to classloaders, which ensures that the JVM's namespace is synced with the ledger during verification and it will interpret names relative to the transaction being processed. States outside of a transaction are bound to these true "decentralised names" using an indirection intended to allow for smooth migration between versions called *contract constraints*. + +A constraint specifies what attachments can be used to implement the state's class name, with differing levels of ambiguity. Therefore a transaction's attachments must satisfy the included state's constraints. This scheme is somewhat complex, but gives developers freedom to combine multiple applications together in an agile environment where software is constantly changing, might be malicious and where trust relationships can be complex. + +The problem is that working with these sorts of sophisticated namespaces is hard, including for platform developers. + +As the Java 8 error message example shows, even implementors of complex namespaces often don't get every detail right. Corda expects developers to understand that a `com.megacorp.token.MegaToken` class they find in a transaction or deserialise out of the vault might *not* have been the same as the `com.megacorp.token.MegaToken` class they had in mind when writing a program. It might be a legitimate later version, but it may also be a totally different class in the worst case written by an adversary e.g. one that gives the adversary the right to spend the token. + +## Goals + +* Provide a way to reduce the complexity of naming and working with names in Corda by allowing for a small amount of centralisation, balanced by a reduction in developer mental load. +* Keep it optional for both zones and developers. +* Allow most developers to work just with ordinary Java class names, without needing to consider the complexities of a decentralised namespace. + +## Non-goals + +* Directly make it easier to work with "decentralised names". This might come later. + +## Design + +To make it harder to accidentally write insecure code, we would like to support a compromise configuration in which a compatibility zone can publish a map of Java package namespaces to public keys. An app/attachment JAR may only define a class in that namespace if it is signed by the given public key. Using this feature would make a zone slightly less decentralised, in order to obtain a significant reduction in mental overhead for developers. + +Example of how the network parameters would be extended, in pseudo-code: + +```kotlin +data class JavaPackageName(name: String) { + init { /* verify 'name' is a valid Java package name */ } +} + +data class NetworkParameters( + ... + val packageOwnership: Map +) +``` + +Where the `PublicKey` object can be any of the algorithms supported by signature constraints. The map defines a set of dotted package names like `com.foo.bar` where any class in that package or any sub-package of that package is considered to match (so `com.foo.bar.baz.boz.Bish` is a match but `com.foo.barrier` does not). + +When a class is loaded from an attachment or application JAR signature checking is enabled. If the package of the class matches one of the owned namespaces, the JAR must be have enough signatures to satisfy the PublicKey (there may need to be more than one if the PublicKey is composite). + +Please note the following: + +* It's OK to have unsigned JARs. +* It's OK to have JARs that are signed, but for which there are no claims in the network parameters. +* It's OK if entries in the map are removed (system becomes more open). If entries in the map are added, this could cause consensus failures if people are still using old unsigned versions of the app. +* The map specifies keys not certificate chains, therefore, the keys do not have to chain off the identity key of a zone member. App developers do not need to be members of a zone for their app to be used there. + +From a privacy and decentralisation perspective, the zone operator *may* learn who is developing apps in their zone or (in cases where a vendor makes a single app and thus it's obvious) which apps are being used. This is not ideal, but there are mitigations: + +* The privacy leak is optional. +* The zone operator still doesn't learn who is using which apps. +* There is no obligation for Java package namespaces to correlate obviously to real world identities or products. For example you could register a trivial "front" domain and claim ownership of that, then use it for your apps. The zone operator would see only a codename. + +#### Claiming a namespace + +The exact mechanism used to claim a namespace is up to the zone operator. A typical approach would be to accept an SSL certificate with the domain in it as proof of domain ownership, or to accept an email from that domain as long as the domain is using DKIM to prevent from header spoofing. + +#### The vault API + +The vault query API is an example of how tricky it can be to manage truly decentralised namespaces. The `Vault.Page` class does not include constraint information for a state. Therefore, if a generic app were to be storing states of many different types to the vault without having the specific apps installed, it might be possible for someone to create a confusing name e.g. an app created by MiniCorp could export a class named `com.megacorp.example.Token` and this would be mapped by the RPC deserialisation logic to the actual MegaCorp app - the RPC client would have no way to know this had happened, even if the user was correctly checking, which it's unlikely they would. + +The `StateMetadata` class can be easily extended to include constraint information, to make safely programming against a decentralised namespace possible. As part of this work this extension will be made. + +But the new field would still need to be used - a subtle detail that would be easy to overlook. Package namespace ownership ensures that if you have an app installed locally on the client side that implements `com.megacorp.example` , then that code is likely to match closely enough with the version that was verified by the node. + + + + + From 8e590cfc55b7d1d1839edf5dc3b8de29c5dfa058 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 12 Oct 2018 15:56:13 +0200 Subject: [PATCH 7/8] Attempt to improve the explanation at the start. --- .../package-namespace-ownership.md | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/source/design/data-model-upgrades/package-namespace-ownership.md b/docs/source/design/data-model-upgrades/package-namespace-ownership.md index aaffcf7473..2878222be1 100644 --- a/docs/source/design/data-model-upgrades/package-namespace-ownership.md +++ b/docs/source/design/data-model-upgrades/package-namespace-ownership.md @@ -18,17 +18,35 @@ A typical Java application, seen from the JVM level, has a flat namespace in whi More complex Java applications may have a nested namespace using classloaders, thus inside a JVM a class is actually a pair of (classloader pointer, class name) and this can be used to support tricks like having two different versions of the same class in use simultaneously. The downside is more complexity for the developer to deal with. When things get mixed up this can surface (in Java 8) as nonsensical error messages like "com.example.Person cannot be casted to com.example.Person". In Java 9 classloaders were finally given names so these errors make more sense. + + #### Corda namespaces -Corda has an even more complex namespace. States are identified using ordinary Java class names because programmers have to be able to write source code that works with them. But because there's no centralised coordination point and Java package namespacing is just a convention, the true name of a class when loaded and run includes the hash of the JAR that defines it. These "attached JARs" are a property of a *transaction* not a state, thus a state only has specific meaning within the context of a transaction. In this way the code that defines a state/contract can evolve over time without needing to constantly edit the ledger when new versions are released. We call this type of just-in-time selection of final logic when building a transaction as "implicit upgrades". The assumption is that data may live for much longer than code. +Corda faces an extension of the Java namespace problem - we have a global namespace in which malicious adversaries might be choosing names to be deliberately confusing. Nothing forces an app developer to follow the standard conventions for Java package or class names - someone could make an app that uses the same class name as one of your own apps. Corda needs to keep these two different classes, from different origins, separated. -The node maps attachments to class paths to classloaders, which ensures that the JVM's namespace is synced with the ledger during verification and it will interpret names relative to the transaction being processed. States outside of a transaction are bound to these true "decentralised names" using an indirection intended to allow for smooth migration between versions called *contract constraints*. +On the core ledger this is done by associating each state with an _attachment_. The attachment is the JAR file that contains the class files used by states. To load a state, a classloader is defined that uses the attachments on a transaction, and then the state class is loaded via that classloader. -A constraint specifies what attachments can be used to implement the state's class name, with differing levels of ambiguity. Therefore a transaction's attachments must satisfy the included state's constraints. This scheme is somewhat complex, but gives developers freedom to combine multiple applications together in an agile environment where software is constantly changing, might be malicious and where trust relationships can be complex. +With this infrastructure in place, the Corda node and JVM can internally keep two classes that share the same name separated. The name of the state is, in effect, a list of attachments (hashes of JAR files) combined with a regular class name. -The problem is that working with these sorts of sophisticated namespaces is hard, including for platform developers. -As the Java 8 error message example shows, even implementors of complex namespaces often don't get every detail right. Corda expects developers to understand that a `com.megacorp.token.MegaToken` class they find in a transaction or deserialise out of the vault might *not* have been the same as the `com.megacorp.token.MegaToken` class they had in mind when writing a program. It might be a legitimate later version, but it may also be a totally different class in the worst case written by an adversary e.g. one that gives the adversary the right to spend the token. + +#### Namespaces and versioning + +Names and namespaces are a critical part of how platforms of any kind handle software evolution. If component A is verifying the precise content of component B, e.g. by hashing it, then there can be no agility - component B can never be upgraded. Sometimes this is what's wanted. But usually you want the indirection of a name or set of names that stands in for some behaviour. Exactly how that behaviour is provided is abstracted away behind the mapping of the namespace to concrete artifacts. + +Versioning and resistance to malicious attack are likewise heavily interrelated, because given two different codebases that export the same names, it's possible that one is a legitimate upgrade which changes the logic behind the names in beneficial ways, and the other is an imposter that changes the logic in malicious ways. It's important to keep the differences straight, which can be hard because by their very nature, two versions of the same app tend to be nearly identical. + + + +#### Namespace complexity + +Reasoning about namespaces is hard and has historically led to security flaws in many platforms. + +Although the Corda namespace system _can_ keep overlapping but distinct apps separated, that unfortunately doesn't mean that everywhere it actually does. In a few places Corda does not currently provide all the data needed to work with full state names, although we are adding this data to RPC in Corda 4. + +Even if Corda was sure to get every detail of this right in every area, a full ecosystem consists of many programs written by app developers - not just contracts and flows, but also RPC clients, bridges from internal systems and so on. It is unreasonable to expect developers to fully keep track of Corda compound names everywhere throughout the entire pipeline of tools and processes that may surround the node: some of them will lose track of the attachments list and end up with only a class name, and others will do things like serialise to JSON in which even type names go missing. + +Although we can work on improving our support and APIs for working with sophisticated compound names, we should also allow people to work with simpler namespaces again - like just Java class names. This involves a small sacrifice of decentralisation but the increase in security is probably worth it for most developers. ## Goals @@ -38,7 +56,7 @@ As the Java 8 error message example shows, even implementors of complex namespac ## Non-goals -* Directly make it easier to work with "decentralised names". This might come later. +* Directly make it easier to work with "decentralised names". This can be a project that comes later. ## Design From 6d4bdb84b94398448505e5eee494e1734517407c Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 15 Oct 2018 12:01:15 +0100 Subject: [PATCH 8/8] Code cleanup, mostly shortening long lines (#4070) --- .../net/corda/core/flows/FinalityFlowTests.kt | 16 ++--- .../flows/WithReferencedStatesFlowTests.kt | 5 +- docs/source/changelog.rst | 63 ++++++++++++------- .../net/corda/docs/java/FlowCookbook.java | 8 +-- .../java/tutorial/helloworld/IOUFlow.java | 10 +-- .../docs/java/tutorial/twoparty/IOUFlow.java | 6 +- .../kotlin/tutorial/helloworld/IOUFlow.kt | 2 +- .../docs/kotlin/tutorial/twoparty/IOUFlow.kt | 1 - docs/source/tutorial-attachments.rst | 4 ++ .../FlowsDrainingModeContentionTest.kt | 30 ++++----- .../events/ScheduledFlowIntegrationTests.kt | 25 ++++++-- .../test/cordapp/v1/FlowCheckpointCordapp.kt | 9 ++- .../ScheduledFlowsDrainingModeTest.kt | 35 ++++++----- .../node/services/FinalityHandlerTest.kt | 8 +-- .../services/ServiceHubConcurrentUsageTest.kt | 12 ++-- .../services/events/ScheduledFlowTests.kt | 6 +- .../vault/VaultSoftLockManagerTest.kt | 18 ++++-- .../net/corda/verification/TestCommsFlow.kt | 13 ++-- .../net/corda/verification/TestNotaryFlow.kt | 13 ++-- 19 files changed, 157 insertions(+), 127 deletions(-) diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index 08d6741580..fd8ceab510 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -2,8 +2,6 @@ package net.corda.core.flows import com.natpryce.hamkrest.and import com.natpryce.hamkrest.assertion.assert -import net.corda.testing.internal.matchers.flow.willReturn -import net.corda.testing.internal.matchers.flow.willThrow import net.corda.core.flows.mixins.WithFinality import net.corda.core.identity.Party import net.corda.core.transactions.SignedTransaction @@ -12,6 +10,8 @@ import net.corda.finance.POUNDS import net.corda.finance.contracts.asset.Cash import net.corda.finance.issuedBy import net.corda.testing.core.* +import net.corda.testing.internal.matchers.flow.willReturn +import net.corda.testing.internal.matchers.flow.willThrow import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.TestStartedNode import net.corda.testing.node.internal.cordappsForPackages @@ -21,7 +21,10 @@ import org.junit.Test class FinalityFlowTests : WithFinality { companion object { private val CHARLIE = TestIdentity(CHARLIE_NAME, 90).party - private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.contracts.asset","net.corda.finance.schemas")) + private val classMockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages( + "net.corda.finance.contracts.asset", + "net.corda.finance.schemas" + )) @JvmStatic @AfterClass @@ -33,7 +36,6 @@ class FinalityFlowTests : WithFinality { private val aliceNode = makeNode(ALICE_NAME) private val bobNode = makeNode(BOB_NAME) - private val alice = aliceNode.info.singleIdentity() private val bob = bobNode.info.singleIdentity() private val notary = mockNet.defaultNotaryIdentity @@ -59,11 +61,9 @@ class FinalityFlowTests : WithFinality { } private fun TestStartedNode.signCashTransactionWith(other: Party): SignedTransaction { - val amount = 1000.POUNDS.issuedBy(alice.ref(0)) + val amount = 1000.POUNDS.issuedBy(info.singleIdentity().ref(0)) val builder = TransactionBuilder(notary) Cash().generateIssue(builder, amount, other, notary) - return services.signInitialTransaction(builder) } - -} \ No newline at end of file +} diff --git a/core/src/test/kotlin/net/corda/core/flows/WithReferencedStatesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/WithReferencedStatesFlowTests.kt index a6d007b549..16be871aa0 100644 --- a/core/src/test/kotlin/net/corda/core/flows/WithReferencedStatesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/WithReferencedStatesFlowTests.kt @@ -55,13 +55,13 @@ internal class CreateRefState : FlowLogic() { } // A flow to update a specific reference state. -internal class UpdateRefState(private val stateAndRef: StateAndRef) : FlowLogic() { +internal class UpdateRefState(private val stateAndRef: StateAndRef) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { val notary = serviceHub.networkMapCache.notaryIdentities.first() val stx = serviceHub.signInitialTransaction(TransactionBuilder(notary = notary).apply { addInputState(stateAndRef) - addOutputState((stateAndRef.state.data as RefState.State).update(), RefState.CONTRACT_ID) + addOutputState(stateAndRef.state.data.update(), RefState.CONTRACT_ID) addCommand(RefState.Update(), listOf(ourIdentity.owningKey)) }) return subFlow(FinalityFlow(stx)) @@ -160,5 +160,4 @@ class WithReferencedStatesFlowTests { val result = useRefTx.getOrThrow() assertEquals(updatedRefState.ref, result.tx.references.single()) } - } diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 9b3e3bb571..f5fd3d3ae3 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -15,7 +15,8 @@ Unreleased * New overload for ``CordaRPCClient.start()`` method allowing to specify target legal identity to use for RPC call. -* Case insensitive vault queries can be specified via a boolean on applicable SQL criteria builder operators. By default queries will be case sensitive. +* Case insensitive vault queries can be specified via a boolean on applicable SQL criteria builder operators. By default + queries will be case sensitive. * Getter added to ``CordaRPCOps`` for the node's network parameters. @@ -32,7 +33,8 @@ Unreleased * "app", "rpc", "p2p" and "unknown" are no longer allowed as uploader values when importing attachments. These are used internally in security sensitive code. -* Introduced ``TestCorDapp`` and utilities to support asymmetric setups for nodes through ``DriverDSL``, ``MockNetwork`` and ``MockServices``. +* Introduced ``TestCorDapp`` and utilities to support asymmetric setups for nodes through ``DriverDSL``, ``MockNetwork`` + and ``MockServices``. * Change type of the ``checkpoint_value`` column. Please check the upgrade-notes on how to update your database. @@ -46,7 +48,8 @@ Unreleased rather than IllegalStateException. * The Corda JPA entities no longer implement java.io.Serializable, as this was causing persistence errors in obscure cases. - Java serialization is disabled globally in the node, but in the unlikely event you were relying on these types being Java serializable please contact us. + Java serialization is disabled globally in the node, but in the unlikely event you were relying on these types being Java + serializable please contact us. * Remove all references to the out-of-process transaction verification. @@ -104,7 +107,8 @@ Unreleased * The node's configuration is only printed on startup if ``devMode`` is ``true``, avoiding the risk of printing passwords in a production setup. -* ``NodeStartup`` will now only print node's configuration if ``devMode`` is ``true``, avoiding the risk of printing passwords in a production setup. +* ``NodeStartup`` will now only print node's configuration if ``devMode`` is ``true``, avoiding the risk of printing passwords + in a production setup. * SLF4J's MDC will now only be printed to the console if not empty. No more log lines ending with "{}". @@ -169,13 +173,15 @@ Unreleased * Added public support for creating ``CordaRPCClient`` using SSL. For this to work the node needs to provide client applications a certificate to be added to a truststore. See :doc:`tutorial-clientrpc-api` -* The node RPC broker opens 2 endpoints that are configured with ``address`` and ``adminAddress``. RPC Clients would connect to the address, while the node will connect - to the adminAddress. Previously if ssl was enabled for RPC the ``adminAddress`` was equal to ``address``. +*The node RPC broker opens 2 endpoints that are configured with ``address`` and ``adminAddress``. RPC Clients would connect + to the address, while the node will connect to the adminAddress. Previously if ssl was enabled for RPC the ``adminAddress`` + was equal to ``address``. * Upgraded H2 to v1.4.197 -* Shell (embedded available only in dev mode or via SSH) connects to the node via RPC instead of using the ``CordaRPCOps`` object directly. - To enable RPC connectivity ensure node’s ``rpcSettings.address`` and ``rpcSettings.adminAddress`` settings are present. +* Shell (embedded available only in dev mode or via SSH) connects to the node via RPC instead of using the ``CordaRPCOps`` + object directly. To enable RPC connectivity ensure node’s ``rpcSettings.address`` and ``rpcSettings.adminAddress`` settings + are present. * Changes to the network bootstrapper: @@ -183,7 +189,8 @@ Unreleased whitelist. * The CorDapp jars are also copied to each nodes' ``cordapps`` directory. -* Errors thrown by a Corda node will now reported to a calling RPC client with attention to serialization and obfuscation of internal data. +* Errors thrown by a Corda node will now reported to a calling RPC client with attention to serialization and obfuscation + of internal data. * Serializing an inner class (non-static nested class in Java, inner class in Kotlin) will be rejected explicitly by the serialization framework. Prior to this change it didn't work, but the error thrown was opaque (complaining about too few arguments @@ -191,13 +198,15 @@ Unreleased reference to the outer class) as per the Java documentation `here `_ we are disallowing this as the paradigm in general makes little sense for contract states. -* Node can be shut down abruptly by ``shutdown`` function in ``CordaRPCOps`` or gracefully (draining flows first) through ``gracefulShutdown`` command from shell. +* Node can be shut down abruptly by ``shutdown`` function in ``CordaRPCOps`` or gracefully (draining flows first) through + ``gracefulShutdown`` command from shell. * API change: ``net.corda.core.schemas.PersistentStateRef`` fields (index and txId) are now non-nullable. The fields were always effectively non-nullable - values were set from non-nullable fields of other objects. The class is used as database Primary Key columns of other entities and databases already impose those columns as non-nullable (even if JPA annotation nullable=false was absent). - In case your Cordapps use this entity class to persist data in own custom tables as non Primary Key columns refer to :doc:`upgrade-notes` for upgrade instructions. + In case your Cordapps use this entity class to persist data in own custom tables as non Primary Key columns refer to + :doc:`upgrade-notes` for upgrade instructions. * Adding a public method to check if a public key satisfies Corda recommended algorithm specs, `Crypto.validatePublicKey(java.security.PublicKey)`. For instance, this method will check if an ECC key lies on a valid curve or if an RSA key is >= 2048bits. This might @@ -248,9 +257,10 @@ Version 3.0 * Per CorDapp configuration is now exposed. ``CordappContext`` now exposes a ``CordappConfig`` object that is populated at CorDapp context creation time from a file source during runtime. -* Introduced Flow Draining mode, in which a node continues executing existing flows, but does not start new. This is to support graceful node shutdown/restarts. - In particular, when this mode is on, new flows through RPC will be rejected, scheduled flows will be ignored, and initial session messages will not be consumed. - This will ensure that the number of checkpoints will strictly diminish with time, allowing for a clean shutdown. +* Introduced Flow Draining mode, in which a node continues executing existing flows, but does not start new. This is to + support graceful node shutdown/restarts. In particular, when this mode is on, new flows through RPC will be rejected, + scheduled flows will be ignored, and initial session messages will not be consumed. This will ensure that the number of + checkpoints will strictly diminish with time, allowing for a clean shutdown. * Make the serialisation finger-printer a pluggable entity rather than hard wiring into the factory @@ -261,17 +271,19 @@ Version 3.0 * Refactored ``NodeConfiguration`` to expose ``NodeRpcOptions`` (using top-level "rpcAddress" property still works with warning). * Modified ``CordaRPCClient`` constructor to take a ``SSLConfiguration?`` additional parameter, defaulted to ``null``. -* Introduced ``CertificateChainCheckPolicy.UsernameMustMatchCommonName`` sub-type, allowing customers to optionally enforce username == CN condition on RPC SSL certificates. +* Introduced ``CertificateChainCheckPolicy.UsernameMustMatchCommonName`` sub-type, allowing customers to optionally enforce + username == CN condition on RPC SSL certificates. * Modified ``DriverDSL`` and sub-types to allow specifying RPC settings for the Node. -* Modified the ``DriverDSL`` to start Cordformation nodes allowing automatic generation of "rpcSettings.adminAddress" in case "rcpSettings.useSsl" is ``false`` (the default). +* Modified the ``DriverDSL`` to start Cordformation nodes allowing automatic generation of "rpcSettings.adminAddress" in case + "rcpSettings.useSsl" is ``false`` (the default). * Introduced ``UnsafeCertificatesFactory`` allowing programmatic generation of X509 certificates for test purposes. * JPA Mapping annotations for States extending ``CommonSchemaV1.LinearState`` and ``CommonSchemaV1.FungibleState`` on the - `participants` collection need to be moved to the actual class. This allows to properly specify the unique table name per a collection. - See: DummyDealStateSchemaV1.PersistentDummyDealState + `participants` collection need to be moved to the actual class. This allows to properly specify the unique table name per + a collection. See: DummyDealStateSchemaV1.PersistentDummyDealState * X.509 certificates now have an extension that specifies the Corda role the certificate is used for, and the role hierarchy is now enforced in the validation code. See ``net.corda.core.internal.CertRole`` for the current implementation @@ -556,7 +568,9 @@ Release 1.0 * Vault query soft locking enhancements and deprecations * removed original ``VaultService`` ``softLockedStates`` query mechanism. - * introduced improved ``SoftLockingCondition`` filterable attribute in ``VaultQueryCriteria`` to enable specification of different soft locking retrieval behaviours (exclusive of soft locked states, soft locked states only, specified by set of lock ids) + * introduced improved ``SoftLockingCondition`` filterable attribute in ``VaultQueryCriteria`` to enable specification of + different soft locking retrieval behaviours (exclusive of soft locked states, soft locked states only, specified by set + of lock ids) * Trader demo now issues cash and commercial paper directly from the bank node, rather than the seller node self-issuing commercial paper but labelling it as if issued by the bank. @@ -586,7 +600,8 @@ Release 1.0 This may require adjusting imports of Cash flow references and also of ``StartFlow`` permission in ``gradle.build`` files. * Removed the concept of relevancy from ``LinearState``. The ``ContractState``'s relevancy to the vault can be determined - by the flow context, the vault will process any transaction from a flow which is not derived from transaction resolution verification. + by the flow context, the vault will process any transaction from a flow which is not derived from transaction resolution + verification. * Removed the tolerance attribute from ``TimeWindowChecker`` and thus, there is no extra tolerance on the notary side anymore. @@ -769,9 +784,11 @@ Milestone 14 * Pagination simplification. Pagination continues to be optional, with following changes: - - If no PageSpecification provided then a maximum of MAX_PAGE_SIZE (200) results will be returned, otherwise we fail-fast with a ``VaultQueryException`` to alert the API user to the need to specify a PageSpecification. - Internally, we no longer need to calculate a results count (thus eliminating an expensive SQL query) unless a PageSpecification is supplied (note: that a value of -1 is returned for total_results in this scenario). - Internally, we now use the AggregateFunction capability to perform the count. + - If no PageSpecification provided then a maximum of MAX_PAGE_SIZE (200) results will be returned, otherwise we fail-fast + with a ``VaultQueryException`` to alert the API user to the need to specify a PageSpecification. Internally, we no + longer need to calculate a results count (thus eliminating an expensive SQL query) unless a PageSpecification is + supplied (note: that a value of -1 is returned for total_results in this scenario). Internally, we now use the + AggregateFunction capability to perform the count. - Paging now starts from 1 (was previously 0). * Additional Sort criteria: by StateRef (or constituents: txId, index) diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java b/docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java index b8cb3a439b..db7ea8818d 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java @@ -28,11 +28,11 @@ import java.security.GeneralSecurityException; import java.security.PublicKey; import java.time.Duration; import java.time.Instant; -import java.util.Collections; import java.util.List; import java.util.Set; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Collections.*; import static net.corda.core.contracts.ContractsDSL.requireThat; import static net.corda.core.crypto.Crypto.generateKeyPair; @@ -528,7 +528,7 @@ public class FlowCookbook { // other required signers using ``CollectSignaturesFlow``. // The responder flow will need to call ``SignTransactionFlow``. // DOCSTART 15 - SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, Collections.emptySet(), SIGS_GATHERING.childProgressTracker())); + SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(twiceSignedTx, emptySet(), SIGS_GATHERING.childProgressTracker())); // DOCEND 15 /*------------------------ @@ -557,7 +557,7 @@ public class FlowCookbook { // ``Arrays.asList(counterpartyPubKey)`` instead of // ``Collections.singletonList(counterpartyPubKey)``. // DOCSTART 54 - onceSignedTx.verifySignaturesExcept(Collections.singletonList(counterpartyPubKey)); + onceSignedTx.verifySignaturesExcept(singletonList(counterpartyPubKey)); // DOCEND 54 // We can also choose to only check the signatures that are @@ -583,7 +583,7 @@ public class FlowCookbook { // We can also choose to send it to additional parties who aren't one // of the state's participants. // DOCSTART 10 - Set additionalParties = Collections.singleton(regulator); + Set additionalParties = singleton(regulator); SignedTransaction notarisedTx2 = subFlow(new FinalityFlow(fullySignedTx, additionalParties, FINALISATION.childProgressTracker())); // DOCEND 10 diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java index fd35ab75ac..c752acd59d 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java @@ -15,7 +15,7 @@ import net.corda.core.utilities.ProgressTracker; import static com.template.TemplateContract.TEMPLATE_CONTRACT_ID; -// Replace TemplateFlow's definition with: +// Replace Initiator's definition with: @InitiatingFlow @StartableByRPC public class IOUFlow extends FlowLogic { @@ -44,7 +44,7 @@ public class IOUFlow extends FlowLogic { @Override public Void call() throws FlowException { // We retrieve the notary identity from the network map. - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // We create the transaction components. IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty); @@ -52,12 +52,12 @@ public class IOUFlow extends FlowLogic { Command cmd = new Command<>(cmdType, getOurIdentity().getOwningKey()); // We create a transaction builder and add the components. - final TransactionBuilder txBuilder = new TransactionBuilder(notary) + TransactionBuilder txBuilder = new TransactionBuilder(notary) .addOutputState(outputState, TEMPLATE_CONTRACT_ID) .addCommand(cmd); // Signing the transaction. - final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder); + SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder); // Finalising the transaction. subFlow(new FinalityFlow(signedTx)); @@ -65,4 +65,4 @@ public class IOUFlow extends FlowLogic { return null; } } -// DOCEND 01 \ No newline at end of file +// DOCEND 01 diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java index a7dd8c6c22..afc88a12fb 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java @@ -43,11 +43,11 @@ public class IOUFlow extends FlowLogic { @Override public Void call() throws FlowException { // We retrieve the notary identity from the network map. - final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); + Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); // DOCSTART 02 // We create a transaction builder. - final TransactionBuilder txBuilder = new TransactionBuilder(); + TransactionBuilder txBuilder = new TransactionBuilder(); txBuilder.setNotary(notary); // We create the transaction components. @@ -63,7 +63,7 @@ public class IOUFlow extends FlowLogic { txBuilder.verify(getServiceHub()); // Signing the transaction. - final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder); + SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder); // Creating a session with the other party. FlowSession otherPartySession = initiateFlow(otherParty); diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/helloworld/IOUFlow.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/helloworld/IOUFlow.kt index 9c92571ce3..d560b5553d 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/helloworld/IOUFlow.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/helloworld/IOUFlow.kt @@ -18,7 +18,7 @@ import net.corda.core.utilities.ProgressTracker import com.template.TemplateContract.TEMPLATE_CONTRACT_ID -// Replace TemplateFlow's definition with: +// Replace Initiator's definition with: @InitiatingFlow @StartableByRPC class IOUFlow(val iouValue: Int, diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUFlow.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUFlow.kt index bffd19472d..f438107c88 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUFlow.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUFlow.kt @@ -14,7 +14,6 @@ import net.corda.core.flows.StartableByRPC import net.corda.core.identity.Party import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker - // DOCEND 01 @InitiatingFlow diff --git a/docs/source/tutorial-attachments.rst b/docs/source/tutorial-attachments.rst index cbd3ad8013..4c01efb226 100644 --- a/docs/source/tutorial-attachments.rst +++ b/docs/source/tutorial-attachments.rst @@ -1,4 +1,8 @@ .. highlight:: kotlin +.. raw:: html + + + Using attachments ================= diff --git a/node/src/integration-test/kotlin/net/corda/node/modes/draining/FlowsDrainingModeContentionTest.kt b/node/src/integration-test/kotlin/net/corda/node/modes/draining/FlowsDrainingModeContentionTest.kt index 72bd435160..24b4c83340 100644 --- a/node/src/integration-test/kotlin/net/corda/node/modes/draining/FlowsDrainingModeContentionTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/modes/draining/FlowsDrainingModeContentionTest.kt @@ -1,10 +1,8 @@ package net.corda.node.modes.draining import co.paralleluniverse.fibers.Suspendable -import net.corda.testMessage.MESSAGE_CONTRACT_PROGRAM_ID -import net.corda.testMessage.Message -import net.corda.testMessage.MessageContract -import net.corda.testMessage.MessageState +import net.corda.RpcInfo +import net.corda.client.rpc.CordaRPCClient import net.corda.core.contracts.Command import net.corda.core.contracts.StateAndContract import net.corda.core.flows.* @@ -15,9 +13,11 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap -import net.corda.RpcInfo -import net.corda.client.rpc.CordaRPCClient import net.corda.node.services.Permissions.Companion.all +import net.corda.testMessage.MESSAGE_CONTRACT_PROGRAM_ID +import net.corda.testMessage.Message +import net.corda.testMessage.MessageContract +import net.corda.testMessage.MessageState import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity @@ -53,7 +53,11 @@ class FlowsDrainingModeContentionTest { @Test fun `draining mode does not deadlock with acks between 2 nodes`() { val message = "Ground control to Major Tom" - driver(DriverParameters(startNodesInProcess = true, portAllocation = portAllocation, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) { + driver(DriverParameters( + startNodesInProcess = true, + portAllocation = portAllocation, + extraCordappPackagesToScan = listOf(MessageState::class.packageName) + )) { val nodeA = startNode(providedName = ALICE_NAME, rpcUsers = users).getOrThrow() val nodeB = startNode(providedName = BOB_NAME, rpcUsers = users).getOrThrow() @@ -70,11 +74,12 @@ class FlowsDrainingModeContentionTest { @StartableByRPC @InitiatingFlow -class ProposeTransactionAndWaitForCommit(private val data: String, private val myRpcInfo: RpcInfo, private val counterParty: Party, private val notary: Party) : FlowLogic() { - +class ProposeTransactionAndWaitForCommit(private val data: String, + private val myRpcInfo: RpcInfo, + private val counterParty: Party, + private val notary: Party) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { - val session = initiateFlow(counterParty) val messageState = MessageState(message = Message(data), by = ourIdentity) val command = Command(MessageContract.Commands.Send(), messageState.participants.map { it.owningKey }) @@ -91,10 +96,8 @@ class ProposeTransactionAndWaitForCommit(private val data: String, private val m @InitiatedBy(ProposeTransactionAndWaitForCommit::class) class SignTransactionTriggerDrainingModeAndFinality(private val session: FlowSession) : FlowLogic() { - @Suspendable override fun call() { - val tx = subFlow(ReceiveTransactionFlow(session)) val signedTx = serviceHub.addSignature(tx) val initiatingRpcInfo = session.receive().unwrap { it } @@ -105,9 +108,8 @@ class SignTransactionTriggerDrainingModeAndFinality(private val session: FlowSes } private fun triggerDrainingModeForInitiatingNode(initiatingRpcInfo: RpcInfo) { - CordaRPCClient(initiatingRpcInfo.address).start(initiatingRpcInfo.username, initiatingRpcInfo.password).use { it.proxy.setFlowsDrainingModeEnabled(true) } } -} \ No newline at end of file +} diff --git a/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt index f858e6189c..c7442a3cc7 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/events/ScheduledFlowIntegrationTests.kt @@ -32,11 +32,14 @@ import kotlin.test.assertEquals class ScheduledFlowIntegrationTests { @StartableByRPC - class InsertInitialStateFlow(private val destination: Party, private val notary: Party, private val identity: Int = 1, private val scheduledFor: Instant? = null) : FlowLogic() { + class InsertInitialStateFlow(private val destination: Party, + private val notary: Party, + private val identity: Int = 1, + private val scheduledFor: Instant? = null) : FlowLogic() { @Suspendable override fun call() { - val scheduledState = ScheduledState(scheduledFor - ?: serviceHub.clock.instant(), ourIdentity, destination, identity.toString()) + val creationTime = scheduledFor ?: serviceHub.clock.instant() + val scheduledState = ScheduledState(creationTime, ourIdentity, destination, identity.toString()) val builder = TransactionBuilder(notary) .addOutputState(scheduledState, DummyContract.PROGRAM_ID) .addCommand(dummyCommand(ourIdentity.owningKey)) @@ -90,8 +93,20 @@ class ScheduledFlowIntegrationTests { val scheduledFor = Instant.now().plusSeconds(10) val initialiseFutures = mutableListOf>() for (i in 0 until N) { - initialiseFutures.add(aliceClient.proxy.startFlow(::InsertInitialStateFlow, bob.nodeInfo.legalIdentities.first(), defaultNotaryIdentity, i, scheduledFor).returnValue) - initialiseFutures.add(bobClient.proxy.startFlow(::InsertInitialStateFlow, alice.nodeInfo.legalIdentities.first(), defaultNotaryIdentity, i + 100, scheduledFor).returnValue) + initialiseFutures.add(aliceClient.proxy.startFlow( + ::InsertInitialStateFlow, + bob.nodeInfo.legalIdentities.first(), + defaultNotaryIdentity, + i, + scheduledFor + ).returnValue) + initialiseFutures.add(bobClient.proxy.startFlow( + ::InsertInitialStateFlow, + alice.nodeInfo.legalIdentities.first(), + defaultNotaryIdentity, + i + 100, + scheduledFor + ).returnValue) } initialiseFutures.getOrThrowAll() diff --git a/node/src/integration-test/kotlin/net/test/cordapp/v1/FlowCheckpointCordapp.kt b/node/src/integration-test/kotlin/net/test/cordapp/v1/FlowCheckpointCordapp.kt index 75ca920b44..e32f106338 100644 --- a/node/src/integration-test/kotlin/net/test/cordapp/v1/FlowCheckpointCordapp.kt +++ b/node/src/integration-test/kotlin/net/test/cordapp/v1/FlowCheckpointCordapp.kt @@ -46,12 +46,12 @@ class SendMessageFlow(private val message: Message, private val notary: Party, p progressTracker.currentStep = FINALISING_TRANSACTION - if (reciepent != null) { + return if (reciepent != null) { val session = initiateFlow(reciepent) subFlow(SendTransactionFlow(session, signedTx)) - return subFlow(FinalityFlow(signedTx, setOf(reciepent), FINALISING_TRANSACTION.childProgressTracker())) + subFlow(FinalityFlow(signedTx, setOf(reciepent), FINALISING_TRANSACTION.childProgressTracker())) } else { - return subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker())) + subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker())) } } } @@ -59,10 +59,9 @@ class SendMessageFlow(private val message: Message, private val notary: Party, p @InitiatedBy(SendMessageFlow::class) class Record(private val session: FlowSession) : FlowLogic() { - @Suspendable override fun call() { val tx = subFlow(ReceiveTransactionFlow(session, statesToRecord = StatesToRecord.ALL_VISIBLE)) serviceHub.addSignature(tx) } -} \ No newline at end of file +} diff --git a/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt b/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt index 9382566ab5..ba34d75539 100644 --- a/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt +++ b/node/src/test/kotlin/net/corda/node/modes/draining/ScheduledFlowsDrainingModeTest.kt @@ -8,8 +8,8 @@ import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.flows.SchedulableFlow import net.corda.core.identity.Party import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.loggerFor import net.corda.testing.contracts.DummyContract import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME @@ -28,6 +28,9 @@ import kotlin.reflect.jvm.jvmName import kotlin.test.fail class ScheduledFlowsDrainingModeTest { + companion object { + private val logger = contextLogger() + } private lateinit var mockNet: InternalMockNetwork private lateinit var aliceNode: TestStartedNode @@ -38,10 +41,6 @@ class ScheduledFlowsDrainingModeTest { private var executor: ScheduledExecutorService? = null - companion object { - private val logger = loggerFor() - } - @Before fun setup() { mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"), threadPerNode = true) @@ -61,7 +60,6 @@ class ScheduledFlowsDrainingModeTest { @Test fun `flows draining mode ignores scheduled flows until unset`() { - val latch = CountDownLatch(1) var shouldFail = true @@ -73,7 +71,8 @@ class ScheduledFlowsDrainingModeTest { .map { update -> update.produced.single().state.data as ScheduledState } scheduledStates.filter { state -> !state.processed }.doOnNext { _ -> - // this is needed because there is a delay between the moment a SchedulableState gets in the Vault and the first time nextScheduledActivity is called + // This is needed because there is a delay between the moment a SchedulableState gets in the Vault and the + // first time nextScheduledActivity is called executor!!.schedule({ logger.info("Disabling flows draining mode") shouldFail = false @@ -96,8 +95,11 @@ class ScheduledFlowsDrainingModeTest { latch.await() } - data class ScheduledState(private val creationTime: Instant, val source: Party, val destination: Party, val processed: Boolean = false, override val linearId: UniqueIdentifier = UniqueIdentifier()) : SchedulableState, LinearState { - + data class ScheduledState(private val creationTime: Instant, + val source: Party, + val destination: Party, + val processed: Boolean = false, + override val linearId: UniqueIdentifier = UniqueIdentifier()) : SchedulableState, LinearState { override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? { return if (!processed) { val logicRef = flowLogicRefFactory.create(ScheduledFlow::class.jvmName, thisStateRef) @@ -111,12 +113,12 @@ class ScheduledFlowsDrainingModeTest { } class InsertInitialStateFlow(private val destination: Party, private val notary: Party) : FlowLogic() { - @Suspendable override fun call() { - val scheduledState = ScheduledState(serviceHub.clock.instant(), ourIdentity, destination) - val builder = TransactionBuilder(notary).addOutputState(scheduledState, DummyContract.PROGRAM_ID).addCommand(dummyCommand(ourIdentity.owningKey)) + val builder = TransactionBuilder(notary) + .addOutputState(scheduledState, DummyContract.PROGRAM_ID) + .addCommand(dummyCommand(ourIdentity.owningKey)) val tx = serviceHub.signInitialTransaction(builder) subFlow(FinalityFlow(tx)) } @@ -124,10 +126,8 @@ class ScheduledFlowsDrainingModeTest { @SchedulableFlow class ScheduledFlow(private val stateRef: StateRef) : FlowLogic() { - @Suspendable override fun call() { - val state = serviceHub.toStateAndRef(stateRef) val scheduledState = state.state.data // Only run flow over states originating on this node @@ -137,9 +137,12 @@ class ScheduledFlowsDrainingModeTest { require(!scheduledState.processed) { "State should not have been previously processed" } val notary = state.state.notary val newStateOutput = scheduledState.copy(processed = true) - val builder = TransactionBuilder(notary).addInputState(state).addOutputState(newStateOutput, DummyContract.PROGRAM_ID).addCommand(dummyCommand(ourIdentity.owningKey)) + val builder = TransactionBuilder(notary) + .addInputState(state) + .addOutputState(newStateOutput, DummyContract.PROGRAM_ID) + .addCommand(dummyCommand(ourIdentity.owningKey)) val tx = serviceHub.signInitialTransaction(builder) subFlow(FinalityFlow(tx, setOf(scheduledState.destination))) } } -} \ No newline at end of file +} diff --git a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt index f055b468fb..9a09548b82 100644 --- a/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/FinalityHandlerTest.kt @@ -21,7 +21,7 @@ import org.junit.After import org.junit.Test class FinalityHandlerTest { - private lateinit var mockNet: InternalMockNetwork + private val mockNet = InternalMockNetwork() @After fun cleanUp() { @@ -32,8 +32,6 @@ class FinalityHandlerTest { fun `sent to flow hospital on error and attempted retry on node restart`() { // Setup a network where only Alice has the finance CorDapp and it sends a cash tx to Bob who doesn't have the // CorDapp. Bob's FinalityHandler will error when validating the tx. - mockNet = InternalMockNetwork() - val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = setOf(FINANCE_CORDAPP))) var bob = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME)) @@ -82,8 +80,6 @@ class FinalityHandlerTest { } private fun TestStartedNode.getTransaction(id: SecureHash): SignedTransaction? { - return database.transaction { - services.validatedTransactions.getTransaction(id) - } + return services.validatedTransactions.getTransaction(id) } } diff --git a/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt b/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt index 4d2b8147b7..df679fcbb5 100644 --- a/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/ServiceHubConcurrentUsageTest.kt @@ -24,8 +24,11 @@ import rx.schedulers.Schedulers import java.util.concurrent.CountDownLatch class ServiceHubConcurrentUsageTest { - - private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.finance.schemas", "net.corda.node.services.vault.VaultQueryExceptionsTests", Cash::class.packageName)) + private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages( + "net.corda.finance.schemas", + "net.corda.node.services.vault.VaultQueryExceptionsTests", + Cash::class.packageName + )) @After fun stopNodes() { @@ -34,7 +37,6 @@ class ServiceHubConcurrentUsageTest { @Test fun `operations requiring a transaction work from another thread`() { - val latch = CountDownLatch(1) var successful = false val initiatingFlow = TestFlow(mockNet.defaultNotaryIdentity) @@ -57,10 +59,8 @@ class ServiceHubConcurrentUsageTest { } class TestFlow(private val notary: Party) : FlowLogic() { - @Suspendable override fun call(): SignedTransaction { - val builder = TransactionBuilder(notary) val issuer = ourIdentity.ref(OpaqueBytes.of(0)) Cash().generateIssue(builder, 10.DOLLARS.issuedBy(issuer), ourIdentity, notary) @@ -68,4 +68,4 @@ class ServiceHubConcurrentUsageTest { return subFlow(FinalityFlow(stx)) } } -} \ No newline at end of file +} diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index c9515977ba..eeb2f0582e 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -33,7 +33,6 @@ import kotlin.test.assertEquals class ScheduledFlowTests { companion object { - const val PAGE_SIZE = 20 val SORTING = Sort(listOf(Sort.SortColumn(SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID), Sort.Direction.DESC))) } @@ -168,6 +167,7 @@ class ScheduledFlowTests { assertTrue("Expect all states have run the scheduled task", statesFromB.all { it.state.data.processed }) } - private fun queryStates(vaultService: VaultService): List> = - vaultService.queryBy(VaultQueryCriteria(), sorting = SORTING).states + private fun queryStates(vaultService: VaultService): List> { + return vaultService.queryBy(VaultQueryCriteria(), sorting = SORTING).states + } } diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index 073800fd0b..5972fcf4df 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -52,7 +52,7 @@ class NodePair(private val mockNet: InternalMockNetwork) { @InitiatingFlow abstract class AbstractClientLogic(nodePair: NodePair) : FlowLogic() { - protected val server = nodePair.server.info.singleIdentity() + private val server = nodePair.server.info.singleIdentity() protected abstract fun callImpl(): T @Suspendable override fun call() = callImpl().also { @@ -82,9 +82,12 @@ class VaultSoftLockManagerTest { private val mockVault = rigorousMock().also { doNothing().whenever(it).softLockRelease(any(), anyOrNull()) } + private val mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages(ContractImpl::class.packageName), defaultFactory = { args -> object : InternalMockNetwork.MockNode(args) { - override fun makeVaultService(keyManagementService: KeyManagementService, services: ServicesForResolution, database: CordaPersistence): VaultServiceInternal { + override fun makeVaultService(keyManagementService: KeyManagementService, + services: ServicesForResolution, + database: CordaPersistence): VaultServiceInternal { val node = this val realVault = super.makeVaultService(keyManagementService, services, database) return object : VaultServiceInternal by realVault { @@ -97,13 +100,11 @@ class VaultSoftLockManagerTest { } } }) + private val nodePair = NodePair(mockNet) - @After - fun tearDown() { - mockNet.stopNodes() - } object CommandDataImpl : CommandData + class ClientLogic(nodePair: NodePair, val state: ContractState) : NodePair.AbstractClientLogic>(nodePair) { override fun callImpl() = run { subFlow(FinalityFlow(serviceHub.signInitialTransaction(TransactionBuilder(notary = ourIdentity).apply { @@ -151,6 +152,11 @@ class VaultSoftLockManagerTest { verifyNoMoreInteractions(mockVault) } + @After + fun tearDown() { + mockNet.stopNodes() + } + @Test fun `plain old state is not soft locked`() = run(false, PlainOldState(nodePair), false) diff --git a/samples/network-verifier/src/main/kotlin/net/corda/verification/TestCommsFlow.kt b/samples/network-verifier/src/main/kotlin/net/corda/verification/TestCommsFlow.kt index 3437cce254..55a3f66109 100644 --- a/samples/network-verifier/src/main/kotlin/net/corda/verification/TestCommsFlow.kt +++ b/samples/network-verifier/src/main/kotlin/net/corda/verification/TestCommsFlow.kt @@ -14,10 +14,9 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap - @StartableByRPC @InitiatingFlow -class TestCommsFlowInitiator(val x500Name: CordaX500Name? = null) : FlowLogic>() { +class TestCommsFlowInitiator(private val x500Name: CordaX500Name? = null) : FlowLogic>() { object SENDING : ProgressTracker.Step("SENDING") object RECIEVED_ALL : ProgressTracker.Step("RECIEVED_ALL") @@ -42,7 +41,7 @@ class TestCommsFlowInitiator(val x500Name: CordaX500Name? = null) : FlowLogic
  • () { +class TestCommsFlowResponder(private val otherSideSession: FlowSession) : FlowLogic() { @Suspendable override fun call() { otherSideSession.send("Hello from: " + serviceHub.myInfo.legalIdentities.first().name.toString()) } - } @CordaSerializable data class CommsTestState(val responses: List, val issuer: AbstractParty) : ContractState { override val participants: List get() = listOf(issuer) - } - @CordaSerializable object CommsTestCommand : CommandData - class CommsTestContract : Contract { override fun verify(tx: LedgerTransaction) { } -} \ No newline at end of file +} diff --git a/samples/network-verifier/src/main/kotlin/net/corda/verification/TestNotaryFlow.kt b/samples/network-verifier/src/main/kotlin/net/corda/verification/TestNotaryFlow.kt index 1dd423ff40..febc4fcfce 100644 --- a/samples/network-verifier/src/main/kotlin/net/corda/verification/TestNotaryFlow.kt +++ b/samples/network-verifier/src/main/kotlin/net/corda/verification/TestNotaryFlow.kt @@ -13,7 +13,6 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker import co.paralleluniverse.fibers.Suspendable - @StartableByRPC class TestNotaryFlow : FlowLogic() { @@ -28,38 +27,34 @@ class TestNotaryFlow : FlowLogic() { override fun call(): String { val issueBuilder = TransactionBuilder() val notary = serviceHub.networkMapCache.notaryIdentities.first() - issueBuilder.notary = notary; + issueBuilder.notary = notary val myIdentity = serviceHub.myInfo.legalIdentities.first() - issueBuilder.addOutputState(NotaryTestState(notary.name.toString(), myIdentity), NotaryTestContract::class.qualifiedName!!) + issueBuilder.addOutputState(NotaryTestState(notary.name.toString(), myIdentity), NotaryTestContract::class.java.name) issueBuilder.addCommand(NotaryTestCommand, myIdentity.owningKey) val signedTx = serviceHub.signInitialTransaction(issueBuilder) val issueResult = subFlow(FinalityFlow(signedTx)) progressTracker.currentStep = ISSUED val destroyBuilder = TransactionBuilder() - destroyBuilder.notary = notary; + destroyBuilder.notary = notary destroyBuilder.addInputState(issueResult.tx.outRefsOfType().first()) destroyBuilder.addCommand(NotaryTestCommand, myIdentity.owningKey) val signedDestroyT = serviceHub.signInitialTransaction(destroyBuilder) val result = subFlow(FinalityFlow(signedDestroyT)) progressTracker.currentStep = DESTROYING progressTracker.currentStep = FINALIZED - return "notarised: " + result.notary.toString() + "::" + result.tx.id + return "notarised: ${result.notary}::${result.tx.id}" } } - @CordaSerializable data class NotaryTestState(val id: String, val issuer: AbstractParty) : ContractState { override val participants: List get() = listOf(issuer) - } - @CordaSerializable object NotaryTestCommand : CommandData - class NotaryTestContract : Contract { override fun verify(tx: LedgerTransaction) { }