diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 7aa6e716dd..298c9ec549 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -338,17 +338,6 @@ public final class net.corda.core.contracts.ComponentGroupEnum extends java.lang public static net.corda.core.contracts.ComponentGroupEnum valueOf(String) public static net.corda.core.contracts.ComponentGroupEnum[] values() ## -@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.ConstraintAttachment extends java.lang.Object implements net.corda.core.contracts.Attachment - public (net.corda.core.contracts.ContractAttachment, String) - public void extractFile(String, java.io.OutputStream) - @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.ContractAttachment getContractAttachment() - @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() - @org.jetbrains.annotations.NotNull public List getSigners() - public int getSize() - @org.jetbrains.annotations.NotNull public final String getStateContract() - @org.jetbrains.annotations.NotNull public java.io.InputStream open() - @org.jetbrains.annotations.NotNull public jar.JarInputStream openAsJAR() -## @net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.Contract public abstract void verify(net.corda.core.transactions.LedgerTransaction) ## @@ -641,7 +630,6 @@ public static final class net.corda.core.contracts.UniqueIdentifier$Companion ex @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.ContractState upgrade(net.corda.core.contracts.ContractState) ## @net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint - public (Map) public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) ## @net.corda.core.DoNotImplement public interface net.corda.core.cordapp.Cordapp 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 437fafa4a4..fbce9daa24 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/AttachmentConstraint.kt @@ -1,8 +1,9 @@ package net.corda.core.contracts import net.corda.core.DoNotImplement +import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint.isSatisfiedBy import net.corda.core.crypto.SecureHash -import net.corda.core.node.services.AttachmentId +import net.corda.core.internal.AttachmentWithContext import net.corda.core.serialization.CordaSerializable /** Constrain which contract-code-containing attachment can be used with a [ContractState]. */ @@ -27,18 +28,14 @@ data class HashAttachmentConstraint(val attachmentId: SecureHash) : AttachmentCo * An [AttachmentConstraint] that verifies that the hash of the attachment is in the network parameters whitelist. * See: [net.corda.core.node.NetworkParameters.whitelistedContractImplementations] * It allows for centralized control over the cordapps that can be used. - * - * @param whitelistedContractImplementations whitelisted attachment IDs by contract class name. */ -class WhitelistedByZoneAttachmentConstraint(private val whitelistedContractImplementations: Map>) : AttachmentConstraint { - +object WhitelistedByZoneAttachmentConstraint : AttachmentConstraint { override fun isSatisfiedBy(attachment: Attachment): Boolean { - return whitelistedContractImplementations.let { whitelist -> - when (attachment) { - is ConstraintAttachment -> attachment.id in (whitelist[attachment.stateContract] ?: emptyList()) - else -> false - } - } + return if (attachment is AttachmentWithContext) { + val whitelist = attachment.whitelistedContractImplementations + ?: throw IllegalStateException("Unable to verify WhitelistedByZoneAttachmentConstraint - whitelist not specified") + attachment.id in (whitelist[attachment.stateContract] ?: emptyList()) + } else false } } @@ -56,16 +53,4 @@ object AutomaticHashConstraint : AttachmentConstraint { override fun isSatisfiedBy(attachment: Attachment): Boolean { throw UnsupportedOperationException("Contracts cannot be satisfied by an AutomaticHashConstraint placeholder") } -} - -/** - * Used only for passing to the Attachment constraint verification. - * Encapsulates a [ContractAttachment] and the state contract - */ -class ConstraintAttachment(val contractAttachment: ContractAttachment, val stateContract: ContractClassName) : Attachment by contractAttachment { - init { - require(stateContract in contractAttachment.allContracts) { - "This ConstraintAttachment was not initialised properly" - } - } -} +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/internal/AttachmentWithContext.kt b/core/src/main/kotlin/net/corda/core/internal/AttachmentWithContext.kt new file mode 100644 index 0000000000..677ca29a8d --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/internal/AttachmentWithContext.kt @@ -0,0 +1,22 @@ +package net.corda.core.internal + +import net.corda.core.contracts.Attachment +import net.corda.core.contracts.ContractAttachment +import net.corda.core.contracts.ContractClassName +import net.corda.core.node.services.AttachmentId + +/** + * Used only for passing to the Attachment constraint verification. + */ +class AttachmentWithContext( + val contractAttachment: ContractAttachment, + val stateContract: ContractClassName, + /** Required for verifying [WhitelistedByZoneAttachmentConstraint] */ + val whitelistedContractImplementations: Map>? +) : Attachment by contractAttachment { + init { + require(stateContract in contractAttachment.allContracts) { + "This AttachmentWithContext was not initialised properly" + } + } +} \ No newline at end of file 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 e34b2f7bf6..632a725dd1 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -3,9 +3,11 @@ package net.corda.core.transactions import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party +import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.UpgradeCommand import net.corda.core.internal.castIfPossible import net.corda.core.internal.uncheckedCast +import net.corda.core.node.NetworkParameters import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.Try import java.security.PublicKey @@ -27,7 +29,7 @@ import java.util.function.Predicate // currently sends this across to out-of-process verifiers. We'll need to change that first. // DOCSTART 1 @CordaSerializable -data class LedgerTransaction( +data class LedgerTransaction @JvmOverloads constructor( /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */ override val inputs: List>, override val outputs: List>, @@ -39,7 +41,8 @@ data class LedgerTransaction( override val id: SecureHash, override val notary: Party?, val timeWindow: TimeWindow?, - val privacySalt: PrivacySalt + val privacySalt: PrivacySalt, + private val networkParameters: NetworkParameters? = null ) : FullTransaction() { //DOCEND 1 init { @@ -108,7 +111,8 @@ data class LedgerTransaction( } val contractAttachment = uniqueAttachmentsForStateContract.first() - if (!state.constraint.isSatisfiedBy(ConstraintAttachment(contractAttachment, state.contract))) { + val constraintAttachment = AttachmentWithContext(contractAttachment, state.contract, networkParameters?.whitelistedContractImplementations) + if (!state.constraint.isSatisfiedBy(constraintAttachment)) { throw TransactionVerificationException.ContractConstraintRejection(id, state.contract) } } @@ -414,5 +418,17 @@ data class LedgerTransaction( * @throws IllegalArgumentException if no item matches the id. */ fun getAttachment(id: SecureHash): Attachment = attachments.first { it.id == id } + + fun copy(inputs: List>, + outputs: List>, + commands: List>, + attachments: List, + id: SecureHash, + notary: Party?, + timeWindow: TimeWindow?, + privacySalt: PrivacySalt + ) = copy(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null) } + + 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 12083ee26d..3204e15580 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -103,7 +103,7 @@ open class TransactionBuilder( val resolvedOutputs = outputs.map { state -> when { state.constraint !is AutomaticHashConstraint -> state - useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint(services.networkParameters.whitelistedContractImplementations)) + useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint) else -> services.cordappProvider.getContractAttachmentID(state.contract)?.let { state.copy(constraint = HashAttachmentConstraint(it)) } ?: throw MissingContractAttachments(listOf(state)) 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 206290c507..273c304912 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -5,6 +5,7 @@ import net.corda.core.contracts.ComponentGroupEnum.* import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.internal.Emoji +import net.corda.core.node.NetworkParameters import net.corda.core.node.ServicesForResolution import net.corda.core.node.services.AttachmentId import net.corda.core.serialization.CordaSerializable @@ -88,7 +89,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr resolveIdentity = { services.identityService.partyFromKey(it) }, resolveAttachment = { services.attachments.openAttachment(it) }, resolveStateRef = { services.loadState(it) }, - maxTransactionSize = services.networkParameters.maxTransactionSize + networkParameters = services.networkParameters ) } @@ -107,14 +108,14 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr resolveStateRef: (StateRef) -> TransactionState<*>?, resolveContractAttachment: (TransactionState) -> AttachmentId? ): LedgerTransaction { - return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, 10485760) + return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef,null) } private fun toLedgerTransactionInternal( resolveIdentity: (PublicKey) -> Party?, resolveAttachment: (SecureHash) -> Attachment?, resolveStateRef: (StateRef) -> TransactionState<*>?, - maxTransactionSize: Int + networkParameters: NetworkParameters? ): LedgerTransaction { // Look up public keys to authenticated identities. val authenticatedArgs = commands.map { @@ -125,8 +126,8 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash) } val attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } - val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt) - checkTransactionSize(ltx, maxTransactionSize) + val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt, networkParameters) + checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: 10485760) return ltx }