CORDA-1942: Moved minimumPlatformVersion and targetPlatformVersion to Cordapp (#4416)

Otherwise ContractAndWorkflow ends up having these two duplicated three times.
This commit is contained in:
Shams Asari 2018-12-14 15:48:01 +00:00 committed by GitHub
parent f2b6f96486
commit 1a065ef13d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 188 additions and 184 deletions

View File

@ -2,12 +2,10 @@ package net.corda.core.cordapp
import net.corda.core.DeleteForDJVM import net.corda.core.DeleteForDJVM
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.cordapp.Cordapp.Info.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN_VALUE import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN_VALUE
import net.corda.core.internal.cordapp.CordappImpl.Companion.parseVersion
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializationWhitelist
@ -21,7 +19,8 @@ import java.net.URL
* *
* This will only need to be constructed manually for certain kinds of tests. * This will only need to be constructed manually for certain kinds of tests.
* *
* @property name Cordapp name - derived from the base name of the Cordapp JAR (therefore may not be unique) * @property name Cordapp name - derived from the base name of the Cordapp JAR (therefore may not be unique). This is different to
* [Cordapp.Info.shortName] which should be used instead.
* @property contractClassNames List of contracts * @property contractClassNames List of contracts
* @property initiatedFlows List of initiatable flow classes * @property initiatedFlows List of initiatable flow classes
* @property rpcFlows List of RPC initiable flows classes * @property rpcFlows List of RPC initiable flows classes
@ -33,7 +32,10 @@ import java.net.URL
* @property customSchemas List of custom schemas * @property customSchemas List of custom schemas
* @property allFlows List of all flow classes * @property allFlows List of all flow classes
* @property jarPath The path to the JAR for this CorDapp * @property jarPath The path to the JAR for this CorDapp
* @property cordappClasses An exhaustive list of all classes relevant to the node within this CorDapp
* @property jarHash Hash of the jar * @property jarHash Hash of the jar
* @property minimumPlatformVersion The minimum platform version this CorDapp will run on.
* @property targetPlatformVersion The target platform version this CorDapp was designed and tested on.
*/ */
@DoNotImplement @DoNotImplement
@DeleteForDJVM @DeleteForDJVM
@ -53,11 +55,14 @@ interface Cordapp {
val cordappClasses: List<String> val cordappClasses: List<String>
val info: Info val info: Info
val jarHash: SecureHash.SHA256 val jarHash: SecureHash.SHA256
val minimumPlatformVersion: Int
val targetPlatformVersion: Int
/** /**
* CorDapp's information, including vendor and version. * Further information about the CorDapp extracted from its MANIFEST. The sub-types [Default], [Contract] and [Workflow] determine what
* type of CorDapp this is.
* *
* @property shortName Cordapp's shortName * @property shortName Cordapp's name
* @property vendor Cordapp's vendor * @property vendor Cordapp's vendor
* @property version Cordapp's version * @property version Cordapp's version
*/ */
@ -67,40 +72,44 @@ interface Cordapp {
val vendor: String val vendor: String
val version: String val version: String
val licence: String val licence: String
val minimumPlatformVersion: Int
val targetPlatformVersion: Int
fun hasUnknownFields(): Boolean fun hasUnknownFields(): Boolean
/** CorDapps that do not separate Contracts and Flows into separate jars (pre Corda 4) */ /**
data class Default(override val shortName: String, override val vendor: String, override val version: String, override val minimumPlatformVersion: Int, override val targetPlatformVersion: Int, override val licence: String = UNKNOWN_VALUE) * CorDapps created on V3 which bundle contracts and flows into the same jar. This is effectively
: Info { * [ContractAndWorkflow] but without the integer versioning.
*/
data class Default(override val shortName: String, override val vendor: String, override val version: String, override val licence: String) : Info {
override fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, version).any { it == UNKNOWN_VALUE } override fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, version).any { it == UNKNOWN_VALUE }
override fun toString() = "CorDapp $shortName version $version by $vendor with licence $licence" override fun toString() = "CorDapp $shortName version $version by $vendor with licence $licence"
} }
/** A Contract CorDapp contains contract definitions (state, commands) and verification logic */ /**
data class Contract(override val shortName: String, override val vendor: String, val versionId: Int, override val licence: String, override val minimumPlatformVersion: Int, override val targetPlatformVersion: Int) * A contract CorDapp contains contract definitions (state, commands) and verification logic
: Info { * @property versionId integer version of the CorDapp. [version] is the toString of this.
*/
data class Contract(override val shortName: String, override val vendor: String, val versionId: Int, override val licence: String) : Info {
override val version: String override val version: String
get() = versionId.toString() get() = versionId.toString()
override fun toString() = "Contract CorDapp: $shortName version $version by vendor $vendor with licence $licence" override fun toString() = "Contract CorDapp: $shortName version $versionId by vendor $vendor with licence $licence"
override fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, licence).any { it == UNKNOWN_VALUE } override fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, licence).any { it == UNKNOWN_VALUE }
} }
/** A Workflow CorDapp contains flows and services used to implement business transactions using contracts and states persisted to the immutable ledger */ /**
data class Workflow(override val shortName: String, override val vendor: String, val versionId: Int, override val licence: String, override val minimumPlatformVersion: Int, override val targetPlatformVersion: Int) * A workflow CorDapp contains flows and services used to implement business transactions using contracts and states persisted to the
: Info { * immutable ledger
* @property versionId integer version of the CorDapp. [version] is the toString of this.
*/
data class Workflow(override val shortName: String, override val vendor: String, val versionId: Int, override val licence: String) : Info {
override val version: String override val version: String
get() = versionId.toString() get() = versionId.toString()
override fun toString() = "Workflow CorDapp: $shortName version $version by vendor $vendor with licence $licence" override fun toString() = "Workflow CorDapp: $shortName version $versionId by vendor $vendor with licence $licence"
override fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, licence).any { it == UNKNOWN_VALUE } override fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, licence).any { it == UNKNOWN_VALUE }
} }
/** A CorDapp that includes both Contract and Workflow classes (not recommended) */ /** A CorDapp that includes both contract and workflow classes, and defined in terms of [Contract] and [Workflow]. */
// TODO: future work in Gradle cordapp plugins to enforce separation of Contract and Workflow classes into separate jars // TODO: future work in Gradle cordapp plugins to enforce separation of Contract and Workflow classes into separate jars
data class ContractAndWorkflow(val contract: Contract, val workflow: Workflow, override val minimumPlatformVersion: Int, override val targetPlatformVersion: Int) data class ContractAndWorkflow(val contract: Contract, val workflow: Workflow) : Info {
: Info {
override val shortName: String override val shortName: String
get() = "Contract: ${contract.shortName}, Workflow: ${workflow.shortName}" get() = "Contract: ${contract.shortName}, Workflow: ${workflow.shortName}"
override val vendor: String override val vendor: String
@ -108,7 +117,7 @@ interface Cordapp {
override val licence: String override val licence: String
get() = "Contract: ${contract.licence}, Workflow: ${workflow.licence}" get() = "Contract: ${contract.licence}, Workflow: ${workflow.licence}"
override val version: String override val version: String
get() = "Contract: ${contract.versionId}, Workflow: ${workflow.versionId}" get() = "Contract: ${contract.version}, Workflow: ${workflow.version}"
override fun toString() = "Combined CorDapp: $contract, $workflow" override fun toString() = "Combined CorDapp: $contract, $workflow"
override fun hasUnknownFields(): Boolean = contract.hasUnknownFields() || workflow.hasUnknownFields() override fun hasUnknownFields(): Boolean = contract.hasUnknownFields() || workflow.hasUnknownFields()
} }

View File

@ -2,9 +2,10 @@ package net.corda.core.internal.cordapp
import net.corda.core.DeleteForDJVM import net.corda.core.DeleteForDJVM
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappInvalidVersionException
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.notary.NotaryService import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.toPath import net.corda.core.internal.toPath
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
@ -12,6 +13,7 @@ import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import java.net.URL import java.net.URL
import java.nio.file.Paths
@DeleteForDJVM @DeleteForDJVM
data class CordappImpl( data class CordappImpl(
@ -28,11 +30,20 @@ data class CordappImpl(
override val jarPath: URL, override val jarPath: URL,
override val info: Cordapp.Info, override val info: Cordapp.Info,
override val jarHash: SecureHash.SHA256, override val jarHash: SecureHash.SHA256,
override val minimumPlatformVersion: Int,
override val targetPlatformVersion: Int,
val notaryService: Class<out NotaryService>?, val notaryService: Class<out NotaryService>?,
/** Indicates whether the CorDapp is loaded from external sources, or generated on node startup (virtual). */ /** Indicates whether the CorDapp is loaded from external sources, or generated on node startup (virtual). */
val isLoaded: Boolean = true) : Cordapp { val isLoaded: Boolean = true
) : Cordapp {
override val name: String = jarName(jarPath) override val name: String = jarName(jarPath)
// TODO: Also add [SchedulableFlow] as a Cordapp class
override val cordappClasses: List<String> = run {
val classList = rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass } + notaryService
classList.mapNotNull { it?.name } + contractClassNames
}
companion object { companion object {
fun jarName(url: URL): String = url.toPath().fileName.toString().removeSuffix(".jar") fun jarName(url: URL): String = url.toPath().fileName.toString().removeSuffix(".jar")
@ -54,30 +65,26 @@ data class CordappImpl(
const val DEFAULT_CORDAPP_VERSION = 1 const val DEFAULT_CORDAPP_VERSION = 1
/** used for CorDapps that do not explicitly define attributes */ /** used for CorDapps that do not explicitly define attributes */
val UNKNOWN = Cordapp.Info.Default(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE,1, 1) val UNKNOWN_INFO = Cordapp.Info.Default(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE)
/** Helper method for version identifier parsing */ @VisibleForTesting
fun parseVersion(versionStr: String?, attributeName: String): Int { val TEST_INSTANCE = CordappImpl(
if (versionStr == null) contractClassNames = emptyList(),
throw CordappInvalidVersionException("Target versionId attribute $attributeName not specified. Please specify a whole number starting from 1.") initiatedFlows = emptyList(),
return try { rpcFlows = emptyList(),
val version = versionStr.toInt() serviceFlows = emptyList(),
if (version < 1) { schedulableFlows = emptyList(),
throw CordappInvalidVersionException("Target versionId ($versionStr) for attribute $attributeName must not be smaller than 1.") services = emptyList(),
} serializationWhitelists = emptyList(),
return version serializationCustomSerializers = emptyList(),
} catch (e: NumberFormatException) { customSchemas = emptySet(),
throw CordappInvalidVersionException("Version identifier ($versionStr) for attribute $attributeName must be a whole number starting from 1.") jarPath = Paths.get("").toUri().toURL(),
} info = CordappImpl.UNKNOWN_INFO,
} allFlows = emptyList(),
} jarHash = SecureHash.allOnesHash,
/** minimumPlatformVersion = 1,
* An exhaustive list of all classes relevant to the node within this CorDapp targetPlatformVersion = PLATFORM_VERSION,
* notaryService = null
* TODO: Also add [SchedulableFlow] as a Cordapp class )
*/
override val cordappClasses: List<String> = run {
val classList = rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass } + notaryService
classList.mapNotNull { it?.name } + contractClassNames
} }
} }

View File

@ -11,10 +11,10 @@ import java.util.concurrent.ConcurrentHashMap
*/ */
object CordappInfoResolver { object CordappInfoResolver {
private val logger = loggerFor<CordappInfoResolver>() private val logger = loggerFor<CordappInfoResolver>()
private val cordappClasses: ConcurrentHashMap<String, Set<Cordapp.Info>> = ConcurrentHashMap() private val cordappClasses: ConcurrentHashMap<String, Set<Cordapp>> = ConcurrentHashMap()
// TODO Use the StackWalker API once we migrate to Java 9+ // TODO Use the StackWalker API once we migrate to Java 9+
private var cordappInfoResolver: () -> Cordapp.Info? = { private var cordappResolver: () -> Cordapp? = {
Exception().stackTrace Exception().stackTrace
.mapNotNull { cordappClasses[it.className] } .mapNotNull { cordappClasses[it.className] }
// If there is more than one cordapp registered for a class name we can't determine the "correct" one and return null. // If there is more than one cordapp registered for a class name we can't determine the "correct" one and return null.
@ -26,8 +26,8 @@ object CordappInfoResolver {
* This could happen when trying to run different versions of the same CorDapp on the same node. * This could happen when trying to run different versions of the same CorDapp on the same node.
*/ */
@Synchronized @Synchronized
fun register(classes: List<String>, cordapp: Cordapp.Info) { fun register(cordapp: Cordapp) {
classes.forEach { cordapp.cordappClasses.forEach {
if (cordappClasses.containsKey(it)) { if (cordappClasses.containsKey(it)) {
logger.warn("More than one CorDapp registered for $it.") logger.warn("More than one CorDapp registered for $it.")
cordappClasses[it] = cordappClasses[it]!! + cordapp cordappClasses[it] = cordappClasses[it]!! + cordapp
@ -46,32 +46,28 @@ object CordappInfoResolver {
* @return Information about the CorDapp from which the invoker is called, null if called outside a CorDapp or the * @return Information about the CorDapp from which the invoker is called, null if called outside a CorDapp or the
* calling CorDapp cannot be reliably determined. * calling CorDapp cannot be reliably determined.
*/ */
val currentCordappInfo: Cordapp.Info? get() = cordappInfoResolver() val currentCordapp: Cordapp? get() = cordappResolver()
/** /**
* Returns the target version of the current calling CorDapp. Defaults to the current platform version if there isn't one. * Returns the target version of the current calling CorDapp. Defaults to the current platform version if there isn't one.
*/ */
// TODO It may be the default is wrong and this should be Int? instead // TODO It may be the default is wrong and this should be Int? instead
val currentTargetVersion: Int get() = currentCordappInfo?.targetPlatformVersion ?: PLATFORM_VERSION val currentTargetVersion: Int get() = currentCordapp?.targetPlatformVersion ?: PLATFORM_VERSION
/** /**
* Temporarily apply a fake CorDapp.Info with the given parameters. For use in testing. * Temporarily apply a fake CorDapp with the given parameters. For use in testing.
*/ */
@Synchronized @Synchronized
@VisibleForTesting @VisibleForTesting
fun <T> withCordappInfo(shortName: String = "CordappInfoResolver.withCordappInfo", fun <T> withCordapp(minimumPlatformVersion: Int = 1, targetPlatformVersion: Int = PLATFORM_VERSION, block: () -> T): T {
vendor: String = "Corda", val currentResolver = cordappResolver
version: String = "1", cordappResolver = {
licence: String = "Apache", CordappImpl.TEST_INSTANCE.copy(minimumPlatformVersion = minimumPlatformVersion, targetPlatformVersion = targetPlatformVersion)
minimumPlatformVersion: Int = 1, }
targetPlatformVersion: Int = PLATFORM_VERSION,
block: () -> T): T {
val currentResolver = cordappInfoResolver
cordappInfoResolver = { Cordapp.Info.Default(shortName, vendor, version, minimumPlatformVersion, targetPlatformVersion, licence) }
try { try {
return block() return block()
} finally { } finally {
cordappInfoResolver = currentResolver cordappResolver = currentResolver
} }
} }

View File

@ -1,28 +1,13 @@
package net.corda.core.internal.cordapp package net.corda.core.internal.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_LICENCE
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VENDOR
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_LICENCE
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_VENDOR
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.MIN_PLATFORM_VERSION import net.corda.core.internal.cordapp.CordappImpl.Companion.MIN_PLATFORM_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.TARGET_PLATFORM_VERSION import net.corda.core.internal.cordapp.CordappImpl.Companion.TARGET_PLATFORM_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN_VALUE
import net.corda.core.internal.cordapp.CordappImpl.Companion.parseVersion
import java.util.jar.Attributes import java.util.jar.Attributes
import java.util.jar.Manifest import java.util.jar.Manifest
operator fun Manifest.set(key: String, value: String): String? { operator fun Manifest.set(key: String, value: String): String? = mainAttributes.putValue(key, value)
return mainAttributes.putValue(key, value)
}
operator fun Manifest.set(key: Attributes.Name, value: String): Any? { operator fun Manifest.set(key: Attributes.Name, value: String): Any? = mainAttributes.put(key, value)
return mainAttributes.put(key, value)
}
operator fun Manifest.get(key: String): String? = mainAttributes.getValue(key) operator fun Manifest.get(key: String): String? = mainAttributes.getValue(key)
@ -31,56 +16,3 @@ val Manifest.targetPlatformVersion: Int
val minPlatformVersion = this[MIN_PLATFORM_VERSION]?.toIntOrNull() ?: 1 val minPlatformVersion = this[MIN_PLATFORM_VERSION]?.toIntOrNull() ?: 1
return this[TARGET_PLATFORM_VERSION]?.toIntOrNull() ?: minPlatformVersion return this[TARGET_PLATFORM_VERSION]?.toIntOrNull() ?: minPlatformVersion
} }
fun Manifest.toCordappInfo(defaultName: String): Cordapp.Info {
/** Common attributes */
val minPlatformVersion = this[MIN_PLATFORM_VERSION]?.toIntOrNull() ?: 1
val targetPlatformVersion = this[TARGET_PLATFORM_VERSION]?.toIntOrNull() ?: minPlatformVersion
/** new identifiers (Corda 4) */
// is it a Contract Jar?
val contractInfo =
if (this[CORDAPP_CONTRACT_NAME] != null) {
Cordapp.Info.Contract(shortName = this[CORDAPP_CONTRACT_NAME] ?: defaultName,
vendor = this[CORDAPP_CONTRACT_VENDOR] ?: UNKNOWN_VALUE,
versionId = parseVersion(this[CORDAPP_CONTRACT_VERSION], CORDAPP_CONTRACT_VERSION),
licence = this[CORDAPP_CONTRACT_LICENCE] ?: UNKNOWN_VALUE,
minimumPlatformVersion = minPlatformVersion,
targetPlatformVersion = targetPlatformVersion
)
} else null
// is it a Workflow (flows and services) Jar?
val workflowInfo =
if (this[CORDAPP_WORKFLOW_NAME] != null) {
Cordapp.Info.Workflow(shortName = this[CORDAPP_WORKFLOW_NAME] ?: defaultName,
vendor = this[CORDAPP_WORKFLOW_VENDOR] ?: UNKNOWN_VALUE,
versionId = parseVersion(this[CORDAPP_WORKFLOW_VERSION],CORDAPP_WORKFLOW_VERSION),
licence = this[CORDAPP_WORKFLOW_LICENCE] ?: UNKNOWN_VALUE,
minimumPlatformVersion = minPlatformVersion,
targetPlatformVersion = targetPlatformVersion
)
} else null
// combined Contract and Workflow Jar ?
if (contractInfo != null && workflowInfo != null) {
return Cordapp.Info.ContractAndWorkflow(contractInfo, workflowInfo, minPlatformVersion, targetPlatformVersion)
}
else if (contractInfo != null) return contractInfo
else if (workflowInfo != null) return workflowInfo
/** need to maintain backwards compatibility so use old identifiers if existent */
val shortName = this["Name"] ?: defaultName
val vendor = this["Implementation-Vendor"] ?: UNKNOWN_VALUE
val version = this["Implementation-Version"] ?: UNKNOWN_VALUE
return Cordapp.Info.Default(
shortName = shortName,
vendor = vendor,
version = version,
licence = UNKNOWN_VALUE,
minimumPlatformVersion = minPlatformVersion,
targetPlatformVersion = targetPlatformVersion
)
}

View File

@ -65,7 +65,7 @@ class FinalityFlowTests : WithFinality {
fun `prevent use of the old API if the CorDapp target version is 4`() { fun `prevent use of the old API if the CorDapp target version is 4`() {
val bob = createBob() val bob = createBob()
val stx = aliceNode.issuesCashTo(bob) val stx = aliceNode.issuesCashTo(bob)
val resultFuture = CordappInfoResolver.withCordappInfo(targetPlatformVersion = 4) { val resultFuture = CordappInfoResolver.withCordapp(targetPlatformVersion = 4) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
aliceNode.startFlowAndRunNetwork(FinalityFlow(stx)).resultFuture aliceNode.startFlowAndRunNetwork(FinalityFlow(stx)).resultFuture
} }
@ -79,7 +79,7 @@ class FinalityFlowTests : WithFinality {
// We need Bob to load at least one old CorDapp so that its FinalityHandler is enabled // We need Bob to load at least one old CorDapp so that its FinalityHandler is enabled
val bob = createBob(cordapps = listOf(cordappForPackages("com.template").withTargetVersion(3))) val bob = createBob(cordapps = listOf(cordappForPackages("com.template").withTargetVersion(3)))
val stx = aliceNode.issuesCashTo(bob) val stx = aliceNode.issuesCashTo(bob)
val resultFuture = CordappInfoResolver.withCordappInfo(targetPlatformVersion = 3) { val resultFuture = CordappInfoResolver.withCordapp(targetPlatformVersion = 3) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
aliceNode.startFlowAndRunNetwork(FinalityFlow(stx)).resultFuture aliceNode.startFlowAndRunNetwork(FinalityFlow(stx)).resultFuture
} }

View File

@ -1,6 +1,5 @@
package net.corda.core.internal.cordapp package net.corda.core.internal.cordapp
import net.corda.core.cordapp.Cordapp
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
@ -18,11 +17,15 @@ class CordappInfoResolverTest {
fun `the correct cordapp resolver is used after calling withCordappInfo`() { fun `the correct cordapp resolver is used after calling withCordappInfo`() {
val defaultTargetVersion = 222 val defaultTargetVersion = 222
CordappInfoResolver.register(listOf(javaClass.name), Cordapp.Info.Default("test", "test", "2", 3, defaultTargetVersion)) CordappInfoResolver.register(CordappImpl.TEST_INSTANCE.copy(
contractClassNames = listOf(javaClass.name),
minimumPlatformVersion = 3,
targetPlatformVersion = defaultTargetVersion
))
assertEquals(defaultTargetVersion, CordappInfoResolver.currentTargetVersion) assertEquals(defaultTargetVersion, CordappInfoResolver.currentTargetVersion)
val expectedTargetVersion = 555 val expectedTargetVersion = 555
CordappInfoResolver.withCordappInfo(targetPlatformVersion = expectedTargetVersion) { CordappInfoResolver.withCordapp(targetPlatformVersion = expectedTargetVersion) {
val actualTargetVersion = CordappInfoResolver.currentTargetVersion val actualTargetVersion = CordappInfoResolver.currentTargetVersion
assertEquals(expectedTargetVersion, actualTargetVersion) assertEquals(expectedTargetVersion, actualTargetVersion)
} }
@ -31,8 +34,16 @@ class CordappInfoResolverTest {
@Test @Test
fun `when more than one cordapp is registered for the same class, the resolver returns null`() { fun `when more than one cordapp is registered for the same class, the resolver returns null`() {
CordappInfoResolver.register(listOf(javaClass.name), Cordapp.Info.Default("test", "test", "2", 3, 222)) CordappInfoResolver.register(CordappImpl.TEST_INSTANCE.copy(
CordappInfoResolver.register(listOf(javaClass.name), Cordapp.Info.Default("test1", "test1", "1", 2, 456)) contractClassNames = listOf(javaClass.name),
assertThat(CordappInfoResolver.currentCordappInfo).isNull() minimumPlatformVersion = 3,
targetPlatformVersion = 222
))
CordappInfoResolver.register(CordappImpl.TEST_INSTANCE.copy(
contractClassNames = listOf(javaClass.name),
minimumPlatformVersion = 2,
targetPlatformVersion = 456
))
assertThat(CordappInfoResolver.currentCordapp).isNull()
} }
} }

View File

@ -693,7 +693,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// TODO Add public API to allow the node operator to accept or reject // TODO Add public API to allow the node operator to accept or reject
private fun installFinalityHandler() { private fun installFinalityHandler() {
// Disable the insecure FinalityHandler if none of the loaded CorDapps are old enough to require it. // Disable the insecure FinalityHandler if none of the loaded CorDapps are old enough to require it.
val cordappsNeedingFinalityHandler = cordappLoader.cordapps.filter { it.info.targetPlatformVersion < 4 } val cordappsNeedingFinalityHandler = cordappLoader.cordapps.filter { it.targetPlatformVersion < 4 }
if (cordappsNeedingFinalityHandler.isEmpty()) { if (cordappsNeedingFinalityHandler.isEmpty()) {
log.info("FinalityHandler is disabled as there are no CorDapps loaded which require it") log.info("FinalityHandler is disabled as there are no CorDapps loaded which require it")
} else { } else {

View File

@ -4,14 +4,15 @@ import io.github.classgraph.ClassGraph
import io.github.classgraph.ScanResult import io.github.classgraph.ScanResult
import net.corda.core.contracts.warnContractWithoutConstraintPropagation import net.corda.core.contracts.warnContractWithoutConstraintPropagation
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappInvalidVersionException
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN_INFO
import net.corda.core.internal.cordapp.CordappInfoResolver import net.corda.core.internal.cordapp.CordappInfoResolver
import net.corda.core.internal.cordapp.toCordappInfo import net.corda.core.internal.cordapp.get
import net.corda.core.internal.notary.NotaryService import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.notary.SinglePartyNotaryService import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.node.services.CordaService import net.corda.core.node.services.CordaService
@ -31,6 +32,7 @@ import java.net.URLClassLoader
import java.nio.file.Path import java.nio.file.Path
import java.util.* import java.util.*
import java.util.jar.JarInputStream import java.util.jar.JarInputStream
import java.util.jar.Manifest
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.streams.toList import kotlin.streams.toList
@ -89,7 +91,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName) private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
private fun jarUrlsInDirectory(directory: Path): List<URL> { private fun jarUrlsInDirectory(directory: Path): List<URL> {
return if (!directory.exists()) { return if (!directory.exists()) {
emptyList() emptyList()
} else { } else {
@ -100,13 +101,14 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
} }
} }
} }
private fun loadCordapps(): List<CordappImpl> { private fun loadCordapps(): List<CordappImpl> {
val cordapps = cordappJarPaths val cordapps = cordappJarPaths
.map { url -> scanCordapp(url).use { it.toCordapp(url) } } .map { url -> scanCordapp(url).use { it.toCordapp(url) } }
.filter { .filter {
if (it.info.minimumPlatformVersion > versionInfo.platformVersion) { if (it.minimumPlatformVersion > versionInfo.platformVersion) {
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum " + logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum " +
"platform version ${it.info.minimumPlatformVersion} (This node is running version ${versionInfo.platformVersion}).") "platform version ${it.minimumPlatformVersion} (This node is running version ${versionInfo.platformVersion}).")
false false
} else { } else {
true true
@ -127,11 +129,15 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
} }
} }
} }
cordapps.forEach { CordappInfoResolver.register(it.cordappClasses, it.info) } cordapps.forEach(CordappInfoResolver::register)
return cordapps return cordapps
} }
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl { private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl {
val info = url.url.openStream().let(::JarInputStream).use { it.manifest?.toCordappInfo(CordappImpl.jarName(url.url)) ?: UNKNOWN } val manifest: Manifest? = url.url.openStream().use { JarInputStream(it).manifest }
val info = parseCordappInfo(manifest, CordappImpl.jarName(url.url))
val minPlatformVersion = manifest?.get(CordappImpl.MIN_PLATFORM_VERSION)?.toIntOrNull() ?: 1
val targetPlatformVersion = manifest?.get(CordappImpl.TARGET_PLATFORM_VERSION)?.toIntOrNull() ?: minPlatformVersion
return CordappImpl( return CordappImpl(
findContractClassNames(this), findContractClassNames(this),
findInitiatedFlows(this), findInitiatedFlows(this),
@ -146,10 +152,67 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
url.url, url.url,
info, info,
getJarHash(url.url), getJarHash(url.url),
minPlatformVersion,
targetPlatformVersion,
findNotaryService(this) findNotaryService(this)
) )
} }
private fun parseCordappInfo(manifest: Manifest?, defaultName: String): Cordapp.Info {
if (manifest == null) return UNKNOWN_INFO
/** new identifiers (Corda 4) */
// is it a Contract Jar?
val contractInfo = if (manifest[CordappImpl.CORDAPP_CONTRACT_NAME] != null) {
Cordapp.Info.Contract(
shortName = manifest[CordappImpl.CORDAPP_CONTRACT_NAME] ?: defaultName,
vendor = manifest[CordappImpl.CORDAPP_CONTRACT_VENDOR] ?: CordappImpl.UNKNOWN_VALUE,
versionId = parseVersion(manifest[CordappImpl.CORDAPP_CONTRACT_VERSION], CordappImpl.CORDAPP_CONTRACT_VERSION),
licence = manifest[CordappImpl.CORDAPP_CONTRACT_LICENCE] ?: CordappImpl.UNKNOWN_VALUE
)
} else {
null
}
// is it a Workflow (flows and services) Jar?
val workflowInfo = if (manifest[CordappImpl.CORDAPP_WORKFLOW_NAME] != null) {
Cordapp.Info.Workflow(
shortName = manifest[CordappImpl.CORDAPP_WORKFLOW_NAME] ?: defaultName,
vendor = manifest[CordappImpl.CORDAPP_WORKFLOW_VENDOR] ?: CordappImpl.UNKNOWN_VALUE,
versionId = parseVersion(manifest[CordappImpl.CORDAPP_WORKFLOW_VERSION], CordappImpl.CORDAPP_WORKFLOW_VERSION),
licence = manifest[CordappImpl.CORDAPP_WORKFLOW_LICENCE] ?: CordappImpl.UNKNOWN_VALUE
)
} else {
null
}
when {
// combined Contract and Workflow Jar?
contractInfo != null && workflowInfo != null -> return Cordapp.Info.ContractAndWorkflow(contractInfo, workflowInfo)
contractInfo != null -> return contractInfo
workflowInfo != null -> return workflowInfo
}
return Cordapp.Info.Default(
shortName = manifest["Name"] ?: defaultName,
vendor = manifest["Implementation-Vendor"] ?: CordappImpl.UNKNOWN_VALUE,
version = manifest["Implementation-Version"] ?: CordappImpl.UNKNOWN_VALUE,
licence = CordappImpl.UNKNOWN_VALUE
)
}
private fun parseVersion(versionStr: String?, attributeName: String): Int {
if (versionStr == null) {
throw CordappInvalidVersionException("Target versionId attribute $attributeName not specified. Please specify a whole number starting from 1.")
}
val version = versionStr.toIntOrNull()
?: throw CordappInvalidVersionException("Version identifier ($versionStr) for attribute $attributeName must be a whole number starting from 1.")
if (version < 1) {
throw CordappInvalidVersionException("Target versionId ($versionStr) for attribute $attributeName must not be smaller than 1.")
}
return version
}
private fun findNotaryService(scanResult: RestrictedScanResult): Class<out NotaryService>? { private fun findNotaryService(scanResult: RestrictedScanResult): Class<out NotaryService>? {
// Note: we search for implementations of both NotaryService and TrustedAuthorityNotaryService as // Note: we search for implementations of both NotaryService and TrustedAuthorityNotaryService as
// the scanner won't find subclasses deeper down the hierarchy if any intermediate class is not // the scanner won't find subclasses deeper down the hierarchy if any intermediate class is not

View File

@ -29,10 +29,12 @@ internal object VirtualCordapp {
serializationWhitelists = listOf(), serializationWhitelists = listOf(),
serializationCustomSerializers = listOf(), serializationCustomSerializers = listOf(),
customSchemas = setOf(), customSchemas = setOf(),
info = Cordapp.Info.Default("corda-core", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion), info = Cordapp.Info.Default("corda-core", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
allFlows = listOf(), allFlows = listOf(),
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
jarHash = SecureHash.allOnesHash, jarHash = SecureHash.allOnesHash,
minimumPlatformVersion = 1,
targetPlatformVersion = versionInfo.platformVersion,
notaryService = null, notaryService = null,
isLoaded = false isLoaded = false
) )
@ -50,10 +52,12 @@ internal object VirtualCordapp {
serializationWhitelists = listOf(), serializationWhitelists = listOf(),
serializationCustomSerializers = listOf(), serializationCustomSerializers = listOf(),
customSchemas = setOf(NodeNotarySchemaV1), customSchemas = setOf(NodeNotarySchemaV1),
info = Cordapp.Info.Default("corda-notary", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion), info = Cordapp.Info.Default("corda-notary", versionInfo.vendor, versionInfo.releaseVersion, "Open Source (Apache 2)"),
allFlows = listOf(), allFlows = listOf(),
jarPath = SimpleNotaryService::class.java.location, jarPath = SimpleNotaryService::class.java.location,
jarHash = SecureHash.allOnesHash, jarHash = SecureHash.allOnesHash,
minimumPlatformVersion = 1,
targetPlatformVersion = versionInfo.platformVersion,
notaryService = SimpleNotaryService::class.java, notaryService = SimpleNotaryService::class.java,
isLoaded = false isLoaded = false
) )

View File

@ -4,9 +4,9 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.internal.packageName import net.corda.core.internal.packageName
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import net.corda.testing.node.internal.TestCordappDirectories import net.corda.testing.node.internal.TestCordappDirectories
import net.corda.testing.node.internal.cordappForPackages import net.corda.testing.node.internal.cordappForPackages
import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.nio.file.Paths import java.nio.file.Paths
@ -96,8 +96,8 @@ class JarScanningCordappLoaderTest {
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/no-min-or-target-version.jar")!! val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/no-min-or-target-version.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
loader.cordapps.forEach { loader.cordapps.forEach {
assertThat(it.info.targetPlatformVersion).isEqualTo(1) assertThat(it.targetPlatformVersion).isEqualTo(1)
assertThat(it.info.minimumPlatformVersion).isEqualTo(1) assertThat(it.minimumPlatformVersion).isEqualTo(1)
} }
} }
@ -108,8 +108,8 @@ class JarScanningCordappLoaderTest {
val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!! val jar = JarScanningCordappLoaderTest::class.java.getResource("versions/min-2-target-3.jar")!!
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
val cordapp = loader.cordapps.first() val cordapp = loader.cordapps.first()
assertThat(cordapp.info.targetPlatformVersion).isEqualTo(3) assertThat(cordapp.targetPlatformVersion).isEqualTo(3)
assertThat(cordapp.info.minimumPlatformVersion).isEqualTo(2) assertThat(cordapp.minimumPlatformVersion).isEqualTo(2)
} }
@Test @Test
@ -119,8 +119,8 @@ class JarScanningCordappLoaderTest {
val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN) val loader = JarScanningCordappLoader.fromJarUrls(listOf(jar), VersionInfo.UNKNOWN)
// exclude the core cordapp // exclude the core cordapp
val cordapp = loader.cordapps.single { it.cordappClasses.contains("net.corda.core.internal.cordapp.CordappImpl") } val cordapp = loader.cordapps.single { it.cordappClasses.contains("net.corda.core.internal.cordapp.CordappImpl") }
assertThat(cordapp.info.targetPlatformVersion).isEqualTo(2) assertThat(cordapp.targetPlatformVersion).isEqualTo(2)
assertThat(cordapp.info.minimumPlatformVersion).isEqualTo(2) assertThat(cordapp.minimumPlatformVersion).isEqualTo(2)
} }
@Test @Test

View File

@ -121,7 +121,7 @@ class FinalityHandlerTest {
} }
private fun TestStartedNode.finaliseWithOldApi(stx: SignedTransaction): CordaFuture<SignedTransaction> { private fun TestStartedNode.finaliseWithOldApi(stx: SignedTransaction): CordaFuture<SignedTransaction> {
return CordappInfoResolver.withCordappInfo(targetPlatformVersion = 3) { return CordappInfoResolver.withCordapp(targetPlatformVersion = 3) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
services.startFlow(FinalityFlow(stx)).resultFuture.apply { services.startFlow(FinalityFlow(stx)).resultFuture.apply {
mockNet.runNetwork() mockNet.runNetwork()

View File

@ -10,13 +10,13 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.NamedCacheFactory import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.cordapp.set
import net.corda.core.internal.createComponentGroups import net.corda.core.internal.createComponentGroups
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.internal.effectiveSerializationEnv import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.core.internal.cordapp.set
import net.corda.node.internal.createCordaPersistence import net.corda.node.internal.createCordaPersistence
import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.internal.startHikariPool import net.corda.node.internal.startHikariPool

View File

@ -2,16 +2,13 @@ package net.corda.testing.internal
import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.ContractClassName
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.node.cordapp.CordappLoader import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.testing.services.MockAttachmentStorage import net.corda.testing.services.MockAttachmentStorage
import java.nio.file.Paths
import java.security.PublicKey import java.security.PublicKey
import java.util.jar.Attributes import java.util.jar.Attributes
@ -30,22 +27,7 @@ class MockCordappProvider(
signers: List<PublicKey> = emptyList(), signers: List<PublicKey> = emptyList(),
jarManifestAttributes: Map<String,String> = emptyMap() jarManifestAttributes: Map<String,String> = emptyMap()
): AttachmentId { ): AttachmentId {
val cordapp = CordappImpl( val cordapp = CordappImpl.TEST_INSTANCE.copy(contractClassNames = listOf(contractClassName))
contractClassNames = listOf(contractClassName),
initiatedFlows = emptyList(),
rpcFlows = emptyList(),
serviceFlows = emptyList(),
schedulableFlows = emptyList(),
services = emptyList(),
serializationWhitelists = emptyList(),
serializationCustomSerializers = emptyList(),
customSchemas = emptySet(),
jarPath = Paths.get("").toUri().toURL(),
info = UNKNOWN,
allFlows = emptyList(),
jarHash = SecureHash.allOnesHash,
notaryService = null
)
val jarManifestAttributesWithObligatoryElement = jarManifestAttributes.toMutableMap() val jarManifestAttributesWithObligatoryElement = jarManifestAttributes.toMutableMap()
jarManifestAttributesWithObligatoryElement.putIfAbsent(Attributes.Name.MANIFEST_VERSION.toString(), "1.0") jarManifestAttributesWithObligatoryElement.putIfAbsent(Attributes.Name.MANIFEST_VERSION.toString(), "1.0")
if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) && it.second == contractHash }) { if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) && it.second == contractHash }) {