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 package net.corda.core.contracts
import net.corda.core.CordaInternal
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint.isSatisfiedBy import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint.isSatisfiedBy
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.isFulfilledBy
import net.corda.core.crypto.keys
import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.isUploaderTrusted import net.corda.core.internal.isUploaderTrusted
import net.corda.core.serialization.internal.AttachmentsClassLoader
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.contextLogger import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.warnOnce
import org.slf4j.LoggerFactory
import java.lang.annotation.Inherited import java.lang.annotation.Inherited
import java.security.PublicKey import java.security.PublicKey
@ -34,72 +29,6 @@ annotation class NoConstraintPropagation
interface AttachmentConstraint { interface AttachmentConstraint {
/** Returns whether the given contract attachment can be used with the [ContractState] associated with this constraint object. */ /** Returns whether the given contract attachment can be used with the [ContractState] associated with this constraint object. */
fun isSatisfiedBy(attachment: Attachment): Boolean 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. */ /** An [AttachmentConstraint] where [isSatisfiedBy] always returns true. */
@ -138,7 +67,11 @@ object WhitelistedByZoneAttachmentConstraint : AttachmentConstraint {
} }
@KeepForDJVM @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 { object AutomaticHashConstraint : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment): Boolean { override fun isSatisfiedBy(attachment: Attachment): Boolean {
throw UnsupportedOperationException("Contracts cannot be satisfied by an AutomaticHashConstraint placeholder.") 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. * This [AttachmentConstraint] is a convenience class that acts as a placeholder and will be automatically resolved by the platform when set
* It is the default constraint of all output states. * 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. * 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. * 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]. * 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) * 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 @KeepForDJVM
data class SignatureAttachmentConstraint( data class SignatureAttachmentConstraint(val key: PublicKey) : AttachmentConstraint {
val key: PublicKey override fun isSatisfiedBy(attachment: Attachment): Boolean = key.isFulfilledBy(attachment.signerKeys.map { it })
) : AttachmentConstraint { }
override fun isSatisfiedBy(attachment: Attachment): Boolean =
key.isFulfilledBy(attachment.signerKeys.map { it })
}

View File

@ -1,5 +1,7 @@
package net.corda.core.contracts package net.corda.core.contracts
import kotlin.reflect.KClass 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 * 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 * [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) @Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
annotation class BelongsToContract(val value: KClass<out Contract>) 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 { override fun toString(): String {
return "ContractAttachment(attachment=${attachment.id}, contracts='$allContracts', uploader='$uploader', signed='$isSigned', version='$version')" 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.KeepForDJVM
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.requiredContractClassName
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.loggerFor 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 * 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,8 +4,8 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.FetchDataFlow import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.RetrieveAnyTransactionPayload
import net.corda.core.internal.readFully import net.corda.core.internal.readFully
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap 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.CordappConfig
import net.corda.core.cordapp.CordappContext import net.corda.core.cordapp.CordappContext
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.DataVendingFlow
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
@ -61,15 +63,25 @@ internal fun SignedTransaction.pushToLoggingContext() {
MDC.put("tx_id", id.toString()) MDC.put("tx_id", id.toString())
} }
/** private fun isPackageValid(packageName: String): Boolean {
* List implementation that applies the expensive [transform] function only when the element is accessed and caches calculated values. return packageName.isNotEmpty() &&
* Size is very cheap as it doesn't call [transform]. !packageName.endsWith(".") &&
*/ packageName.split(".").all { token ->
class LazyMappedList<T, U>(val originalList: List<T>, val transform: (T, Int) -> U) : AbstractList<U>() { Character.isJavaIdentifierStart(token[0]) && token.toCharArray().drop(1).all { Character.isJavaIdentifierPart(it) }
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 }
} }
/**
* 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.Deflater
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
import kotlin.collections.AbstractList
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance 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) 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) { class LazyMappedList<T, U>(val originalList: List<T>, val transform: (T, Int) -> U) : AbstractList<U>() {
require(isPackageValid(name)) { "Invalid Java package name: `$name`." } 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.*
import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException
import net.corda.core.contracts.TransactionVerificationException.TransactionRequiredContractUnspecifiedException import net.corda.core.contracts.TransactionVerificationException.TransactionRequiredContractUnspecifiedException
import net.corda.core.contracts.Version
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.rules.StateContractValidationEnforcementRule import net.corda.core.internal.rules.StateContractValidationEnforcementRule
import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.ConstructorForDeserialization import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -91,7 +89,7 @@ private constructor(
componentGroups: List<ComponentGroup>? = null, componentGroups: List<ComponentGroup>? = null,
serializedInputs: List<SerializedStateAndRef>? = null, serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null, serializedReferences: List<SerializedStateAndRef>? = null,
inputStatesContractClassNameToMaxVersion: Map<ContractClassName,Version> inputStatesContractClassNameToMaxVersion: Map<ContractClassName, Version>
): LedgerTransaction { ): LedgerTransaction {
return LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, inputStatesContractClassNameToMaxVersion).apply { return LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, inputStatesContractClassNameToMaxVersion).apply {
this.componentGroups = componentGroups 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.ContractState
import net.corda.core.contracts.TransactionState import net.corda.core.contracts.TransactionState
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.internal.Version
import net.corda.core.serialization.CordaSerializable 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 * 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 @CordaSerializable
@KeepForDJVM @KeepForDJVM
class MissingContractAttachments @JvmOverloads constructor (val states: List<TransactionState<ContractState>>, minimumRequiredContractClassVersion: Version? = null) class MissingContractAttachments
: FlowException("Cannot find contract attachments for ${states.map { it.contract }.distinct()}${minimumRequiredContractClassVersion?.let { ", minimum required contract class version $minimumRequiredContractClassVersion"}}. " + @JvmOverloads
"See https://docs.corda.net/api-contract-constraints.html#debugging") 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.CordaInternal
import net.corda.core.DeleteForDJVM import net.corda.core.DeleteForDJVM
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.contracts.ContractAttachment.Companion.getContractVersion
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.* 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.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.KeyManagementService 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.SerializationContext
import net.corda.core.serialization.SerializationFactory import net.corda.core.serialization.SerializationFactory
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
@ -26,6 +23,7 @@ import java.time.Duration
import java.time.Instant import java.time.Instant
import java.util.ArrayDeque import java.util.ArrayDeque
import java.util.UUID import java.util.UUID
import kotlin.collections.ArrayList
import kotlin.collections.component1 import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
@ -431,7 +429,7 @@ open class TransactionBuilder @JvmOverloads constructor(
require(constraints.none { it in automaticConstraints }) require(constraints.none { it in automaticConstraints })
require(isReference || constraints.none { it is HashAttachmentConstraint }) 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) return services.attachments.getContractAttachmentWithHighestContractVersion(contractClassName, minimumRequiredContractClassVersion)
?: throw MissingContractAttachments(states, 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.*
import net.corda.core.contracts.ComponentGroupEnum.COMMANDS_GROUP import net.corda.core.contracts.ComponentGroupEnum.COMMANDS_GROUP
import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_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.crypto.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.*
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.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.createComponentGroups
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
@ -181,7 +177,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
it.toStateAndRef() it.toStateAndRef()
}.groupBy { it.state.contract } }.groupBy { it.state.contract }
val inputStateContractClassToMaxVersion: Map<ContractClassName, Version> = inputStateContractClassToStateRefs.mapValues { 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( 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.doReturn
import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SecureHash.Companion.allOnesHash import net.corda.core.crypto.SecureHash.Companion.allOnesHash
import net.corda.core.crypto.SecureHash.Companion.zeroHash 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.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.canBeTransitionedFrom
import net.corda.core.internal.inputStream import net.corda.core.internal.inputStream
import net.corda.core.internal.toPath import net.corda.core.internal.toPath
import net.corda.core.node.NotaryInfo 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.finance.contracts.asset.Cash
import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.* import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey 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.ContractJarTestUtils
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
import net.corda.testing.core.internal.SelfCleaningDir import net.corda.testing.core.internal.SelfCleaningDir
import net.corda.testing.internal.MockCordappProvider import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
@ -31,8 +37,6 @@ import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger import net.corda.testing.node.ledger
import org.junit.* import org.junit.*
import java.security.PublicKey import java.security.PublicKey
import org.junit.Rule
import org.junit.Test
import java.util.jar.Attributes import java.util.jar.Attributes
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertFalse import kotlin.test.assertFalse
@ -52,8 +56,8 @@ class ConstraintsPropagationTests {
val BOB = TestIdentity(CordaX500Name("BOB", "London", "GB")) val BOB = TestIdentity(CordaX500Name("BOB", "London", "GB"))
val BOB_PARTY get() = BOB.party val BOB_PARTY get() = BOB.party
val BOB_PUBKEY get() = BOB.publicKey val BOB_PUBKEY get() = BOB.publicKey
val noPropagationContractClassName = "net.corda.core.contracts.NoPropagationContract" const val noPropagationContractClassName = "net.corda.core.contracts.NoPropagationContract"
val propagatingContractClassName = "net.corda.core.contracts.PropagationContract" const val propagatingContractClassName = "net.corda.core.contracts.PropagationContract"
private lateinit var keyStoreDir: SelfCleaningDir private lateinit var keyStoreDir: SelfCleaningDir
private lateinit var hashToSignatureConstraintsKey: PublicKey private lateinit var hashToSignatureConstraintsKey: PublicKey

View File

@ -2,6 +2,7 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.internal.FetchDataFlow import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.RetrieveAnyTransactionPayload
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
// Flow to start data vending without sending transaction. For testing only. // 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.ClassGraph
import io.github.classgraph.ScanResult import io.github.classgraph.ScanResult
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.*
@ -359,6 +357,11 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
*/ */
class MultipleCordappsForFlowException(message: String) : Exception(message) 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 { abstract class CordappLoaderTemplate : CordappLoader {
override val flowCordappMap: Map<Class<out FlowLogic<*>>, Cordapp> by lazy { override val flowCordappMap: Map<Class<out FlowLogic<*>>, Cordapp> by lazy {
cordapps.flatMap { corDapp -> corDapp.allFlows.map { flow -> flow to corDapp } } 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.Attachment
import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.ContractClassName
import net.corda.core.contracts.Version
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.internal.* 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.CORDAPP_CONTRACT_VERSION
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
@ -39,9 +39,7 @@ import java.io.InputStream
import java.nio.file.Paths import java.nio.file.Paths
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
import java.util.NavigableMap import java.util.*
import java.util.Optional
import java.util.TreeMap
import java.util.jar.JarInputStream import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
import javax.persistence.* import javax.persistence.*