CORDA-2128: Moved constraints and attachments stuff out of the public API that shouldn't be there (#4460)

This commit is contained in:
Shams Asari 2018-12-24 15:09:38 +00:00 committed by GitHub
parent 60d215aaa8
commit 00672f97fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 218 additions and 205 deletions

View File

@ -1,19 +1,14 @@
package net.corda.core.contracts
import net.corda.core.CordaInternal
import net.corda.core.DoNotImplement
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint.isSatisfiedBy
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.crypto.keys
import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.isUploaderTrusted
import net.corda.core.serialization.internal.AttachmentsClassLoader
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.warnOnce
import org.slf4j.LoggerFactory
import net.corda.core.transactions.TransactionBuilder
import java.lang.annotation.Inherited
import java.security.PublicKey
@ -34,72 +29,6 @@ annotation class NoConstraintPropagation
interface AttachmentConstraint {
/** Returns whether the given contract attachment can be used with the [ContractState] associated with this constraint object. */
fun isSatisfiedBy(attachment: Attachment): Boolean
private companion object {
private val log = contextLogger()
}
/**
* This method will be used in conjunction with [NoConstraintPropagation]. It is run during transaction verification when the contract is not annotated with [NoConstraintPropagation].
* When constraints propagation is enabled, constraints set on output states need to follow certain rules with regards to constraints of input states.
*
* Rules:
* * It is allowed for output states to inherit the exact same constraint as the input states.
* * The [AlwaysAcceptAttachmentConstraint] is not allowed to transition to a different constraint, as that could be used to hide malicious behaviour.
* * Anything (except the [AlwaysAcceptAttachmentConstraint]) can be transitioned to a [HashAttachmentConstraint].
* * You can transition from the [WhitelistedByZoneAttachmentConstraint] to the [SignatureAttachmentConstraint] only if all signers of the JAR are required to sign in the future.
* * You can transition from a [HashAttachmentConstraint] to a [SignatureAttachmentConstraint] when the following conditions are met:
* * 1. Jar contents (per entry, by hashcode) of both original (unsigned) and signed contract jars are identical
* * Note: this step is enforced in the [AttachmentsClassLoader] no overlap rule checking.
* * 2. Java package namespace of signed contract jar is registered in the CZ network map with same public keys (as used to sign contract jar)
*
* TODO - SignatureConstraint third party signers.
*/
@CordaInternal
fun canBeTransitionedFrom(input: AttachmentConstraint, attachment: AttachmentWithContext): Boolean {
val output = this
return when {
// These branches should not happen, as this has been already checked.
input is AutomaticPlaceholderConstraint || output is AutomaticPlaceholderConstraint -> throw IllegalArgumentException("Illegal constraint: AutomaticPlaceholderConstraint.")
input is AutomaticHashConstraint || output is AutomaticHashConstraint -> throw IllegalArgumentException("Illegal constraint: AutomaticHashConstraint.")
// Transition to the same constraint.
input == output -> true
// You can't transition from the AlwaysAcceptAttachmentConstraint to anything else, as it could hide something illegal.
input is AlwaysAcceptAttachmentConstraint && output !is AlwaysAcceptAttachmentConstraint -> false
// Nothing can be migrated from the HashConstraint except a HashConstraint with the same Hash. (This check is redundant, but added for clarity)
input is HashAttachmentConstraint && output is HashAttachmentConstraint -> input == output
// Anything (except the AlwaysAcceptAttachmentConstraint) can be transformed to a HashAttachmentConstraint.
input !is HashAttachmentConstraint && output is HashAttachmentConstraint -> true
// The SignatureAttachmentConstraint allows migration from a Signature constraint with the same key.
// TODO - we don't support currently third party signers. When we do, the output key will have to be stronger then the input key.
input is SignatureAttachmentConstraint && output is SignatureAttachmentConstraint -> input.key == output.key
// You can transition from the WhitelistConstraint to the SignatureConstraint only if all signers of the JAR are required to sign in the future.
input is WhitelistedByZoneAttachmentConstraint && output is SignatureAttachmentConstraint ->
attachment.signerKeys.isNotEmpty() && output.key.keys.containsAll(attachment.signerKeys)
// Transition from Hash to Signature constraint requires
// signer(s) of signature-constrained output state is same as signer(s) of registered package namespace
input is HashAttachmentConstraint && output is SignatureAttachmentConstraint -> {
val packageOwnerPK = attachment.networkParameters.getPackageOwnerOf(attachment.contractAttachment.allContracts)
if (packageOwnerPK == null) {
log.warn("Missing registered java package owner for ${attachment.contractAttachment.contract} in network parameters: ${attachment.networkParameters} (input constraint = $input, output constraint = $output)")
return false
}
else if (!packageOwnerPK.isFulfilledBy(output.key) ) {
log.warn("Java package owner keys do not match signature constrained output state keys")
return false
}
return true
}
else -> false
}
}
}
/** An [AttachmentConstraint] where [isSatisfiedBy] always returns true. */
@ -138,7 +67,11 @@ object WhitelistedByZoneAttachmentConstraint : AttachmentConstraint {
}
@KeepForDJVM
@Deprecated("The name is no longer valid as multiple constraints were added.", replaceWith = ReplaceWith("AutomaticPlaceholderConstraint"), level = DeprecationLevel.WARNING)
@Deprecated(
"The name is no longer valid as multiple constraints were added.",
replaceWith = ReplaceWith("AutomaticPlaceholderConstraint"),
level = DeprecationLevel.WARNING
)
object AutomaticHashConstraint : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment): Boolean {
throw UnsupportedOperationException("Contracts cannot be satisfied by an AutomaticHashConstraint placeholder.")
@ -146,8 +79,8 @@ object AutomaticHashConstraint : AttachmentConstraint {
}
/**
* This [AttachmentConstraint] is a convenience class that acts as a placeholder and will be automatically resolved by the platform when set on an output state.
* It is the default constraint of all output states.
* This [AttachmentConstraint] is a convenience class that acts as a placeholder and will be automatically resolved by the platform when set
* on an output state. It is the default constraint of all output states.
*
* The resolution occurs in [TransactionBuilder.toWireTransaction] and is based on the input states and the attachments.
* If the [Contract] was not annotated with [NoConstraintPropagation], then the platform will ensure the correct constraint propagation.
@ -159,48 +92,13 @@ object AutomaticPlaceholderConstraint : AttachmentConstraint {
}
}
private val logger = LoggerFactory.getLogger(AttachmentConstraint::class.java)
private val validConstraints = setOf(
AlwaysAcceptAttachmentConstraint::class,
HashAttachmentConstraint::class,
WhitelistedByZoneAttachmentConstraint::class,
SignatureAttachmentConstraint::class)
/**
* Fails if the constraint is not of a known type.
* Only the Corda core is allowed to implement the [AttachmentConstraint] interface.
*/
internal fun checkConstraintValidity(state: TransactionState<*>) {
require(state.constraint::class in validConstraints) { "Found state ${state.contract} with an illegal constraint: ${state.constraint}" }
if (state.constraint is AlwaysAcceptAttachmentConstraint) {
logger.warnOnce("Found state ${state.contract} that is constrained by the insecure: AlwaysAcceptAttachmentConstraint.")
}
}
/**
* Check for the [NoConstraintPropagation] annotation on the contractClassName.
* If it's present it means that the automatic secure core behaviour is not applied, and it's up to the contract developer to enforce a secure propagation logic.
*/
internal fun ContractClassName.contractHasAutomaticConstraintPropagation(classLoader: ClassLoader? = null) =
(classLoader ?: NoConstraintPropagation::class.java.classLoader)
.loadClass(this).getAnnotation(NoConstraintPropagation::class.java) == null
fun ContractClassName.warnContractWithoutConstraintPropagation(classLoader: ClassLoader? = null) {
if (!this.contractHasAutomaticConstraintPropagation(classLoader)) {
logger.warnOnce("Found contract $this with automatic constraint propagation disabled.")
}
}
/**
* An [AttachmentConstraint] that verifies that the attachment has signers that fulfil the provided [PublicKey].
* See: [Signature Constraints](https://docs.corda.net/design/data-model-upgrades/signature-constraints.html)
*
* @param key A [PublicKey] that must be fulfilled by the owning keys of the attachment's signing parties.
* @property key A [PublicKey] that must be fulfilled by the owning keys of the attachment's signing parties.
*/
@KeepForDJVM
data class SignatureAttachmentConstraint(
val key: PublicKey
) : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment): Boolean =
key.isFulfilledBy(attachment.signerKeys.map { it })
}
data class SignatureAttachmentConstraint(val key: PublicKey) : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment): Boolean = key.isFulfilledBy(attachment.signerKeys.map { it })
}

View File

@ -1,5 +1,7 @@
package net.corda.core.contracts
import kotlin.reflect.KClass
/**
* This annotation is required by any [ContractState] which needs to ensure that it is only ever processed as part of a
* [TransactionState] referencing the specified [Contract]. It may be omitted in the case that the [ContractState] class
@ -17,16 +19,3 @@ import kotlin.reflect.KClass
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class BelongsToContract(val value: KClass<out Contract>)
/**
* Obtain the typename of the required [ContractClass] associated with the target [ContractState], using the
* [BelongsToContract] annotation by default, but falling through to checking the state's enclosing class if there is
* one and it inherits from [Contract].
*/
val ContractState.requiredContractClassName: String? get() {
val annotation = javaClass.getAnnotation(BelongsToContract::class.java)
if (annotation != null) {
return annotation.value.java.typeName
}
val enclosingClass = javaClass.enclosingClass ?: return null
return if (Contract::class.java.isAssignableFrom(enclosingClass)) enclosingClass.typeName else null
}

View File

@ -29,13 +29,4 @@ class ContractAttachment @JvmOverloads constructor(
override fun toString(): String {
return "ContractAttachment(attachment=${attachment.id}, contracts='$allContracts', uploader='$uploader', signed='$isSigned', version='$version')"
}
companion object {
fun getContractVersion(attachment: Attachment) : Version =
if (attachment is ContractAttachment) {
attachment.version
} else {
DEFAULT_CORDAPP_VERSION
}
}
}
}

View File

@ -3,6 +3,7 @@ package net.corda.core.contracts
import net.corda.core.KeepForDJVM
import net.corda.core.identity.Party
import net.corda.core.internal.requiredContractClassName
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.loggerFor

View File

@ -1,6 +0,0 @@
package net.corda.core.contracts
/**
* Contract version and flow versions are integers.
*/
typealias Version = Int

View File

@ -4,8 +4,3 @@ 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)
/**
* Thrown if an exception occurs whilst parsing version identifiers within cordapp configuration
*/
class CordappInvalidVersionException(msg: String) : Exception(msg)

View File

@ -4,8 +4,8 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.StateAndRef
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.RetrieveAnyTransactionPayload
import net.corda.core.internal.readFully
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap
@ -115,11 +115,3 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any)
}
}
}
/**
* This is a wildcard payload to be used by the invoker of the [DataVendingFlow] to allow unlimited access to its vault.
*
* TODO Fails with a serialization exception if it is not a list. Why?
*/
@CordaSerializable
object RetrieveAnyTransactionPayload : ArrayList<Any>()

View File

@ -0,0 +1,136 @@
package net.corda.core.internal
import net.corda.core.contracts.*
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.crypto.keys
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.warnOnce
/**
* Contract version and flow versions are integers.
*/
typealias Version = Int
private val log = loggerFor<AttachmentConstraint>()
val Attachment.contractVersion: Version get() = if (this is ContractAttachment) version else CordappImpl.DEFAULT_CORDAPP_VERSION
/**
* Obtain the typename of the required [ContractClass] associated with the target [ContractState], using the
* [BelongsToContract] annotation by default, but falling through to checking the state's enclosing class if there is
* one and it inherits from [Contract].
*/
val ContractState.requiredContractClassName: String? get() {
val annotation = javaClass.getAnnotation(BelongsToContract::class.java)
if (annotation != null) {
return annotation.value.java.typeName
}
val enclosingClass = javaClass.enclosingClass ?: return null
return if (Contract::class.java.isAssignableFrom(enclosingClass)) enclosingClass.typeName else null
}
/**
* This method will be used in conjunction with [NoConstraintPropagation]. It is run during transaction verification when the contract is not
* annotated with [NoConstraintPropagation]. When constraints propagation is enabled, constraints set on output states need to follow certain
* rules with regards to constraints of input states.
*
* Rules:
*
* * It is allowed for output states to inherit the exact same constraint as the input states.
*
* * The [AlwaysAcceptAttachmentConstraint] is not allowed to transition to a different constraint, as that could be used to hide malicious
* behaviour.
*
* * Anything (except the [AlwaysAcceptAttachmentConstraint]) can be transitioned to a [HashAttachmentConstraint].
*
* * You can transition from the [WhitelistedByZoneAttachmentConstraint] to the [SignatureAttachmentConstraint] only if all signers of the
* JAR are required to sign in the future.
*
* * You can transition from a [HashAttachmentConstraint] to a [SignatureAttachmentConstraint] when the following conditions are met:
*
* 1. Jar contents (per entry, by hashcode) of both original (unsigned) and signed contract jars are identical
* Note: this step is enforced in the [AttachmentsClassLoader] no overlap rule checking.
*
* 2. Java package namespace of signed contract jar is registered in the CZ network map with same public keys (as used to sign contract jar)
*/
// TODO - SignatureConstraint third party signers.
fun AttachmentConstraint.canBeTransitionedFrom(input: AttachmentConstraint, attachment: AttachmentWithContext): Boolean {
val output = this
return when {
// These branches should not happen, as this has been already checked.
input is AutomaticPlaceholderConstraint || output is AutomaticPlaceholderConstraint -> throw IllegalArgumentException("Illegal constraint: AutomaticPlaceholderConstraint.")
input is AutomaticHashConstraint || output is AutomaticHashConstraint -> throw IllegalArgumentException("Illegal constraint: AutomaticHashConstraint.")
// Transition to the same constraint.
input == output -> true
// You can't transition from the AlwaysAcceptAttachmentConstraint to anything else, as it could hide something illegal.
input is AlwaysAcceptAttachmentConstraint && output !is AlwaysAcceptAttachmentConstraint -> false
// Nothing can be migrated from the HashConstraint except a HashConstraint with the same Hash. (This check is redundant, but added for clarity)
input is HashAttachmentConstraint && output is HashAttachmentConstraint -> input == output
// Anything (except the AlwaysAcceptAttachmentConstraint) can be transformed to a HashAttachmentConstraint.
input !is HashAttachmentConstraint && output is HashAttachmentConstraint -> true
// The SignatureAttachmentConstraint allows migration from a Signature constraint with the same key.
// TODO - we don't support currently third party signers. When we do, the output key will have to be stronger then the input key.
input is SignatureAttachmentConstraint && output is SignatureAttachmentConstraint -> input.key == output.key
// You can transition from the WhitelistConstraint to the SignatureConstraint only if all signers of the JAR are required to sign in the future.
input is WhitelistedByZoneAttachmentConstraint && output is SignatureAttachmentConstraint ->
attachment.signerKeys.isNotEmpty() && output.key.keys.containsAll(attachment.signerKeys)
// Transition from Hash to Signature constraint requires
// signer(s) of signature-constrained output state is same as signer(s) of registered package namespace
input is HashAttachmentConstraint && output is SignatureAttachmentConstraint -> {
val packageOwnerPK = attachment.networkParameters.getPackageOwnerOf(attachment.contractAttachment.allContracts)
if (packageOwnerPK == null) {
log.warn("Missing registered java package owner for ${attachment.contractAttachment.contract} in network parameters: " +
"${attachment.networkParameters} (input constraint = $input, output constraint = $output)")
return false
}
else if (!packageOwnerPK.isFulfilledBy(output.key) ) {
log.warn("Java package owner keys do not match signature constrained output state keys")
return false
}
return true
}
else -> false
}
}
private val validConstraints = setOf(
AlwaysAcceptAttachmentConstraint::class,
HashAttachmentConstraint::class,
WhitelistedByZoneAttachmentConstraint::class,
SignatureAttachmentConstraint::class
)
/**
* Fails if the constraint is not of a known type.
* Only the Corda core is allowed to implement the [AttachmentConstraint] interface.
*/
internal fun checkConstraintValidity(state: TransactionState<*>) {
require(state.constraint::class in validConstraints) { "Found state ${state.contract} with an illegal constraint: ${state.constraint}" }
if (state.constraint is AlwaysAcceptAttachmentConstraint) {
log.warnOnce("Found state ${state.contract} that is constrained by the insecure: AlwaysAcceptAttachmentConstraint.")
}
}
/**
* Check for the [NoConstraintPropagation] annotation on the contractClassName. If it's present it means that the automatic secure core behaviour
* is not applied, and it's up to the contract developer to enforce a secure propagation logic.
*/
internal fun ContractClassName.contractHasAutomaticConstraintPropagation(classLoader: ClassLoader? = null): Boolean {
return (classLoader ?: NoConstraintPropagation::class.java.classLoader)
.loadClass(this)
.getAnnotation(NoConstraintPropagation::class.java) == null
}
fun ContractClassName.warnContractWithoutConstraintPropagation(classLoader: ClassLoader? = null) {
if (!this.contractHasAutomaticConstraintPropagation(classLoader)) {
log.warnOnce("Found contract $this with automatic constraint propagation disabled.")
}
}

View File

@ -5,9 +5,11 @@ import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappConfig
import net.corda.core.cordapp.CordappContext
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.DataVendingFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
@ -61,15 +63,25 @@ internal fun SignedTransaction.pushToLoggingContext() {
MDC.put("tx_id", id.toString())
}
/**
* List implementation that applies the expensive [transform] function only when the element is accessed and caches calculated values.
* Size is very cheap as it doesn't call [transform].
*/
class LazyMappedList<T, U>(val originalList: List<T>, val transform: (T, Int) -> U) : AbstractList<U>() {
private val partialResolvedList = MutableList<U?>(originalList.size) { null }
override val size = originalList.size
override fun get(index: Int) = partialResolvedList[index]
?: transform(originalList[index], index).also { computed -> partialResolvedList[index] = computed }
private fun isPackageValid(packageName: String): Boolean {
return packageName.isNotEmpty() &&
!packageName.endsWith(".") &&
packageName.split(".").all { token ->
Character.isJavaIdentifierStart(token[0]) && token.toCharArray().drop(1).all { Character.isJavaIdentifierPart(it) }
}
}
/**
* Check if a string is a legal Java package name.
*/
fun requirePackageValid(name: String) {
require(isPackageValid(name)) { "Invalid Java package name: `$name`." }
}
/**
* This is a wildcard payload to be used by the invoker of the [DataVendingFlow] to allow unlimited access to its vault.
*
* TODO Fails with a serialization exception if it is not a list. Why?
*/
@CordaSerializable
object RetrieveAnyTransactionPayload : ArrayList<Any>()

View File

@ -50,6 +50,7 @@ import java.util.stream.StreamSupport
import java.util.zip.Deflater
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.collections.AbstractList
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
@ -518,13 +519,15 @@ fun <K, V> createSimpleCache(maxSize: Int, onEject: (MutableMap.MutableEntry<K,
fun <K, V> MutableMap<K, V>.toSynchronised(): MutableMap<K, V> = Collections.synchronizedMap(this)
private fun isPackageValid(packageName: String): Boolean = packageName.isNotEmpty() && !packageName.endsWith(".") && packageName.split(".").all { token ->
Character.isJavaIdentifierStart(token[0]) && token.toCharArray().drop(1).all { Character.isJavaIdentifierPart(it) }
}
/**
* Check if a string is a legal Java package name.
* List implementation that applies the expensive [transform] function only when the element is accessed and caches calculated values.
* Size is very cheap as it doesn't call [transform].
*/
fun requirePackageValid(name: String) {
require(isPackageValid(name)) { "Invalid Java package name: `$name`." }
class LazyMappedList<T, U>(val originalList: List<T>, val transform: (T, Int) -> U) : AbstractList<U>() {
private val partialResolvedList = MutableList<U?>(originalList.size) { null }
override val size get() = originalList.size
override fun get(index: Int): U {
return partialResolvedList[index]
?: transform(originalList[index], index).also { computed -> partialResolvedList[index] = computed }
}
}

View File

@ -5,14 +5,12 @@ import net.corda.core.KeepForDJVM
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.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
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable
@ -91,7 +89,7 @@ private constructor(
componentGroups: List<ComponentGroup>? = null,
serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null,
inputStatesContractClassNameToMaxVersion: Map<ContractClassName,Version>
inputStatesContractClassNameToMaxVersion: Map<ContractClassName, Version>
): LedgerTransaction {
return LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, inputStatesContractClassNameToMaxVersion).apply {
this.componentGroups = componentGroups

View File

@ -4,8 +4,9 @@ import net.corda.core.KeepForDJVM
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionState
import net.corda.core.flows.FlowException
import net.corda.core.internal.Version
import net.corda.core.serialization.CordaSerializable
import net.corda.core.contracts.Version
/**
* A contract attachment was missing when trying to automatically attach all known contract attachments
*
@ -13,6 +14,9 @@ import net.corda.core.contracts.Version
*/
@CordaSerializable
@KeepForDJVM
class MissingContractAttachments @JvmOverloads constructor (val states: List<TransactionState<ContractState>>, minimumRequiredContractClassVersion: Version? = null)
: FlowException("Cannot find contract attachments for ${states.map { it.contract }.distinct()}${minimumRequiredContractClassVersion?.let { ", minimum required contract class version $minimumRequiredContractClassVersion"}}. " +
"See https://docs.corda.net/api-contract-constraints.html#debugging")
class MissingContractAttachments
@JvmOverloads
constructor(val states: List<TransactionState<ContractState>>, minimumRequiredContractClassVersion: Version? = null) : FlowException(
"Cannot find contract attachments for " +
"${states.map { it.contract }.distinct()}${minimumRequiredContractClassVersion?.let { ", minimum required contract class version $minimumRequiredContractClassVersion"}}. " +
"See https://docs.corda.net/api-contract-constraints.html#debugging")

View File

@ -4,7 +4,6 @@ import co.paralleluniverse.strands.Strand
import net.corda.core.CordaInternal
import net.corda.core.DeleteForDJVM
import net.corda.core.contracts.*
import net.corda.core.contracts.ContractAttachment.Companion.getContractVersion
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.internal.*
@ -15,8 +14,6 @@ import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.KeyManagementService
import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.Builder
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.utilities.contextLogger
@ -26,6 +23,7 @@ import java.time.Duration
import java.time.Instant
import java.util.ArrayDeque
import java.util.UUID
import kotlin.collections.ArrayList
import kotlin.collections.component1
import kotlin.collections.component2
@ -431,7 +429,7 @@ open class TransactionBuilder @JvmOverloads constructor(
require(constraints.none { it in automaticConstraints })
require(isReference || constraints.none { it is HashAttachmentConstraint })
val minimumRequiredContractClassVersion = stateRefs?.map { getContractVersion(services.loadContractAttachment(it)) }?.max() ?: DEFAULT_CORDAPP_VERSION
val minimumRequiredContractClassVersion = stateRefs?.map { services.loadContractAttachment(it).contractVersion }?.max() ?: DEFAULT_CORDAPP_VERSION
return services.attachments.getContractAttachmentWithHighestContractVersion(contractClassName, minimumRequiredContractClassVersion)
?: throw MissingContractAttachments(states, minimumRequiredContractClassVersion)
}

View File

@ -6,14 +6,10 @@ import net.corda.core.KeepForDJVM
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.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.*
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
import net.corda.core.node.ServicesForResolution
@ -181,7 +177,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
it.toStateAndRef()
}.groupBy { it.state.contract }
val inputStateContractClassToMaxVersion: Map<ContractClassName, Version> = inputStateContractClassToStateRefs.mapValues {
it.value.map { getContractVersion(resolveContractAttachment(it.ref)) }.max() ?: DEFAULT_CORDAPP_VERSION
it.value.map { resolveContractAttachment(it.ref).contractVersion }.max() ?: DEFAULT_CORDAPP_VERSION
}
val ltx = LedgerTransaction.create(

View File

@ -3,13 +3,16 @@ package net.corda.core.contracts
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SecureHash.Companion.allOnesHash
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.crypto.*
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.canBeTransitionedFrom
import net.corda.core.internal.inputStream
import net.corda.core.internal.toPath
import net.corda.core.node.NotaryInfo
@ -21,9 +24,12 @@ import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.*
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.core.internal.ContractJarTestUtils
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
import net.corda.testing.core.internal.SelfCleaningDir
import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.internal.rigorousMock
@ -31,8 +37,6 @@ import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import org.junit.*
import java.security.PublicKey
import org.junit.Rule
import org.junit.Test
import java.util.jar.Attributes
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
@ -52,8 +56,8 @@ class ConstraintsPropagationTests {
val BOB = TestIdentity(CordaX500Name("BOB", "London", "GB"))
val BOB_PARTY get() = BOB.party
val BOB_PUBKEY get() = BOB.publicKey
val noPropagationContractClassName = "net.corda.core.contracts.NoPropagationContract"
val propagatingContractClassName = "net.corda.core.contracts.PropagationContract"
const val noPropagationContractClassName = "net.corda.core.contracts.NoPropagationContract"
const val propagatingContractClassName = "net.corda.core.contracts.PropagationContract"
private lateinit var keyStoreDir: SelfCleaningDir
private lateinit var hashToSignatureConstraintsKey: PublicKey

View File

@ -2,6 +2,7 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.RetrieveAnyTransactionPayload
import net.corda.core.utilities.UntrustworthyData
// Flow to start data vending without sending transaction. For testing only.

View File

@ -2,9 +2,7 @@ package net.corda.node.internal.cordapp
import io.github.classgraph.ClassGraph
import io.github.classgraph.ScanResult
import net.corda.core.contracts.warnContractWithoutConstraintPropagation
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappInvalidVersionException
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.*
@ -359,6 +357,11 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
*/
class MultipleCordappsForFlowException(message: String) : Exception(message)
/**
* Thrown if an exception occurs whilst parsing version identifiers within cordapp configuration
*/
class CordappInvalidVersionException(msg: String) : Exception(msg)
abstract class CordappLoaderTemplate : CordappLoader {
override val flowCordappMap: Map<Class<out FlowLogic<*>>, Cordapp> by lazy {
cordapps.flatMap { corDapp -> corDapp.allFlows.map { flow -> flow to corDapp } }

View File

@ -10,10 +10,10 @@ import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.contracts.Version
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.internal.*
import net.corda.core.internal.Version
import net.corda.core.internal.cordapp.CordappImpl.Companion.CORDAPP_CONTRACT_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.node.ServicesForResolution
@ -39,9 +39,7 @@ import java.io.InputStream
import java.nio.file.Paths
import java.security.PublicKey
import java.time.Instant
import java.util.NavigableMap
import java.util.Optional
import java.util.TreeMap
import java.util.*
import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe
import javax.persistence.*