diff --git a/.idea/compiler.xml b/.idea/compiler.xml index e37ac97a21..f711367fa9 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -48,14 +48,14 @@ - - + + @@ -95,6 +95,8 @@ + + @@ -222,4 +224,4 @@ - + \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index 73d39166e2..c46714ebfd 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -51,4 +51,21 @@ interface Cordapp { val customSchemas: Set val jarPath: URL val cordappClasses: List + val info: Info + + /** + * CorDapp's information, including vendor and version. + * + * @property shortName Cordapp's shortName + * @property vendor Cordapp's vendor + * @property version Cordapp's version + */ + @DoNotImplement + interface Info { + val shortName: String + val vendor: String + val version: String + + fun hasUnknownFields(): Boolean + } } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt index a6360147a9..ee6ba046c2 100644 --- a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt +++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappImpl.kt @@ -29,8 +29,9 @@ data class CordappImpl( override val serializationWhitelists: List, override val serializationCustomSerializers: List>, override val customSchemas: Set, - override val jarPath: URL) : Cordapp { - override val name: String = jarPath.toPath().fileName.toString().removeSuffix(".jar") + override val jarPath: URL, + override val info: Cordapp.Info = CordappImpl.Info.UNKNOWN, + override val name: String = jarPath.toPath().fileName.toString().removeSuffix(".jar")) : Cordapp { /** * An exhaustive list of all classes relevant to the node within this CorDapp @@ -38,4 +39,16 @@ data class CordappImpl( * TODO: Also add [SchedulableFlow] as a Cordapp class */ override val cordappClasses = ((rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames) + + data class Info(override val shortName: String, override val vendor: String, override val version: String): Cordapp.Info { + companion object { + private const val UNKNOWN_VALUE = "Unknown" + + val UNKNOWN = Info(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE) + } + + override fun hasUnknownFields(): Boolean { + return setOf(shortName, vendor, version).any { it == UNKNOWN_VALUE } + } + } } diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt index 6f5f86a195..2efb6ee4ed 100644 --- a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt +++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt @@ -20,7 +20,6 @@ import org.assertj.core.api.Assertions.assertThat import java.time.Duration class ScenarioState { - companion object { val log = contextLogger() } diff --git a/finance/build.gradle b/finance/build.gradle index 2eaba4c794..c04e8b18a7 100644 --- a/finance/build.gradle +++ b/finance/build.gradle @@ -81,6 +81,19 @@ jar { exclude "META-INF/*.MF" exclude "META-INF/LICENSE" exclude "META-INF/NOTICE" + + manifest { + attributes( + "Manifest-Version": "1.0", + "Name": "net/corda/finance", + "Specification-Title": description, + "Specification-Version": version, + "Specification-Vendor": "R3", + "Implementation-Title": "$group.$baseName", + "Implementation-Version": version, + "Implementation-Vendor": "R3" + ) + } } publish { diff --git a/node/src/main/kotlin/net/corda/node/VersionInfo.kt b/node/src/main/kotlin/net/corda/node/VersionInfo.kt index 5971c0d43c..71b3fda2f8 100644 --- a/node/src/main/kotlin/net/corda/node/VersionInfo.kt +++ b/node/src/main/kotlin/net/corda/node/VersionInfo.kt @@ -24,4 +24,9 @@ data class VersionInfo( /** The exact version control commit ID of the node build. */ val revision: String, /** The node vendor */ - val vendor: String) \ No newline at end of file + val vendor: String) { + + companion object { + val UNKNOWN = VersionInfo(1, "Unknown", "Unknown", "Unknown") + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt b/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt index 2202e2e085..8bd4953e35 100644 --- a/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/EnterpriseNode.kt @@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit open class EnterpriseNode(configuration: NodeConfiguration, versionInfo: VersionInfo, initialiseSerialization: Boolean = true, - cordappLoader: CordappLoader = makeCordappLoader(configuration) + cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo) ) : Node(configuration, versionInfo, initialiseSerialization, cordappLoader) { companion object { private val logger by lazy { loggerFor() } diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 246650e794..f0fdf70f6e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -72,7 +72,7 @@ import kotlin.system.exitProcess open class Node(configuration: NodeConfiguration, versionInfo: VersionInfo, private val initialiseSerialization: Boolean = true, - cordappLoader: CordappLoader = makeCordappLoader(configuration) + cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo) ) : AbstractNode(configuration, createClock(configuration), versionInfo, cordappLoader) { companion object { private val staticLog = contextLogger() @@ -100,10 +100,10 @@ open class Node(configuration: NodeConfiguration, const val scanPackagesSeparator = "," @JvmStatic - protected fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader { + protected fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader { return System.getProperty(scanPackagesSystemProperty)?.let { scanPackages -> - CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator)) - } ?: CordappLoader.createDefault(configuration.baseDirectory) + CordappLoader.createDefaultWithTestPackages(configuration, scanPackages.split(scanPackagesSeparator), versionInfo) + } ?: CordappLoader.createDefault(configuration.baseDirectory, versionInfo) } // TODO Wire up maxMessageSize diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 0991826bef..076c65a92d 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -12,13 +12,19 @@ package net.corda.node.internal import com.jcabi.manifests.Manifests import net.corda.core.crypto.Crypto +import net.corda.core.cordapp.Cordapp import net.corda.core.internal.Emoji import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.randomOrNull import net.corda.core.utilities.loggerFor -import net.corda.node.* +import net.corda.node.CmdLineOptions +import net.corda.node.NodeArgsParser +import net.corda.node.NodeRegistrationOption +import net.corda.node.SerialFilter +import net.corda.node.VersionInfo +import net.corda.node.defaultSerialFilter import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfigurationImpl import net.corda.node.services.config.shouldStartLocalShell @@ -28,8 +34,8 @@ import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NodeRegistrationHelper import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException -import net.corda.tools.shell.InteractiveShell import net.corda.nodeapi.internal.persistence.oracleJdbcDriverSerialFilter +import net.corda.tools.shell.InteractiveShell import org.fusesource.jansi.Ansi import org.fusesource.jansi.AnsiConsole import org.slf4j.bridge.SLF4JBridgeHandler @@ -153,7 +159,7 @@ open class NodeStartup(val args: Array) { return } val startedNode = node.start() - Node.printBasicNodeInfo("Loaded CorDapps", startedNode.services.cordappProvider.cordapps.joinToString { it.name }) + logLoadedCorDapps(startedNode.services.cordappProvider.cordapps) startedNode.internals.nodeReadyFuture.thenMatch({ val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0 val name = startedNode.info.legalIdentitiesAndCerts.first().name.organisation @@ -242,6 +248,17 @@ open class NodeStartup(val args: Array) { ) } + open protected fun logLoadedCorDapps(corDapps: List) { + fun Cordapp.Info.description() = "$shortName version $version by $vendor" + + Node.printBasicNodeInfo("Loaded ${corDapps.size} CorDapp(s)", corDapps.map { it.info }.joinToString(", ", transform = Cordapp.Info::description)) + corDapps.map { it.info }.filter { it.hasUnknownFields() }.let { malformed -> + if (malformed.isNotEmpty()) { + logger.warn("Found ${malformed.size} CorDapp(s) with unknown information. They will be unable to run on Corda in the future.") + } + } + } + private fun enforceSingleNodeIsRunning(baseDirectory: Path) { // Write out our process ID (which may or may not resemble a UNIX process id - to us it's just a string) to a // file that we'll do our best to delete on exit. But if we don't, it'll be overwritten next time. If it already diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index f7c3ec89c6..a7c1a56292 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -14,15 +14,32 @@ import com.github.benmanes.caffeine.cache.Caffeine import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult import net.corda.core.cordapp.Cordapp -import net.corda.core.flows.* -import net.corda.core.internal.* +import net.corda.core.flows.ContractUpgradeFlow +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.SchedulableFlow +import net.corda.core.flows.StartableByRPC +import net.corda.core.flows.StartableByService +import net.corda.core.internal.VisibleForTesting +import net.corda.core.internal.copyTo import net.corda.core.internal.cordapp.CordappImpl +import net.corda.core.internal.createDirectories +import net.corda.core.internal.div +import net.corda.core.internal.exists +import net.corda.core.internal.isRegularFile +import net.corda.core.internal.list +import net.corda.core.internal.objectOrNewInstance +import net.corda.core.internal.outputStream +import net.corda.core.internal.toPath +import net.corda.core.internal.toTypedArray +import net.corda.core.internal.walk import net.corda.core.node.services.CordaService import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import net.corda.core.utilities.contextLogger +import net.corda.node.VersionInfo import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.coreContractClasses @@ -38,6 +55,7 @@ import java.nio.file.attribute.FileTime import java.time.Instant import java.util.* import java.util.concurrent.ConcurrentHashMap +import java.util.jar.JarInputStream import java.util.jar.JarOutputStream import java.util.zip.ZipEntry import kotlin.reflect.KClass @@ -48,7 +66,7 @@ import kotlin.streams.toList * * @property cordappJarPaths The classpath of cordapp JARs */ -class CordappLoader private constructor(private val cordappJarPaths: List) { +class CordappLoader private constructor(private val cordappJarPaths: List, versionInfo: VersionInfo) { val cordapps: List by lazy { loadCordapps() + coreCordapp } val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader) @@ -62,6 +80,22 @@ class CordappLoader private constructor(private val cordappJarPaths: List get() = cordapps.flatMap { it.customSchemas }.toSet() + /** A Cordapp representing the core package which is not scanned automatically. */ + @VisibleForTesting + internal val coreCordapp = CordappImpl( + contractClassNames = listOf(), + initiatedFlows = listOf(), + rpcFlows = coreRPCFlows, + serviceFlows = listOf(), + schedulableFlows = listOf(), + services = listOf(), + serializationWhitelists = listOf(), + serializationCustomSerializers = listOf(), + customSchemas = setOf(), + jarPath = ContractUpgradeFlow.javaClass.protectionDomain.codeSource.location, // Core JAR location + info = CordappImpl.Info("corda-core", versionInfo.vendor, versionInfo.releaseVersion) + ) + companion object { private val logger = contextLogger() /** @@ -75,7 +109,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List, CordappLoader>() @@ -98,12 +132,12 @@ class CordappLoader private constructor(private val cordappJarPaths: List): CordappLoader { + fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader { if (!configuration.devMode) { logger.warn("Package scanning should only occur in dev mode!") } val urls = getNodeCordappURLs(configuration.baseDirectory) + simplifyScanPackages(testPackages).flatMap(this::getPackageURLs) - return cordappLoadersCache.asMap().computeIfAbsent(urls, ::CordappLoader) + return cordappLoadersCache.asMap().computeIfAbsent(urls) { values -> CordappLoader(values, versionInfo) } } /** @@ -114,9 +148,9 @@ class CordappLoader private constructor(private val cordappJarPaths: List): CordappLoader { + fun createWithTestPackages(testPackages: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader { val urls = simplifyScanPackages(testPackages).flatMap(this::getPackageURLs) - return cordappLoadersCache.asMap().computeIfAbsent(urls, ::CordappLoader) + return cordappLoadersCache.asMap().computeIfAbsent(urls) { values -> CordappLoader(values, versionInfo) } } /** @@ -125,7 +159,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List) = CordappLoader(scanJars.map { RestrictedURL(it, null) }) + fun createDevMode(scanJars: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN) = CordappLoader(scanJars.map { RestrictedURL(it, null) }, versionInfo) private fun getPackageURLs(scanPackage: String): List { val resource = scanPackage.replace('.', '/') @@ -148,20 +182,24 @@ class CordappLoader private constructor(private val cordappJarPaths: List - val scanDir = url.toPath() - scanDir.walk { it.forEach { - val entryPath = "$resource/${scanDir.relativize(it).toString().replace('\\', '/')}" - val time = FileTime.from(Instant.EPOCH) - val entry = ZipEntry(entryPath).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time) - jos.putNextEntry(entry) - if (it.isRegularFile()) { - it.copyTo(jos) + val manifest = createTestManifest(resource, scanPackage, uuid) + val scanDir = url.toPath() + JarOutputStream(cordappJar.outputStream(), manifest).use { jos -> + scanDir.walk { + it.forEach { + val entryPath = "$resource/${scanDir.relativize(it).toString().replace('\\', '/')}" + val time = FileTime.from(Instant.EPOCH) + val entry = ZipEntry(entryPath).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time) + jos.putNextEntry(entry) + if (it.isRegularFile()) { + it.copyTo(jos) + } + jos.closeEntry() } - jos.closeEntry() - } } + } } cordappJar } @@ -183,25 +221,13 @@ class CordappLoader private constructor(private val cordappJarPaths: List { return cordappJarPaths.map { + val url = it.url + val name = url.toPath().fileName.toString().removeSuffix(".jar") + val info = url.openStream().let(::JarInputStream).use { it.manifest }.toCordappInfo(name) val scanResult = scanCordapp(it) CordappImpl(findContractClassNames(scanResult), findInitiatedFlows(scanResult), @@ -212,7 +238,9 @@ class CordappLoader private constructor(private val cordappJarPaths: List + unknown = unknown.copy(shortName = shortName) + } + this?.mainAttributes?.getValue("Implementation-Vendor")?.let { vendor -> + unknown = unknown.copy(vendor = vendor) + } + this?.mainAttributes?.getValue("Implementation-Version")?.let { version -> + unknown = unknown.copy(version = version) + } + return unknown +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt b/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt index 6e72fb8e19..9c2557baf2 100644 --- a/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt @@ -64,7 +64,7 @@ class NodeTest { fun `generateAndSaveNodeInfo works`() { val nodeAddress = NetworkHostAndPort("0.1.2.3", 456) val nodeName = CordaX500Name("Manx Blockchain Corp", "Douglas", "IM") - val platformVersion = 789 + val info = VersionInfo(789, "3.0", "SNAPSHOT", "R3") val dataSourceProperties = makeTestDataSourceProperties() val databaseConfig = DatabaseConfig() val configuration = rigorousMock().also { @@ -79,10 +79,8 @@ class NodeTest { doReturn("tsp").whenever(it).trustStorePassword doReturn("ksp").whenever(it).keyStorePassword } - configureDatabase(dataSourceProperties, databaseConfig, rigorousMock()).use { database -> - val node = Node(configuration, rigorousMock().also { - doReturn(platformVersion).whenever(it).platformVersion - }, initialiseSerialization = false) + configureDatabase(dataSourceProperties, databaseConfig, rigorousMock()).use { _ -> + val node = Node(configuration, info, initialiseSerialization = false) assertEquals(node.generateNodeInfo(), node.generateNodeInfo()) // Node info doesn't change (including the serial) } } diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt index b65bde311a..a9b8ef9f2f 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappLoaderTest.kt @@ -51,7 +51,7 @@ class CordappLoaderTest { fun `test that classes that aren't in cordapps aren't loaded`() { // Basedir will not be a corda node directory so the dummy flow shouldn't be recognised as a part of a cordapp val loader = CordappLoader.createDefault(Paths.get(".")) - assertThat(loader.cordapps).containsOnly(CordappLoader.coreCordapp) + assertThat(loader.cordapps).containsOnly(loader.coreCordapp) } @Test @@ -62,7 +62,7 @@ class CordappLoaderTest { val actual = loader.cordapps.toTypedArray() assertThat(actual).hasSize(2) - val actualCordapp = actual.single { it != CordappLoader.coreCordapp } + val actualCordapp = actual.single { it != loader.coreCordapp } assertThat(actualCordapp.contractClassNames).isEqualTo(listOf(isolatedContractId)) assertThat(actualCordapp.initiatedFlows.single().name).isEqualTo("net.corda.finance.contracts.isolated.IsolatedDummyFlow\$Acceptor") assertThat(actualCordapp.rpcFlows).isEmpty() diff --git a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt index e353d27390..86a9ae4af7 100644 --- a/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt +++ b/samples/attachment-demo/src/integration-test/kotlin/net/corda/attachmentdemo/AttachmentDemoTest.kt @@ -41,7 +41,7 @@ class AttachmentDemoTest : IntegrationTest() { @Test fun `attachment demo using a 10MB zip file`() { val numOfExpectedBytes = 10_000_000 - driver(DriverParameters(isDebug = true, portAllocation = PortAllocation.Incremental(20000))) { + driver(DriverParameters(isDebug = true, portAllocation = PortAllocation.Incremental(20000), startNodesInProcess = true)) { val demoUser = listOf(User("demo", "demo", setOf( startFlow(), invokeRpc(CordaRPCOps::attachmentExists),