CORDA-2149 CorDapp Contract and Workflow version identifiers (#4363)

* Implementation of Contract and Workflow attribute identifiers.

* Fixes following rebase from master.

* Fix broken JUnit test.

* Fix broken JUnit test.

* Fix broken JUnit test.

* Added missing constants.

* Further clean-up.

* Updated documentation.

* Added changelog entry.

* Updated all samples (using new Gradle Plugin 4.0.37 functionality)

* Temporarily resolve gradle plugins from latest published snapshot.

* Temporarily resolve gradle plugins from latest published snapshot.

* Updates following feedback from PR review.

* Move constants into CordappInfo companion object.

* Contract and Workflow attribute `version` to `versionId` (as version is a reserved gradle variable)

* Clarified warning message on incorrect version identifier.

* Align version identifier processing logic with gradle cordapp plugin.

* Updated comment.

* Minor fixes following rebase from master.

* Fixed broken unit test.

* Improved exception reporting.

* Update to use 4.0.37 of Gradle Plugins.

* Added support for combined Contract and Workflow CorDapp info.

* Updated following discussions with Shams + cleanup.

* Updated following Shams PR review.

* Minor API improvements.

* Added missing cordapp info causing deployNodes to fail.
This commit is contained in:
josecoll 2018-12-14 09:39:23 +00:00 committed by GitHub
parent 767e37a34e
commit 9cdda3bd77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 475 additions and 231 deletions

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=4.0.36
gradlePluginsVersion=4.0.37
kotlinVersion=1.2.71
# ***************************************************************#
# When incrementing platformVersion make sure to update #

View File

@ -1,8 +1,8 @@
package net.corda.core.contracts
import net.corda.core.KeepForDJVM
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.serialization.CordaSerializable
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import java.security.PublicKey
/**

View File

@ -3,4 +3,9 @@ package net.corda.core.cordapp
/**
* Thrown if an exception occurs in accessing or parsing cordapp configuration
*/
class CordappConfigException(msg: String, e: Throwable) : Exception(msg, e)
class CordappConfigException(msg: String, e: Throwable) : Exception(msg, e)
/**
* Thrown if an exception occurs whilst parsing version identifiers within cordapp configuration
*/
class CordappInvalidVersionException(msg: String) : Exception(msg)

View File

@ -4,6 +4,10 @@ import net.corda.core.DeleteForDJVM
import net.corda.core.DoNotImplement
import net.corda.core.crypto.SecureHash
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.parseVersion
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist
@ -47,5 +51,66 @@ interface Cordapp {
val allFlows: List<Class<out FlowLogic<*>>>
val jarPath: URL
val cordappClasses: List<String>
val info: Info
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
val licence: String
val minimumPlatformVersion: Int
val targetPlatformVersion: Int
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)
: Info {
override fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, version).any { it == UNKNOWN_VALUE }
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)
: Info {
override val version: String
get() = versionId.toString()
override fun toString() = "Contract CorDapp: $shortName version $version by vendor $vendor with licence $licence"
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)
: Info {
override val version: String
get() = versionId.toString()
override fun toString() = "Workflow CorDapp: $shortName version $version by vendor $vendor with licence $licence"
override fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, licence).any { it == UNKNOWN_VALUE }
}
/** A CorDapp that includes both Contract and Workflow classes (not recommended) */
// 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)
: Info {
override val shortName: String
get() = "Contract: ${contract.shortName}, Workflow: ${workflow.shortName}"
override val vendor: String
get() = "Contract: ${contract.vendor}, Workflow: ${workflow.vendor}"
override val licence: String
get() = "Contract: ${contract.licence}, Workflow: ${workflow.licence}"
override val version: String
get() = "Contract: ${contract.versionId}, Workflow: ${workflow.versionId}"
override fun toString() = "Combined CorDapp: $contract, $workflow"
override fun hasUnknownFields(): Boolean = contract.hasUnknownFields() || workflow.hasUnknownFields()
}
}
}

View File

@ -1,5 +0,0 @@
package net.corda.core.cordapp
const val CORDAPP_CONTRACT_VERSION = "Implementation-Version" //TODO will be changed to "Corda-Contract-Version"
const val DEFAULT_CORDAPP_VERSION: Int = 1

View File

@ -2,6 +2,7 @@ package net.corda.core.internal.cordapp
import net.corda.core.DeleteForDJVM
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappInvalidVersionException
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.notary.NotaryService
@ -25,7 +26,7 @@ data class CordappImpl(
override val customSchemas: Set<MappedSchema>,
override val allFlows: List<Class<out FlowLogic<*>>>,
override val jarPath: URL,
val info: Info,
override val info: Cordapp.Info,
override val jarHash: SecureHash.SHA256,
val notaryService: Class<out NotaryService>?,
/** Indicates whether the CorDapp is loaded from external sources, or generated on node startup (virtual). */
@ -34,8 +35,42 @@ data class CordappImpl(
companion object {
fun jarName(url: URL): String = url.toPath().fileName.toString().removeSuffix(".jar")
}
/** CorDapp manifest entries */
const val CORDAPP_CONTRACT_NAME = "Cordapp-Contract-Name"
const val CORDAPP_CONTRACT_VERSION = "Cordapp-Contract-Version"
const val CORDAPP_CONTRACT_VENDOR = "Cordapp-Contract-Vendor"
const val CORDAPP_CONTRACT_LICENCE = "Cordapp-Contract-Licence"
const val CORDAPP_WORKFLOW_NAME = "Cordapp-Workflow-Name"
const val CORDAPP_WORKFLOW_VERSION = "Cordapp-Workflow-Version"
const val CORDAPP_WORKFLOW_VENDOR = "Cordapp-Workflow-Vendor"
const val CORDAPP_WORKFLOW_LICENCE = "Cordapp-Workflow-Licence"
const val TARGET_PLATFORM_VERSION = "Target-Platform-Version"
const val MIN_PLATFORM_VERSION = "Min-Platform-Version"
const val UNKNOWN_VALUE = "Unknown"
const val DEFAULT_CORDAPP_VERSION = 1
/** used for CorDapps that do not explicitly define attributes */
val UNKNOWN = Cordapp.Info.Default(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE,1, 1)
/** Helper method for version identifier parsing */
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.")
return try {
val version = versionStr.toInt()
if (version < 1) {
throw CordappInvalidVersionException("Target versionId ($versionStr) for attribute $attributeName must not be smaller than 1.")
}
return version
} catch (e: NumberFormatException) {
throw CordappInvalidVersionException("Version identifier ($versionStr) for attribute $attributeName must be a whole number starting from 1.")
}
}
}
/**
* An exhaustive list of all classes relevant to the node within this CorDapp
*
@ -45,14 +80,4 @@ data class CordappImpl(
val classList = rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass } + notaryService
classList.mapNotNull { it?.name } + contractClassNames
}
// 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, val minimumPlatformVersion: Int, val targetPlatformVersion: Int) {
companion object {
const val UNKNOWN_VALUE = "Unknown"
val UNKNOWN = Info(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE, 1, 1)
}
fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, version).any { it == UNKNOWN_VALUE }
}
}

View File

@ -1,5 +1,6 @@
package net.corda.core.internal.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.VisibleForTesting
import net.corda.core.utilities.loggerFor
@ -10,10 +11,10 @@ import java.util.concurrent.ConcurrentHashMap
*/
object CordappInfoResolver {
private val logger = loggerFor<CordappInfoResolver>()
private val cordappClasses: ConcurrentHashMap<String, Set<CordappImpl.Info>> = ConcurrentHashMap()
private val cordappClasses: ConcurrentHashMap<String, Set<Cordapp.Info>> = ConcurrentHashMap()
// TODO Use the StackWalker API once we migrate to Java 9+
private var cordappInfoResolver: () -> CordappImpl.Info? = {
private var cordappInfoResolver: () -> Cordapp.Info? = {
Exception().stackTrace
.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.
@ -25,7 +26,7 @@ object CordappInfoResolver {
* This could happen when trying to run different versions of the same CorDapp on the same node.
*/
@Synchronized
fun register(classes: List<String>, cordapp: CordappImpl.Info) {
fun register(classes: List<String>, cordapp: Cordapp.Info) {
classes.forEach {
if (cordappClasses.containsKey(it)) {
logger.warn("More than one CorDapp registered for $it.")
@ -45,7 +46,7 @@ object CordappInfoResolver {
* @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.
*/
val currentCordappInfo: CordappImpl.Info? get() = cordappInfoResolver()
val currentCordappInfo: Cordapp.Info? get() = cordappInfoResolver()
/**
* Returns the target version of the current calling CorDapp. Defaults to the current platform version if there isn't one.
@ -60,12 +61,13 @@ object CordappInfoResolver {
@VisibleForTesting
fun <T> withCordappInfo(shortName: String = "CordappInfoResolver.withCordappInfo",
vendor: String = "Corda",
version: String = "1.0",
version: String = "1",
licence: String = "Apache",
minimumPlatformVersion: Int = 1,
targetPlatformVersion: Int = PLATFORM_VERSION,
block: () -> T): T {
val currentResolver = cordappInfoResolver
cordappInfoResolver = { CordappImpl.Info(shortName, vendor, version, minimumPlatformVersion, targetPlatformVersion) }
cordappInfoResolver = { Cordapp.Info.Default(shortName, vendor, version, minimumPlatformVersion, targetPlatformVersion, licence) }
try {
return block()
} finally {

View File

@ -0,0 +1,86 @@
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.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.Manifest
operator fun Manifest.set(key: String, value: String): String? {
return mainAttributes.putValue(key, value)
}
operator fun Manifest.set(key: Attributes.Name, value: String): Any? {
return mainAttributes.put(key, value)
}
operator fun Manifest.get(key: String): String? = mainAttributes.getValue(key)
val Manifest.targetPlatformVersion: Int
get() {
val minPlatformVersion = this[MIN_PLATFORM_VERSION]?.toIntOrNull() ?: 1
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

@ -1,13 +1,12 @@
package net.corda.core.internal.rules
import net.corda.core.contracts.ContractState
import net.corda.core.utilities.contextLogger
import net.corda.core.internal.cordapp.targetPlatformVersion
import net.corda.core.utilities.warnOnce
import org.slf4j.LoggerFactory
import java.net.URL
import java.util.concurrent.ConcurrentHashMap
import java.util.jar.JarInputStream
import java.util.jar.Manifest
// This file provides rules that depend on the targetVersion of the current Contract or Flow.
// Rules defined in this package are automatically removed from the DJVM in core-deterministic,
@ -47,9 +46,4 @@ object StateContractValidationEnforcementRule {
return targetVersion >= 4
}
}
private val Manifest.targetPlatformVersion: Int get() {
val minPlatformVersion = mainAttributes.getValue("Min-Platform-Version")?.toInt() ?: 1
return mainAttributes.getValue("Target-Platform-Version")?.toInt() ?: minPlatformVersion
}

View File

@ -6,6 +6,7 @@ import net.corda.core.contracts.TransactionVerificationException.OverlappingAtta
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.cordapp.targetPlatformVersion
import net.corda.core.internal.createSimpleCache
import net.corda.core.internal.isUploaderTrusted
import net.corda.core.internal.toSynchronised
@ -18,7 +19,6 @@ import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.*
import java.util.jar.Manifest
/**
* A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only
@ -108,14 +108,6 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
}
}
// This was reused from: https://github.com/corda/corda/pull/4240.
// TODO - Once that is merged it should be extracted to a utility.
private val Manifest.targetPlatformVersion: Int
get() {
val minPlatformVersion = mainAttributes.getValue("Min-Platform-Version")?.toInt() ?: 1
return mainAttributes.getValue("Target-Platform-Version")?.toInt() ?: minPlatformVersion
}
@VisibleForTesting
private fun readAttachment(attachment: Attachment, filepath: String): ByteArray {
ByteArrayOutputStream().use {

View File

@ -6,11 +6,11 @@ import net.corda.core.contracts.*
import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException
import net.corda.core.contracts.TransactionVerificationException.TransactionRequiredContractUnspecifiedException
import net.corda.core.contracts.Version
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters

View File

@ -4,11 +4,11 @@ import co.paralleluniverse.strands.Strand
import net.corda.core.CordaInternal
import net.corda.core.DeleteForDJVM
import net.corda.core.contracts.*
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.core.contracts.ContractAttachment.Companion.getContractVersion
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution

View File

@ -7,13 +7,12 @@ import net.corda.core.contracts.*
import net.corda.core.contracts.ComponentGroupEnum.COMMANDS_GROUP
import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP
import net.corda.core.contracts.ContractAttachment.Companion.getContractVersion
import net.corda.core.contracts.Version
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.Emoji
import net.corda.core.internal.SerializedStateAndRef
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.createComponentGroups
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub

View File

@ -4,19 +4,22 @@ import co.paralleluniverse.fibers.Suspendable
import com.natpryce.hamkrest.*
import com.natpryce.hamkrest.assertion.assert
import net.corda.core.contracts.Attachment
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.core.crypto.SecureHash
import net.corda.testing.internal.matchers.flow.willReturn
import net.corda.testing.internal.matchers.flow.willThrow
import net.corda.core.flows.mixins.WithMockNet
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.FetchAttachmentsFlow
import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.hash
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.testing.core.*
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.makeUnique
import net.corda.testing.core.singleIdentity
import net.corda.testing.internal.fakeAttachment
import net.corda.testing.internal.matchers.flow.willReturn
import net.corda.testing.internal.matchers.flow.willThrow
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.TestStartedNode

View File

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

View File

@ -4,7 +4,6 @@ import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
@ -12,6 +11,7 @@ import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.RPC_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentStorage

View File

@ -18,6 +18,13 @@ Unreleased
For a given contract class, the contract attachment of the output states must be of the same or newer version than the contract attachment of the input states.
See :ref:`Contract attachment non-downgrade rule <contract_non-downgrade_rule_ref>` for further information.
* Standardised CorDapp version identifiers in jar manifests (aligned with associated cordapp Gradle plugin changes).
Updated all samples to reflect new conventions.
* Introduction of unique CorDapp version identifiers in jar manifests for contract and flows/services CorDapps.
Updated all sample CorDapps to reflect new conventions.
See :ref:`CorDapp separation <cordapp_separation_ref>` for further information.
* Automatic Constraints propagation for hash-constrained states to signature-constrained states.
This allows Corda 4 signed CorDapps using signature constraints to consume existing hash constrained states generated
by unsigned CorDapps in previous versions of Corda.

View File

@ -368,13 +368,20 @@ There is an example project that demonstrates in ``samples`` called ``cordapp-co
Minimum and target platform version
-----------------------------------
CorDapps can advertise their minimum and target platform version. The minimum platform version indicates that a node has to run at least this version in order to be able to run this CorDapp. The target platform version indicates that a CorDapp was tested with this version of the Corda Platform and should be run at this API level if possible. It provides a means of maintaining behavioural compatibility for the cases where the platform's behaviour has changed. These attributes are specified in the JAR manifest of the CorDapp, for example:
CorDapps can advertise their minimum and target platform version. The minimum platform version indicates that a node has to run at least this
version in order to be able to run this CorDapp. The target platform version indicates that a CorDapp was tested with this version of the Corda
Platform and should be run at this API level if possible. It provides a means of maintaining behavioural compatibility for the cases where the
platform's behaviour has changed. These attributes are specified in the JAR manifest of the CorDapp, for example:
.. sourcecode:: groovy
'Min-Platform-Version': 4
'Target-Platform-Version': 4
**Defaults**
- ``Target-Platform-Version`` (mandatory) is a whole number and must comply with the rules mentioned above.
- ``Min-Platform-Version`` (optional) will default to 1 if not specified.
Using the `cordapp` Gradle plugin, this can be achieved by putting this in your CorDapp's `build.gradle`:
.. container:: codeset
@ -382,23 +389,80 @@ Using the `cordapp` Gradle plugin, this can be achieved by putting this in your
.. sourcecode:: groovy
cordapp {
info {
targetPlatformVersion 4
minimumPlatformVersion 4
}
targetPlatformVersion 4
minimumPlatformVersion 4
}
Without using the `cordapp` plugin, you can achieve the same by modifying the jar task as shown in this example:
.. _cordapp_separation_ref:
Separation of CorDapp contracts, flows and services
---------------------------------------------------
It is recommended that **contract** code (states, commands, verification logic) be packaged separately from **business flows** (and associated services).
This decoupling enables *contracts* to evolve independently from the *flows* and *services* that use them. Contracts may even be specified and implemented by different
providers (eg. Corda currently ships with a cash financial contract which in turn is used in many other flows and many other CorDapps).
As of Corda 4, CorDapps can explicitly differentiate their type by specifying the following attributes in the JAR manifest:
.. sourcecode:: groovy
'Cordapp-Contract-Name'
'Cordapp-Contract-Version'
'Cordapp-Contract-Vendor'
'Cordapp-Contract-Licence'
'Cordapp-Workflow-Name'
'Cordapp-Workflow-Version'
'Cordapp-Workflow-Vendor'
'Cordapp-Workflow-Licence'
**Defaults**
``Cordapp-Contract-Name`` (optional) if specified, the following Contract related attributes are also used:
- ``Cordapp-Contract-Version`` (mandatory), must be a whole number starting from 1.
- ``Cordapp-Contract-Vendor`` (optional), defaults to UNKNOWN if not specified.
- ``Cordapp-Contract-Licence`` (optional), defaults to UNKNOWN if not specified.
``Cordapp-Workflow-Name`` (optional) if specified, the following Workflow related attributes are also used:
- ``Cordapp-Workflow-Version`` (mandatory), must be a whole number starting from 1.
- ``Cordapp-Workflow-Vendor`` (optional), defaults to UNKNOWN if not specified.
- ``Cordapp-Workflow-Licence`` (optional), defaults to UNKNOWN if not specified.
As with the general CorDapp attributes (minimum and target platform version), these can be specified using the Gradle `cordapp` plugin as follows:
For a contract only CorDapp we specify the `contract` tag:
.. container:: codeset
.. sourcecode:: groovy
jar {
manifest {
attributes(
'Min-Platform-Version': 4
'Target-Platform-Version': 4
)
cordapp {
targetPlatformVersion 4
minimumPlatformVersion 3
contract {
name "my contract name"
versionId 1
vendor "my company"
licence "my licence"
}
}
For a CorDapp that contains flows and/or services we specify the `workflow` tag:
.. container:: codeset
.. sourcecode:: groovy
cordapp {
targetPlatformVersion 4
minimumPlatformVersion 3
workflow {
name "my workflow name"
versionId 1
vendor "my company"
licence "my licence"
}
}
.. note:: It is possible, but *not recommended*, to include everything in a single CorDapp jar and use both the ``contract`` and ``workflow`` Gradle plugin tags.

View File

@ -35,12 +35,13 @@ publish {
name 'corda-notary-bft-smart'
}
cordapp {
info {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
workflow {
name "net/corda/experimental/notary-bft-smart"
vendor "Corda Open Source"
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}

View File

@ -36,10 +36,12 @@ publish {
}
cordapp {
info {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
workflow {
name "net/corda/experimental/notary-raft"
vendor "Corda Open Source"
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}

View File

@ -62,30 +62,22 @@ artifacts {
jar {
baseName 'corda-finance'
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
exclude "META-INF/*.SF"
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 {
name "net/corda/finance"
vendor "Corda Open Source"
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
contract {
name "net/corda/finance/contracts"
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
workflow {
name "net/corda/finance/flows"
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
// By default the Cordapp is signed by Corda development certificate, for production build pass the following system properties to Gradle to use specific keystore e.g:
// ./gradlew -Dsigning.enabled="true" -Dsigning.keystore="/path/to/keystore.jks" -Dsigning.alias="alias" -Dsigning.storepass="password" -Dsigning.keypass="password"

View File

@ -4,13 +4,13 @@ import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.internal.RPC_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.NetworkParametersStorage

View File

@ -35,8 +35,8 @@ import kotlin.test.assertNotNull
class SignatureConstraintVersioningTests {
private val base = cordappForPackages(MessageState::class.packageName, DummyMessageContract::class.packageName)
private val oldCordapp = base.withCordappVersion("2")
private val newCordapp = base.withCordappVersion("3")
private val oldCordapp = base.withVersion("2")
private val newCordapp = base.withVersion("3")
private val user = User("mark", "dadada", setOf(startFlow<CreateMessage>(), startFlow<ConsumeMessage>(), invokeRpc("vaultQuery")))
private val message = Message("Hello world!")
private val transformetMessage = Message(message.value + "A")

View File

@ -243,9 +243,7 @@ open class NodeStartup : NodeStartupLogging {
}
protected open 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))
Node.printBasicNodeInfo("Loaded ${corDapps.size} CorDapp(s)", corDapps.map { it.info }.joinToString(", "))
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.")

View File

@ -9,7 +9,9 @@ import net.corda.core.crypto.sha256
import net.corda.core.flows.*
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN
import net.corda.core.internal.cordapp.CordappInfoResolver
import net.corda.core.internal.cordapp.toCordappInfo
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.node.services.CordaService
@ -129,7 +131,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
return cordapps
}
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl {
val info = url.url.openStream().let(::JarInputStream).use { it.manifest?.toCordappInfo(CordappImpl.jarName(url.url)) ?: CordappImpl.Info.UNKNOWN }
val info = url.url.openStream().let(::JarInputStream).use { it.manifest?.toCordappInfo(CordappImpl.jarName(url.url)) ?: UNKNOWN }
return CordappImpl(
findContractClassNames(this),
findInitiatedFlows(this),

View File

@ -1,53 +0,0 @@
package net.corda.node.internal.cordapp
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.CordappImpl.Info.Companion.UNKNOWN_VALUE
import java.util.jar.Attributes
import java.util.jar.Manifest
//TODO implementationVersion parmemater and update `Implementation-Version` when we finally agree on a naming split for Contracts vs Flows jars.
fun createTestManifest(name: String, title: String, version: String, vendor: String, targetVersion: Int, implementationVersion: String): Manifest {
val manifest = Manifest()
// Mandatory manifest attribute. If not present, all other entries are silently skipped.
manifest.mainAttributes[Attributes.Name.MANIFEST_VERSION] = "1.0"
manifest["Name"] = name
manifest["Specification-Title"] = title
manifest["Specification-Version"] = version
manifest["Specification-Vendor"] = vendor
manifest["Implementation-Title"] = title
manifest[Attributes.Name.IMPLEMENTATION_VERSION] = implementationVersion
manifest["Implementation-Vendor"] = vendor
manifest["Target-Platform-Version"] = targetVersion.toString()
return manifest
}
operator fun Manifest.set(key: String, value: String): String? {
return mainAttributes.putValue(key, value)
}
operator fun Manifest.set(key: Attributes.Name, value: String): Any? {
return mainAttributes.put(key, value)
}
operator fun Manifest.get(key: String): String? = mainAttributes.getValue(key)
fun Manifest.toCordappInfo(defaultShortName: String): CordappImpl.Info {
val shortName = this["Name"] ?: defaultShortName
val vendor = this["Implementation-Vendor"] ?: UNKNOWN_VALUE
val version = this["Implementation-Version"] ?: DEFAULT_CORDAPP_VERSION.toString()
val minPlatformVersion = this["Min-Platform-Version"]?.toIntOrNull() ?: 1
val targetPlatformVersion = this["Target-Platform-Version"]?.toIntOrNull() ?: minPlatformVersion
return CordappImpl.Info(
shortName = shortName,
vendor = vendor,
version = version,
minimumPlatformVersion = minPlatformVersion,
targetPlatformVersion = targetPlatformVersion
)
}

View File

@ -1,5 +1,6 @@
package net.corda.node.internal.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.internal.cordapp.CordappImpl
@ -28,7 +29,7 @@ internal object VirtualCordapp {
serializationWhitelists = listOf(),
serializationCustomSerializers = listOf(),
customSchemas = setOf(),
info = CordappImpl.Info("corda-core", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion),
info = Cordapp.Info.Default("corda-core", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion),
allFlows = listOf(),
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
jarHash = SecureHash.allOnesHash,
@ -49,7 +50,7 @@ internal object VirtualCordapp {
serializationWhitelists = listOf(),
serializationCustomSerializers = listOf(),
customSchemas = setOf(NodeNotarySchemaV1),
info = CordappImpl.Info("corda-notary", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion),
info = Cordapp.Info.Default("corda-notary", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion),
allFlows = listOf(),
jarPath = SimpleNotaryService::class.java.location,
jarHash = SecureHash.allOnesHash,

View File

@ -7,18 +7,20 @@ import com.google.common.hash.Hashing
import com.google.common.hash.HashingInputStream
import com.google.common.io.CountingInputStream
import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.*
import net.corda.core.cordapp.CORDAPP_CONTRACT_VERSION
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort
import net.corda.core.serialization.*
import net.corda.core.utilities.contextLogger
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
import net.corda.node.utilities.NonInvalidatingCache
import net.corda.node.utilities.NonInvalidatingWeightBasedCache

View File

@ -4,16 +4,12 @@ import co.paralleluniverse.fibers.Suspendable
import com.codahale.metrics.MetricRegistry
import com.google.common.jimfs.Configuration
import com.google.common.jimfs.Jimfs
import net.corda.testing.core.internal.ContractJarTestUtils.makeTestContractJar
import net.corda.testing.core.internal.ContractJarTestUtils.makeTestJar
import net.corda.testing.core.internal.ContractJarTestUtils.makeTestSignedContractJar
import net.corda.testing.core.internal.SelfCleaningDir
import net.corda.core.contracts.ContractAttachment
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.vault.AttachmentQueryCriteria.AttachmentsQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort
@ -23,6 +19,10 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.internal.ContractJarTestUtils.makeTestContractJar
import net.corda.testing.core.internal.ContractJarTestUtils.makeTestJar
import net.corda.testing.core.internal.ContractJarTestUtils.makeTestSignedContractJar
import net.corda.testing.core.internal.SelfCleaningDir
import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.configureDatabase

View File

@ -114,10 +114,12 @@ jar {
}
cordapp {
info {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
workflow {
name "net/corda/samples/attachment-demo"
vendor "Corda Open Source"
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}

View File

@ -115,10 +115,12 @@ jar {
}
cordapp {
info {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
workflow {
name "net/corda/samples/bank-of-corda-demo"
vendor "Corda Open Source"
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}

View File

@ -59,10 +59,12 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
}
cordapp {
info {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
workflow {
name "net/corda/samples/cordapp-configuration"
vendor "Corda Open Source"
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}

View File

@ -172,10 +172,18 @@ artifacts {
}
cordapp {
info {
name "net/corda/irs-demo"
vendor "Corda Open Source"
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
contract {
name "net/corda/irs-demo/contract"
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
workflow {
name "net/corda/irs-demo/flows"
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}

View File

@ -53,10 +53,12 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask])
}
cordapp {
info {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
workflow {
name "net/corda/samples/network-verifier"
vendor "Corda Open Source"
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}

View File

@ -255,10 +255,12 @@ jar {
}
cordapp {
info {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
workflow {
name "net/corda/samples/notary-demo"
vendor "Corda Open Source"
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}

View File

@ -140,8 +140,17 @@ task integrationTest(type: Test, dependsOn: []) {
}
cordapp {
info {
vendor = 'R3'
targetPlatformVersion = corda_platform_version.toInteger()
targetPlatformVersion = corda_platform_version.toInteger()
contract {
name "net/corda/vega/contracts"
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
workflow {
name "net/corda/vega/flows"
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}

View File

@ -4,10 +4,8 @@ def javaHome = System.getProperty('java.home')
def shrinkJar = file("$buildDir/libs/${project.name}-${project.version}-tiny.jar")
cordapp {
info {
vendor = 'R3'
targetPlatformVersion = corda_platform_version.toInteger()
}
targetPlatformVersion = corda_platform_version.toInteger()
minimumPlatformVersion 1
signing {
// Cordapp is signed after the "shrink" task.
enabled false
@ -16,6 +14,12 @@ cordapp {
// Cannot seal JAR because other module also defines classes in the package net.corda.vega.analytics
enabled false
}
contract {
name "net/corda/vega/contracts"
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}
configurations {

View File

@ -2,14 +2,18 @@ apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.cordapp'
cordapp {
info {
vendor = 'R3'
targetPlatformVersion = corda_platform_version.toInteger()
}
targetPlatformVersion = corda_platform_version.toInteger()
minimumPlatformVersion 1
sealing {
// Cannot seal JAR because other module also defines classes in the package net.corda.vega.analytics
enabled false
}
workflow {
name "net/corda/vega/flows"
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}
dependencies {

View File

@ -140,10 +140,12 @@ jar {
}
cordapp {
info {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
workflow {
name "net/corda/samples/trader-demo"
vendor "Corda Open Source"
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}

View File

@ -1,7 +1,6 @@
package net.corda.testing.node
import net.corda.core.DoNotImplement
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.testing.node.internal.TestCordappImpl
import net.corda.testing.node.internal.simplifyScanPackages
@ -27,9 +26,6 @@ interface TestCordapp {
/** Returns the target platform version, defaults to the current platform version if not specified. */
val targetVersion: Int
/** Returns the cordapp version. */
val cordappVersion: String
/** Returns the config for this CorDapp, defaults to empty if not specified. */
val config: Map<String, Any>
@ -61,8 +57,6 @@ interface TestCordapp {
* Optionally can pass in the location of an existing java key store to use */
fun signJar(keyStorePath: Path? = null): TestCordappImpl
fun withCordappVersion(version: String): TestCordappImpl
class Factory {
companion object {
/**
@ -80,11 +74,10 @@ interface TestCordapp {
fun fromPackages(packageNames: Collection<String>): TestCordapp {
return TestCordappImpl(
name = "test-name",
version = "1.0",
version = "1",
vendor = "test-vendor",
title = "test-title",
targetVersion = PLATFORM_VERSION,
cordappVersion = DEFAULT_CORDAPP_VERSION.toString(),
config = emptyMap(),
packages = simplifyScanPackages(packageNames),
classes = emptySet()

View File

@ -8,7 +8,6 @@ data class TestCordappImpl(override val name: String,
override val vendor: String,
override val title: String,
override val targetVersion: Int,
override val cordappVersion: String,
override val config: Map<String, Any>,
override val packages: Set<String>,
override val signJar: Boolean = false,
@ -26,8 +25,6 @@ data class TestCordappImpl(override val name: String,
override fun withTargetVersion(targetVersion: Int): TestCordappImpl = copy(targetVersion = targetVersion)
override fun withCordappVersion(version: String): TestCordappImpl = copy(cordappVersion = version)
override fun withConfig(config: Map<String, Any>): TestCordappImpl = copy(config = config)
override fun signJar(keyStorePath: Path?): TestCordappImpl = copy(signJar = true, keyStorePath = keyStorePath)

View File

@ -1,15 +1,22 @@
package net.corda.testing.node.internal
import io.github.classgraph.ClassGraph
import net.corda.core.internal.cordapp.*
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.TARGET_PLATFORM_VERSION
import net.corda.core.internal.outputStream
import net.corda.node.internal.cordapp.createTestManifest
import net.corda.testing.node.TestCordapp
import java.io.BufferedOutputStream
import java.nio.file.Path
import java.nio.file.attribute.FileTime
import java.time.Instant
import java.util.jar.Attributes
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import java.util.zip.ZipEntry
import kotlin.reflect.KClass
@ -66,7 +73,7 @@ fun TestCordappImpl.packageAsJar(file: Path) {
.scan()
scanResult.use {
val manifest = createTestManifest(name, title, version, vendor, targetVersion, cordappVersion)
val manifest = createTestManifest(name, title, version, vendor, targetVersion)
JarOutputStream(file.outputStream()).use { jos ->
val time = FileTime.from(Instant.EPOCH)
val manifestEntry = ZipEntry(JarFile.MANIFEST_NAME).setCreationTime(time).setLastAccessTime(time).setLastModifiedTime(time)
@ -85,3 +92,22 @@ fun TestCordappImpl.packageAsJar(file: Path) {
}
}
}
fun createTestManifest(name: String, title: String, version: String, vendor: String, targetVersion: Int): Manifest {
val manifest = Manifest()
// Mandatory manifest attribute. If not present, all other entries are silently skipped.
manifest[Attributes.Name.MANIFEST_VERSION.toString()] = "1.0"
manifest["Name"] = name
manifest[Attributes.Name.IMPLEMENTATION_TITLE] = title
manifest[Attributes.Name.IMPLEMENTATION_VERSION] = version
manifest[Attributes.Name.IMPLEMENTATION_VENDOR] = vendor
manifest[CORDAPP_CONTRACT_NAME] = name
manifest[CORDAPP_CONTRACT_VERSION] = version
manifest[CORDAPP_WORKFLOW_NAME] = name
manifest[CORDAPP_WORKFLOW_VERSION] = version
manifest[TARGET_PLATFORM_VERSION] = targetVersion.toString()
return manifest
}

View File

@ -1,7 +1,10 @@
package net.corda.testing.node.internal
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_WORKFLOW_NAME
import net.corda.core.internal.cordapp.CordappImpl.Companion.TARGET_PLATFORM_VERSION
import net.corda.core.internal.cordapp.get
import net.corda.core.internal.inputStream
import net.corda.node.internal.cordapp.get
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
@ -34,8 +37,9 @@ class TestCordappsUtilsTest {
val jarFile = packageAsJar(cordapp)
JarInputStream(jarFile.inputStream()).use {
assertThat(it.manifest["Target-Platform-Version"]).isEqualTo("123")
assertThat(it.manifest["Name"]).isEqualTo("TestCordappsUtilsTest")
assertThat(it.manifest[TARGET_PLATFORM_VERSION]).isEqualTo("123")
assertThat(it.manifest[CORDAPP_CONTRACT_NAME]).isEqualTo("TestCordappsUtilsTest")
assertThat(it.manifest[CORDAPP_WORKFLOW_NAME]).isEqualTo("TestCordappsUtilsTest")
}
}

View File

@ -1,18 +1,21 @@
package net.corda.testing.core.internal
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION
import net.corda.core.internal.delete
import net.corda.core.internal.div
import net.corda.core.internal.toPath
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.internal.JarSignatureTestUtils.addManifest
import net.corda.testing.core.internal.JarSignatureTestUtils.createJar
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
import net.corda.testing.core.internal.JarSignatureTestUtils.signJar
import net.corda.core.internal.delete
import net.corda.core.internal.div
import net.corda.core.internal.toPath
import net.corda.core.cordapp.CORDAPP_CONTRACT_VERSION
import net.corda.testing.core.ALICE_NAME
import java.io.OutputStream
import java.net.URI
import java.net.URL
import java.nio.file.*
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.security.PublicKey
import java.util.jar.Attributes
import java.util.jar.JarEntry

View File

@ -16,7 +16,7 @@ import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.loggerFor
import net.corda.node.internal.cordapp.set
import net.corda.core.internal.cordapp.set
import net.corda.node.internal.createCordaPersistence
import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.internal.startHikariPool

View File

@ -5,6 +5,7 @@ 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.cordapp.CordappImpl
import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
import net.corda.node.cordapp.CordappLoader
@ -40,7 +41,7 @@ class MockCordappProvider(
serializationCustomSerializers = emptyList(),
customSchemas = emptySet(),
jarPath = Paths.get("").toUri().toURL(),
info = CordappImpl.Info.UNKNOWN,
info = UNKNOWN,
allFlows = emptyList(),
jarHash = SecureHash.allOnesHash,
notaryService = null

View File

@ -3,11 +3,11 @@ package net.corda.testing.services
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.cordapp.DEFAULT_CORDAPP_VERSION
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.UNKNOWN_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.readFully
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage