From 00672f97fa31eecab2d4d922b13a4ccdab318e82 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Mon, 24 Dec 2018 15:09:38 +0000 Subject: [PATCH] CORDA-2128: Moved constraints and attachments stuff out of the public API that shouldn't be there (#4460) --- .../core/contracts/AttachmentConstraint.kt | 126 ++-------------- .../corda/core/contracts/BelongsToContract.kt | 15 +- .../core/contracts/ContractAttachment.kt | 11 +- .../corda/core/contracts/TransactionState.kt | 1 + .../net/corda/core/contracts/Version.kt | 6 - .../net/corda/core/cordapp/ConfigException.kt | 5 - .../corda/core/flows/SendTransactionFlow.kt | 10 +- .../corda/core/internal/ConstraintsUtils.kt | 136 ++++++++++++++++++ .../net/corda/core/internal/CordaUtils.kt | 34 +++-- .../net/corda/core/internal/InternalUtils.kt | 17 ++- .../core/transactions/LedgerTransaction.kt | 4 +- .../MissingContractAttachments.kt | 12 +- .../core/transactions/TransactionBuilder.kt | 6 +- .../core/transactions/WireTransaction.kt | 8 +- .../contracts/ConstraintsPropagationTests.kt | 18 ++- .../flows/TestNoSecurityDataVendingFlow.kt | 1 + .../cordapp/JarScanningCordappLoader.kt | 7 +- .../persistence/NodeAttachmentService.kt | 6 +- 18 files changed, 218 insertions(+), 205 deletions(-) delete mode 100644 core/src/main/kotlin/net/corda/core/contracts/Version.kt create mode 100644 core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt diff --git a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt index 6787c7a2e1..576a2cdd62 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt @@ -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 }) -} \ No newline at end of file +data class SignatureAttachmentConstraint(val key: PublicKey) : AttachmentConstraint { + override fun isSatisfiedBy(attachment: Attachment): Boolean = key.isFulfilledBy(attachment.signerKeys.map { it }) +} diff --git a/core/src/main/kotlin/net/corda/core/contracts/BelongsToContract.kt b/core/src/main/kotlin/net/corda/core/contracts/BelongsToContract.kt index b5683f51aa..def0a947fa 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/BelongsToContract.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/BelongsToContract.kt @@ -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) -/** - * 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 -} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt index 29d893a3ed..b52b1e8ffe 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt @@ -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 - } - } -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt index dadda29255..8531eb82c1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionState.kt @@ -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 diff --git a/core/src/main/kotlin/net/corda/core/contracts/Version.kt b/core/src/main/kotlin/net/corda/core/contracts/Version.kt deleted file mode 100644 index b9afe43e43..0000000000 --- a/core/src/main/kotlin/net/corda/core/contracts/Version.kt +++ /dev/null @@ -1,6 +0,0 @@ -package net.corda.core.contracts - -/** - * Contract version and flow versions are integers. - */ -typealias Version = Int \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/cordapp/ConfigException.kt b/core/src/main/kotlin/net/corda/core/cordapp/ConfigException.kt index e1b5064aa5..6ca7fcb8ad 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/ConfigException.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/ConfigException.kt @@ -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) \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt index 33034efa07..0da13b1ea9 100644 --- a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt @@ -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() \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt b/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt new file mode 100644 index 0000000000..b163ee9461 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt @@ -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() + +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.") + } +} diff --git a/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt b/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt index ecab0e89be..099e47e6d4 100644 --- a/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt @@ -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(val originalList: List, val transform: (T, Int) -> U) : AbstractList() { - private val partialResolvedList = MutableList(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() diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 8b23024208..0f074130ac 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -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 createSimpleCache(maxSize: Int, onEject: (MutableMap.MutableEntry MutableMap.toSynchronised(): MutableMap = 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(val originalList: List, val transform: (T, Int) -> U) : AbstractList() { + private val partialResolvedList = MutableList(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 } + } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index 6bb6dbe290..d45cbada70 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -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? = null, serializedInputs: List? = null, serializedReferences: List? = null, - inputStatesContractClassNameToMaxVersion: Map + inputStatesContractClassNameToMaxVersion: Map ): LedgerTransaction { return LedgerTransaction(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, inputStatesContractClassNameToMaxVersion).apply { this.componentGroups = componentGroups diff --git a/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt b/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt index 3ff5daf190..407d00ecbc 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MissingContractAttachments.kt @@ -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>, 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") \ No newline at end of file +class MissingContractAttachments +@JvmOverloads +constructor(val states: List>, 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") diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt index e7c6c75fba..78da36a12e 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -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) } diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index b4a88e0796..40c4c3091a 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -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, val privacySalt: Pr it.toStateAndRef() }.groupBy { it.state.contract } val inputStateContractClassToMaxVersion: Map = 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( diff --git a/core/src/test/kotlin/net/corda/core/contracts/ConstraintsPropagationTests.kt b/core/src/test/kotlin/net/corda/core/contracts/ConstraintsPropagationTests.kt index 3b678fa20b..d77a660132 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/ConstraintsPropagationTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/ConstraintsPropagationTests.kt @@ -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 diff --git a/core/src/test/kotlin/net/corda/core/flows/TestNoSecurityDataVendingFlow.kt b/core/src/test/kotlin/net/corda/core/flows/TestNoSecurityDataVendingFlow.kt index 23d61e1c01..94181067e8 100644 --- a/core/src/test/kotlin/net/corda/core/flows/TestNoSecurityDataVendingFlow.kt +++ b/core/src/test/kotlin/net/corda/core/flows/TestNoSecurityDataVendingFlow.kt @@ -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. diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt index b63fbe9577..4a319df1eb 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt @@ -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>, Cordapp> by lazy { cordapps.flatMap { corDapp -> corDapp.allFlows.map { flow -> flow to corDapp } } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 5f8c673dae..985e7693fc 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -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.*