CORDA-941 - Instead of storing the network map contract implementation whitelist along with (#2581)

the constraint, pass it in during verification on LedgerTransaction
This commit is contained in:
Andrius Dagys 2018-02-21 16:45:50 +00:00 committed by Katelyn Baker
parent adf9d50940
commit 7358e902a5
6 changed files with 57 additions and 45 deletions

View File

@ -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 valueOf(String)
public static net.corda.core.contracts.ComponentGroupEnum[] values() 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 <init>(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 @net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.Contract
public abstract void verify(net.corda.core.transactions.LedgerTransaction) 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) @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 @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 <init>(Map)
public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
## ##
@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.Cordapp @net.corda.core.DoNotImplement public interface net.corda.core.cordapp.Cordapp

View File

@ -1,8 +1,9 @@
package net.corda.core.contracts package net.corda.core.contracts
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint.isSatisfiedBy
import net.corda.core.crypto.SecureHash 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 import net.corda.core.serialization.CordaSerializable
/** Constrain which contract-code-containing attachment can be used with a [ContractState]. */ /** 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. * An [AttachmentConstraint] that verifies that the hash of the attachment is in the network parameters whitelist.
* See: [net.corda.core.node.NetworkParameters.whitelistedContractImplementations] * See: [net.corda.core.node.NetworkParameters.whitelistedContractImplementations]
* It allows for centralized control over the cordapps that can be used. * 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<String, List<AttachmentId>>) : AttachmentConstraint { object WhitelistedByZoneAttachmentConstraint : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment): Boolean { override fun isSatisfiedBy(attachment: Attachment): Boolean {
return whitelistedContractImplementations.let { whitelist -> return if (attachment is AttachmentWithContext) {
when (attachment) { val whitelist = attachment.whitelistedContractImplementations
is ConstraintAttachment -> attachment.id in (whitelist[attachment.stateContract] ?: emptyList()) ?: throw IllegalStateException("Unable to verify WhitelistedByZoneAttachmentConstraint - whitelist not specified")
else -> false attachment.id in (whitelist[attachment.stateContract] ?: emptyList())
} } else false
}
} }
} }
@ -56,16 +53,4 @@ 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")
} }
} }
/**
* 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"
}
}
}

View File

@ -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<String, List<AttachmentId>>?
) : Attachment by contractAttachment {
init {
require(stateContract in contractAttachment.allContracts) {
"This AttachmentWithContext was not initialised properly"
}
}
}

View File

@ -3,9 +3,11 @@ package net.corda.core.transactions
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.UpgradeCommand import net.corda.core.internal.UpgradeCommand
import net.corda.core.internal.castIfPossible import net.corda.core.internal.castIfPossible
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.Try import net.corda.core.utilities.Try
import java.security.PublicKey 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. // currently sends this across to out-of-process verifiers. We'll need to change that first.
// DOCSTART 1 // DOCSTART 1
@CordaSerializable @CordaSerializable
data class LedgerTransaction( data class LedgerTransaction @JvmOverloads constructor(
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */ /** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
override val inputs: List<StateAndRef<ContractState>>, override val inputs: List<StateAndRef<ContractState>>,
override val outputs: List<TransactionState<ContractState>>, override val outputs: List<TransactionState<ContractState>>,
@ -39,7 +41,8 @@ data class LedgerTransaction(
override val id: SecureHash, override val id: SecureHash,
override val notary: Party?, override val notary: Party?,
val timeWindow: TimeWindow?, val timeWindow: TimeWindow?,
val privacySalt: PrivacySalt val privacySalt: PrivacySalt,
private val networkParameters: NetworkParameters? = null
) : FullTransaction() { ) : FullTransaction() {
//DOCEND 1 //DOCEND 1
init { init {
@ -108,7 +111,8 @@ data class LedgerTransaction(
} }
val contractAttachment = uniqueAttachmentsForStateContract.first() 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) throw TransactionVerificationException.ContractConstraintRejection(id, state.contract)
} }
} }
@ -414,5 +418,17 @@ data class LedgerTransaction(
* @throws IllegalArgumentException if no item matches the id. * @throws IllegalArgumentException if no item matches the id.
*/ */
fun getAttachment(id: SecureHash): Attachment = attachments.first { it.id == id } fun getAttachment(id: SecureHash): Attachment = attachments.first { it.id == id }
fun copy(inputs: List<StateAndRef<ContractState>>,
outputs: List<TransactionState<ContractState>>,
commands: List<CommandWithParties<CommandData>>,
attachments: List<Attachment>,
id: SecureHash,
notary: Party?,
timeWindow: TimeWindow?,
privacySalt: PrivacySalt
) = copy(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null)
} }

View File

@ -103,7 +103,7 @@ open class TransactionBuilder(
val resolvedOutputs = outputs.map { state -> val resolvedOutputs = outputs.map { state ->
when { when {
state.constraint !is AutomaticHashConstraint -> state 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 { else -> services.cordappProvider.getContractAttachmentID(state.contract)?.let {
state.copy(constraint = HashAttachmentConstraint(it)) state.copy(constraint = HashAttachmentConstraint(it))
} ?: throw MissingContractAttachments(listOf(state)) } ?: throw MissingContractAttachments(listOf(state))

View File

@ -5,6 +5,7 @@ import net.corda.core.contracts.ComponentGroupEnum.*
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.Emoji import net.corda.core.internal.Emoji
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -88,7 +89,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveIdentity = { services.identityService.partyFromKey(it) }, resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(it) }, resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRef = { services.loadState(it) }, resolveStateRef = { services.loadState(it) },
maxTransactionSize = services.networkParameters.maxTransactionSize networkParameters = services.networkParameters
) )
} }
@ -107,14 +108,14 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveStateRef: (StateRef) -> TransactionState<*>?, resolveStateRef: (StateRef) -> TransactionState<*>?,
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId? resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
): LedgerTransaction { ): LedgerTransaction {
return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, 10485760) return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef,null)
} }
private fun toLedgerTransactionInternal( private fun toLedgerTransactionInternal(
resolveIdentity: (PublicKey) -> Party?, resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?, resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?, resolveStateRef: (StateRef) -> TransactionState<*>?,
maxTransactionSize: Int networkParameters: NetworkParameters?
): LedgerTransaction { ): LedgerTransaction {
// Look up public keys to authenticated identities. // Look up public keys to authenticated identities.
val authenticatedArgs = commands.map { val authenticatedArgs = commands.map {
@ -125,8 +126,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash) resolveStateRef(ref)?.let { StateAndRef(it, ref) } ?: throw TransactionResolutionException(ref.txhash)
} }
val attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } val attachments = attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) }
val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt) val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt, networkParameters)
checkTransactionSize(ltx, maxTransactionSize) checkTransactionSize(ltx, networkParameters?.maxTransactionSize ?: 10485760)
return ltx return ltx
} }