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),