Syncing Cordapp info code from ENT so that ENT-1731 is fully ported (#3914)

Also, Cordapp.Info has been made internal as it's not used in the public API
This commit is contained in:
Shams Asari 2018-09-10 10:43:00 +01:00 committed by GitHub
parent 4183d55650
commit 83e66d542d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 113 additions and 93 deletions

View File

@ -48,20 +48,4 @@ interface Cordapp {
val jarPath: URL
val cordappClasses: List<String>
val jarHash: SecureHash.SHA256
/**
* 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
}
}
}

View File

@ -24,25 +24,28 @@ data class CordappImpl(
override val customSchemas: Set<MappedSchema>,
override val allFlows: List<Class<out FlowLogic<*>>>,
override val jarPath: URL,
val info: Info,
override val jarHash: SecureHash.SHA256) : Cordapp {
override val name: String = jarPath.toPath().fileName.toString().removeSuffix(".jar")
override val name: String = jarName(jarPath)
companion object {
fun jarName(url: URL): String = url.toPath().fileName.toString().removeSuffix(".jar")
}
/**
* An exhaustive list of all classes relevant to the node within this CorDapp
*
* TODO: Also add [SchedulableFlow] as a Cordapp class
*/
override val cordappClasses = ((rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames)
override val cordappClasses: List<String> = (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 {
// TODO Why a seperate Info class and not just have the fields directly in CordappImpl?
data class Info(val shortName: String, val vendor: String, val version: String) {
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 }
}
fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, version).any { it == UNKNOWN_VALUE }
}
}

View File

@ -71,11 +71,22 @@ jar {
exclude "META-INF/*.MF"
exclude "META-INF/LICENSE"
exclude "META-INF/NOTICE"
manifest {
attributes(
"Manifest-Version": "1.0",
"Specification-Title": description,
"Specification-Version": version,
"Specification-Vendor": "Corda Open Source",
"Implementation-Title": "$group.$baseName",
)
}
}
cordapp {
info {
vendor "R3"
name "net/corda/finance"
vendor "Corda Open Source"
}
}

View File

@ -14,4 +14,9 @@ data class VersionInfo(
/** The exact version control commit ID of the node build. */
val revision: String,
/** The node vendor */
val vendor: String)
val vendor: String) {
companion object {
val UNKNOWN = VersionInfo(1, "Unknown", "Unknown", "Unknown")
}
}

View File

@ -2,6 +2,7 @@ package net.corda.node.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.schemas.MappedSchema
/**
@ -12,7 +13,7 @@ interface CordappLoader {
/**
* Returns all [Cordapp]s found.
*/
val cordapps: List<Cordapp>
val cordapps: List<CordappImpl>
/**
* Returns a [ClassLoader] containing all types from all [Cordapp]s.

View File

@ -88,7 +88,7 @@ class NodeWithInfo(val node: Node, val info: NodeInfo) {
open class Node(configuration: NodeConfiguration,
versionInfo: VersionInfo,
private val initialiseSerialization: Boolean = true,
cordappLoader: CordappLoader = makeCordappLoader(configuration)
cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo)
) : AbstractNode<NodeInfo>(
configuration,
createClock(configuration),
@ -130,9 +130,11 @@ open class Node(configuration: NodeConfiguration,
}
private val sameVmNodeCounter = AtomicInteger()
private fun makeCordappLoader(configuration: NodeConfiguration): CordappLoader {
return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories)
private fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader {
return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories, versionInfo)
}
// TODO: make this configurable.
const val MAX_RPC_MESSAGE_SIZE = 10485760
}

View File

@ -10,6 +10,7 @@ import net.corda.cliutils.ExitCodes
import net.corda.core.crypto.Crypto
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.errors.AddressBindingException
import net.corda.core.utilities.Try
import net.corda.core.utilities.loggerFor
@ -325,7 +326,8 @@ open class NodeStartup: CordaCliWrapper("corda", "Runs a Corda Node") {
}
val nodeInfo = node.start()
Node.printBasicNodeInfo("Loaded CorDapps", node.services.cordappProvider.cordapps.joinToString { it.name })
logLoadedCorDapps(node.services.cordappProvider.cordapps)
node.nodeReadyFuture.thenMatch({
val elapsed = (System.currentTimeMillis() - startTime) / 10 / 100.0
val name = nodeInfo.legalIdentitiesAndCerts.first().name.organisation
@ -407,6 +409,17 @@ open class NodeStartup: CordaCliWrapper("corda", "Runs a Corda Node") {
)
}
open protected fun logLoadedCorDapps(corDapps: List<CordappImpl>) {
fun CordappImpl.Info.description() = "$shortName version $version by $vendor"
Node.printBasicNodeInfo("Loaded ${corDapps.size} CorDapp(s)", corDapps.map { it.info }.joinToString(", ", transform = CordappImpl.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

View File

@ -8,6 +8,7 @@ import net.corda.core.cordapp.CordappContext
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.createCordappContext
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
@ -34,7 +35,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader,
/**
* Current known CorDapps loaded on this node
*/
override val cordapps get() = cordappLoader.cordapps
override val cordapps: List<CordappImpl> get() = cordappLoader.cordapps
fun start(whitelistedContractImplementations: Map<String, List<AttachmentId>>) {
cordappAttachments.putAll(loadContractsIntoAttachmentStore())

View File

@ -3,8 +3,9 @@ package net.corda.node.internal.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.cordapp.CordappImpl
interface CordappProviderInternal : CordappProvider {
val cordapps: List<Cordapp>
val cordapps: List<CordappImpl>
fun getCordappForFlow(flowLogic: FlowLogic<*>): Cordapp?
}

View File

@ -14,6 +14,7 @@ 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.cordapp.CordappLoader
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.nodeapi.internal.coreContractClasses
@ -24,6 +25,7 @@ import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Path
import java.util.*
import java.util.jar.JarInputStream
import kotlin.reflect.KClass
import kotlin.streams.toList
@ -32,9 +34,10 @@ import kotlin.streams.toList
*
* @property cordappJarPaths The classpath of cordapp JARs
*/
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>) : CordappLoaderTemplate() {
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>,
versionInfo: VersionInfo = VersionInfo.UNKNOWN) : CordappLoaderTemplate() {
override val cordapps: List<Cordapp> by lazy { loadCordapps() + coreCordapp }
override val cordapps: List<CordappImpl> by lazy { loadCordapps() + coreCordapp }
override val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader)
@ -54,10 +57,9 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
*
* @param corDappDirectories Directories used to scan for CorDapp JARs.
*/
fun fromDirectories(corDappDirectories: Iterable<Path>): CordappLoader {
fun fromDirectories(corDappDirectories: Iterable<Path>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader {
logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}")
return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() })
return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }, versionInfo)
}
/**
@ -65,7 +67,9 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
*
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
*/
fun fromJarUrls(scanJars: List<URL>) = JarScanningCordappLoader(scanJars.map { it.restricted() })
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader {
return JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo)
}
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
@ -86,31 +90,30 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
ContractUpgradeFlow.Initiate::class.java,
ContractUpgradeFlow.Authorise::class.java,
ContractUpgradeFlow.Deauthorise::class.java)
/** 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(),
allFlows = listOf(),
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
jarHash = SecureHash.allOnesHash
)
}
private fun loadCordapps(): List<Cordapp> {
return cordappJarPaths.map { scanCordapp(it).toCordapp(it) }
}
/** 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(),
info = CordappImpl.Info("corda-core", versionInfo.vendor, versionInfo.releaseVersion),
allFlows = listOf(),
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
jarHash = SecureHash.allOnesHash
)
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): Cordapp {
private fun loadCordapps(): List<CordappImpl> = cordappJarPaths.map { scanCordapp(it).toCordapp(it) }
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl {
val info = url.url.openStream().let(::JarInputStream).use { it.manifest }.toCordappInfo(CordappImpl.jarName(url.url))
return CordappImpl(
findContractClassNames(this),
findInitiatedFlows(this),
@ -123,6 +126,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
findCustomSchemas(this),
findAllFlows(this),
url.url,
info,
getJarHash(url.url)
)
}
@ -268,24 +272,23 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
class MultipleCordappsForFlowException(message: String) : Exception(message)
abstract class CordappLoaderTemplate : CordappLoader {
override val flowCordappMap: Map<Class<out FlowLogic<*>>, Cordapp> by lazy {
cordapps.flatMap { corDapp -> corDapp.allFlows.map { flow -> flow to corDapp } }
.groupBy { it.first }
.mapValues {
if(it.value.size > 1) { throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow ${it.value.first().first.name}: [ ${it.value.joinToString { it.second.name }} ].") }
it.value.single().second
.mapValues { entry ->
if (entry.value.size > 1) {
throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow " +
"${entry.value.first().first.name}: [ ${entry.value.joinToString { it.second.name }} ].")
}
entry.value.single().second
}
}
override val cordappSchemas: Set<MappedSchema> by lazy {
cordapps.flatMap { it.customSchemas }.toSet()
}
override val appClassLoader: ClassLoader by lazy {
URLClassLoader(cordapps.stream().map { it.jarPath }.toTypedArray(), javaClass.classLoader)
}
}

View File

@ -1,13 +1,10 @@
package net.corda.node.internal.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.cordapp.CordappImpl
import java.util.*
import java.util.jar.Attributes
import java.util.jar.Manifest
fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest {
val manifest = Manifest()
// Mandatory manifest attribute. If not present, all other entries are silently skipped.
@ -27,21 +24,19 @@ fun createTestManifest(name: String, title: String, version: String, vendor: Str
}
operator fun Manifest.set(key: String, value: String) {
mainAttributes.putValue(key, value)
}
internal fun Manifest?.toCordappInfo(defaultShortName: String): Cordapp.Info {
var unknown = CordappImpl.Info.UNKNOWN
fun Manifest?.toCordappInfo(defaultShortName: String): CordappImpl.Info {
var info = CordappImpl.Info.UNKNOWN
(this?.mainAttributes?.getValue("Name") ?: defaultShortName).let { shortName ->
unknown = unknown.copy(shortName = shortName)
info = info.copy(shortName = shortName)
}
this?.mainAttributes?.getValue("Implementation-Vendor")?.let { vendor ->
unknown = unknown.copy(vendor = vendor)
info = info.copy(vendor = vendor)
}
this?.mainAttributes?.getValue("Implementation-Version")?.let { version ->
unknown = unknown.copy(version = version)
info = info.copy(version = version)
}
return unknown
}
return info
}

View File

@ -58,12 +58,9 @@ class NodeTest {
@Test
fun `generateAndSaveNodeInfo works`() {
val configuration = createConfig(ALICE_NAME)
val platformVersion = 789
val info = VersionInfo(789, "3.0", "SNAPSHOT", "R3")
configureDatabase(configuration.dataSourceProperties, configuration.database, { null }, { null }).use {
val versionInfo = rigorousMock<VersionInfo>().also {
doReturn(platformVersion).whenever(it).platformVersion
}
val node = Node(configuration, versionInfo, initialiseSerialization = false)
val node = Node(configuration, info, initialiseSerialization = false)
assertEquals(node.generateNodeInfo(), node.generateNodeInfo()) // Node info doesn't change (including the serial)
}
}
@ -87,9 +84,8 @@ class NodeTest {
// Save some NodeInfo
session.save(persistentNodeInfo)
}
val node = Node(configuration, rigorousMock<VersionInfo>().also {
doReturn(10).whenever(it).platformVersion
}, initialiseSerialization = false)
val versionInfo = VersionInfo(10, "3.0", "SNAPSHOT", "R3")
val node = Node(configuration, versionInfo, initialiseSerialization = false)
assertThat(getAllInfos(it)).isNotEmpty
node.clearNetworkMapCache()
assertThat(getAllInfos(it)).isEmpty()
@ -97,7 +93,7 @@ class NodeTest {
}
@Test
fun `Node can start with multiple keypairs for it's identity`() {
fun `Node can start with multiple keypairs for its identity`() {
val configuration = createConfig(ALICE_NAME)
val (nodeInfo1, _) = createNodeInfoAndSigned(ALICE_NAME)
val (nodeInfo2, _) = createNodeInfoAndSigned(ALICE_NAME)
@ -135,6 +131,8 @@ class NodeTest {
val node = Node(configuration, rigorousMock<VersionInfo>().also {
doReturn(10).whenever(it).platformVersion
doReturn("test-vendor").whenever(it).vendor
doReturn("1.0").whenever(it).releaseVersion
}, initialiseSerialization = false)
//this throws an exception with old behaviour

View File

@ -46,7 +46,7 @@ class JarScanningCordappLoaderTest {
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 = JarScanningCordappLoader.fromDirectories(listOf(Paths.get(".")))
assertThat(loader.cordapps).containsOnly(JarScanningCordappLoader.coreCordapp)
assertThat(loader.cordapps).containsOnly(loader.coreCordapp)
}
@Test
@ -57,7 +57,7 @@ class JarScanningCordappLoaderTest {
val actual = loader.cordapps.toTypedArray()
assertThat(actual).hasSize(2)
val actualCordapp = actual.single { it != JarScanningCordappLoader.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()
@ -91,7 +91,7 @@ class JarScanningCordappLoaderTest {
@Test
fun `sub-packages are ignored`() {
val loader = cordappLoaderForPackages(listOf("net.corda", testScanPackage))
val loader = cordappLoaderForPackages(listOf("net.corda.core", testScanPackage))
val cordapps = loader.cordapps.filter { LoaderTestFlow::class.java in it.initiatedFlows }
assertThat(cordapps).hasSize(1)
}

View File

@ -17,7 +17,7 @@ class AttachmentDemoTest {
@Test
fun `attachment demo using a 10MB zip file`() {
val numOfExpectedBytes = 10_000_000
driver(DriverParameters(portAllocation = PortAllocation.Incremental(20000))) {
driver(DriverParameters(portAllocation = PortAllocation.Incremental(20000), startNodesInProcess = true)) {
val demoUser = listOf(User("demo", "demo", setOf(all())))
val (nodeA, nodeB) = listOf(
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = demoUser, maximumHeapSize = "1g"),

View File

@ -33,6 +33,7 @@ class MockCordappProvider(
serializationCustomSerializers = emptyList(),
customSchemas = emptySet(),
jarPath = Paths.get("").toUri().toURL(),
info = CordappImpl.Info.UNKNOWN,
allFlows = emptyList(),
jarHash = SecureHash.allOnesHash)
if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) {
@ -40,7 +41,9 @@ class MockCordappProvider(
}
}
override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? = cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second ?: super.getContractAttachmentID(contractClassName)
override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? {
return cordappRegistry.find { it.first.contractClassNames.contains(contractClassName) }?.second ?: super.getContractAttachmentID(contractClassName)
}
private fun findOrImportAttachment(contractClassNames: List<ContractClassName>, data: ByteArray, attachments: MockAttachmentStorage): AttachmentId {
val existingAttachment = attachments.files.filter {