mirror of
https://github.com/corda/corda.git
synced 2024-12-20 13:33:12 +00:00
CORDA-2871: Refactor the contract verification code into a separate class,
and allow LedgerTransaction to choose different Verifier objects.
This commit is contained in:
parent
444881d536
commit
840e717ccf
@ -1,12 +1,15 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
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.crypto.SecureHash
|
||||||
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
|
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
@DeleteForDJVM
|
@DeleteForDJVM
|
||||||
interface TransactionVerifierServiceInternal {
|
interface TransactionVerifierServiceInternal {
|
||||||
@ -25,10 +28,8 @@ fun LedgerTransaction.prepareVerify(extraAttachments: List<Attachment>) = this.i
|
|||||||
/**
|
/**
|
||||||
* Because we create a separate [LedgerTransaction] onto which we need to perform verification, it becomes important we don't verify the
|
* Because we create a separate [LedgerTransaction] onto which we need to perform verification, it becomes important we don't verify the
|
||||||
* wrong object instance. This class helps avoid that.
|
* wrong object instance. This class helps avoid that.
|
||||||
*
|
|
||||||
* @param inputVersions A map linking each contract class name to the advertised version of the JAR that defines it. Used for downgrade protection.
|
|
||||||
*/
|
*/
|
||||||
class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: ClassLoader) {
|
open class Verifier(val ltx: LedgerTransaction, protected open val transactionClassLoader: ClassLoader) {
|
||||||
private val inputStates: List<TransactionState<*>> = ltx.inputs.map { it.state }
|
private val inputStates: List<TransactionState<*>> = ltx.inputs.map { it.state }
|
||||||
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map { it.state } + ltx.outputs
|
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map { it.state } + ltx.outputs
|
||||||
|
|
||||||
@ -140,7 +141,7 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C
|
|||||||
.withIndex()
|
.withIndex()
|
||||||
.filter { it.value.encumbrance != null }
|
.filter { it.value.encumbrance != null }
|
||||||
.map { Pair(it.index, it.value.encumbrance!!) }
|
.map { Pair(it.index, it.value.encumbrance!!) }
|
||||||
if (!statesAndEncumbrance.isEmpty()) {
|
if (statesAndEncumbrance.isNotEmpty()) {
|
||||||
checkBidirectionalOutputEncumbrances(statesAndEncumbrance)
|
checkBidirectionalOutputEncumbrances(statesAndEncumbrance)
|
||||||
checkNotariesOutputEncumbrance(statesAndEncumbrance)
|
checkNotariesOutputEncumbrance(statesAndEncumbrance)
|
||||||
}
|
}
|
||||||
@ -349,22 +350,42 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C
|
|||||||
*
|
*
|
||||||
* Note: Reference states are not verified.
|
* Note: Reference states are not verified.
|
||||||
*/
|
*/
|
||||||
private fun verifyContracts() {
|
open fun verifyContracts() {
|
||||||
|
try {
|
||||||
// Loads the contract class from the transactionClassLoader.
|
ContractVerifier(transactionClassLoader).apply(ltx)
|
||||||
fun contractClassFor(className: ContractClassName) = try {
|
} catch (e: TransactionVerificationException.ContractRejection) {
|
||||||
transactionClassLoader.loadClass(className).asSubclass(Contract::class.java)
|
logger.error("Error validating transaction ${ltx.id}.", e.cause)
|
||||||
} catch (e: Exception) {
|
throw e
|
||||||
throw TransactionVerificationException.ContractCreationError(ltx.id, className, e)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val contractClasses: Map<ContractClassName, Class<out Contract>> = (inputStates + ltx.outputs)
|
/**
|
||||||
.map { it.contract }
|
* Verify all of the contracts on the given [LedgerTransaction].
|
||||||
.toSet()
|
*/
|
||||||
.map { contract -> contract to contractClassFor(contract) }
|
@KeepForDJVM
|
||||||
.toMap()
|
class ContractVerifier(private val transactionClassLoader: ClassLoader) : Function<LedgerTransaction, Unit> {
|
||||||
|
// This constructor is used inside the DJVM's sandbox.
|
||||||
|
@Suppress("unused")
|
||||||
|
constructor() : this(ClassLoader.getSystemClassLoader())
|
||||||
|
|
||||||
val contractInstances: List<Contract> = contractClasses.map { (contractClassName, contractClass) ->
|
// Loads the contract class from the transactionClassLoader.
|
||||||
|
private fun createContractClass(id: SecureHash, contractClassName: ContractClassName): Class<out Contract> {
|
||||||
|
return try {
|
||||||
|
Class.forName(contractClassName, false, transactionClassLoader).asSubclass(Contract::class.java)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw TransactionVerificationException.ContractCreationError(id, contractClassName, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(ltx: LedgerTransaction) {
|
||||||
|
val contractClassNames = (ltx.inputs.map(StateAndRef<ContractState>::state) + ltx.outputs)
|
||||||
|
.map(TransactionState<*>::contract)
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
contractClassNames.associateBy(
|
||||||
|
{ it }, { createContractClass(ltx.id, it) }
|
||||||
|
).map { (contractClassName, contractClass) ->
|
||||||
try {
|
try {
|
||||||
/**
|
/**
|
||||||
* This function must execute with the DJVM's sandbox, which does not
|
* This function must execute with the DJVM's sandbox, which does not
|
||||||
@ -377,13 +398,10 @@ class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: C
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw TransactionVerificationException.ContractCreationError(ltx.id, contractClassName, e)
|
throw TransactionVerificationException.ContractCreationError(ltx.id, contractClassName, e)
|
||||||
}
|
}
|
||||||
}
|
}.forEach { contract ->
|
||||||
|
|
||||||
contractInstances.forEach { contract ->
|
|
||||||
try {
|
try {
|
||||||
contract.verify(ltx)
|
contract.verify(ltx)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.error("Error validating transaction ${ltx.id}.", e)
|
|
||||||
throw TransactionVerificationException.ContractRejection(ltx.id, contract, e)
|
throw TransactionVerificationException.ContractRejection(ltx.id, contract, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,7 @@ private constructor(
|
|||||||
private var serializedInputs: List<SerializedStateAndRef>? = null
|
private var serializedInputs: List<SerializedStateAndRef>? = null
|
||||||
private var serializedReferences: List<SerializedStateAndRef>? = null
|
private var serializedReferences: List<SerializedStateAndRef>? = null
|
||||||
private var isAttachmentTrusted: (Attachment) -> Boolean = { it.isUploaderTrusted() }
|
private var isAttachmentTrusted: (Attachment) -> Boolean = { it.isUploaderTrusted() }
|
||||||
|
private var verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier = ::Verifier
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
|
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
|
||||||
@ -151,10 +152,30 @@ private constructor(
|
|||||||
// Create a copy of the outer LedgerTransaction which deserializes all fields inside the [transactionClassLoader].
|
// Create a copy of the outer LedgerTransaction which deserializes all fields inside the [transactionClassLoader].
|
||||||
// Only the copy will be used for verification, and the outer shell will be discarded.
|
// Only the copy will be used for verification, and the outer shell will be discarded.
|
||||||
// This artifice is required to preserve backwards compatibility.
|
// This artifice is required to preserve backwards compatibility.
|
||||||
Verifier(createLtxForVerification(), transactionClassLoader)
|
verifierFactory(createLtxForVerification(), transactionClassLoader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need a way to customise transaction verification inside the
|
||||||
|
* Node without changing either the wire format or any public APIs.
|
||||||
|
*/
|
||||||
|
@CordaInternal
|
||||||
|
fun specialise(alternateVerifier: (LedgerTransaction, ClassLoader) -> Verifier): LedgerTransaction = LedgerTransaction(
|
||||||
|
inputs = inputs,
|
||||||
|
outputs = outputs,
|
||||||
|
commands = commands,
|
||||||
|
attachments = attachments,
|
||||||
|
id = id,
|
||||||
|
notary = notary,
|
||||||
|
timeWindow = timeWindow,
|
||||||
|
privacySalt = privacySalt,
|
||||||
|
networkParameters = networkParameters,
|
||||||
|
references = references
|
||||||
|
).also { ltx ->
|
||||||
|
ltx.verifierFactory = alternateVerifier
|
||||||
|
}
|
||||||
|
|
||||||
// Read network parameters with backwards compatibility goo.
|
// Read network parameters with backwards compatibility goo.
|
||||||
private fun getParamsWithGoo(): NetworkParameters {
|
private fun getParamsWithGoo(): NetworkParameters {
|
||||||
var params = networkParameters
|
var params = networkParameters
|
||||||
|
Loading…
Reference in New Issue
Block a user