mirror of
https://github.com/corda/corda.git
synced 2025-03-22 12:05:59 +00:00
Minor refactorings.
Take out a useless parameter from a method that was added to the public API, document it. Add some comments explaining more about why we are looking up attachment versions in WireTransaction.toLedgerTransaction.
This commit is contained in:
parent
5f70abfeca
commit
0a9f4c68ae
@ -28,7 +28,7 @@ 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
|
||||
* wrong object instance. This class helps avoid that.
|
||||
*/
|
||||
class Verifier(val ltx: LedgerTransaction, val transactionClassLoader: ClassLoader, private val inputStatesContractClassNameToMaxVersion: Map<ContractClassName, Version>) {
|
||||
class Verifier(val ltx: LedgerTransaction, private val transactionClassLoader: ClassLoader, private val inputStatesContractClassNameToMaxVersion: Map<ContractClassName, Version>) {
|
||||
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 contractAttachmentsByContract: Map<ContractClassName, Set<ContractAttachment>> = getContractAttachmentsByContract()
|
||||
@ -207,7 +207,7 @@ class Verifier(val ltx: LedgerTransaction, val transactionClassLoader: ClassLoad
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that contract class versions of output states are not lower that versions of relevant input states.
|
||||
* Verify that contract class versions of output states are greater than or equal to the versions of the input states.
|
||||
*/
|
||||
private fun validateContractVersions() {
|
||||
contractAttachmentsByContract.forEach { contractClassName, attachments ->
|
||||
|
@ -55,6 +55,7 @@ interface ServicesForResolution {
|
||||
*/
|
||||
@Throws(TransactionResolutionException::class)
|
||||
fun loadState(stateRef: StateRef): TransactionState<*>
|
||||
|
||||
/**
|
||||
* Given a [Set] of [StateRef]'s loads the referenced transaction and looks up the specified output [ContractState].
|
||||
*
|
||||
@ -65,8 +66,11 @@ interface ServicesForResolution {
|
||||
@Throws(TransactionResolutionException::class)
|
||||
fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>>
|
||||
|
||||
/**
|
||||
* Returns the [Attachment] that defines the given [StateRef], which must be in the visible subset of the ledger.
|
||||
*/
|
||||
@Throws(TransactionResolutionException::class, AttachmentResolutionException::class)
|
||||
fun loadContractAttachment(stateRef: StateRef, forContractClassName: ContractClassName? = null): Attachment
|
||||
fun loadContractAttachment(stateRef: StateRef): Attachment
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -190,13 +190,18 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
|
||||
val resolvedNetworkParameters = resolveParameters(networkParametersHash) ?: throw TransactionResolutionException(id)
|
||||
|
||||
// Keep resolvedInputs lazy and resolve the inputs separately here to get Version.
|
||||
val inputStateContractClassToStateRefs: Map<ContractClassName, List<StateAndRef<ContractState>>> = serializedResolvedInputs.map {
|
||||
it.toStateAndRef()
|
||||
}.groupBy { it.state.contract }
|
||||
val inputStateContractClassToMaxVersion: Map<ContractClassName, Version> = inputStateContractClassToStateRefs.mapValues {
|
||||
it.value.map { resolveContractAttachment(it.ref).contractVersion }.max() ?: DEFAULT_CORDAPP_VERSION
|
||||
}
|
||||
// For each contract referenced in the inputs, figure out the highest version being used. The outputs must be
|
||||
// at least that version or higher, to prevent adversaries from downgrading the app to an old version that has
|
||||
// known bugs they can then exploit. This is part of the version ratchet that ensures apps can only ever be
|
||||
// upgraded, not downgraded. We don't use resolvedInputs here to keep it lazy. TODO: why?
|
||||
// We do this resolution now instead of in LedgerTransaction because here we have the function to map
|
||||
// StateRefs to their attachments directly.
|
||||
val appVersionsInInputs: Map<ContractClassName, Version> = serializedResolvedInputs
|
||||
.map { it.toStateAndRef() }
|
||||
.groupBy { it.state.contract }
|
||||
.mapValues { (_ , statesAndRefs) ->
|
||||
statesAndRefs.map { resolveContractAttachment(it.ref).contractVersion }.max() ?: DEFAULT_CORDAPP_VERSION
|
||||
}
|
||||
|
||||
val ltx = LedgerTransaction.create(
|
||||
resolvedInputs,
|
||||
@ -212,7 +217,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
componentGroups,
|
||||
serializedResolvedInputs,
|
||||
serializedResolvedReferences,
|
||||
inputStateContractClassToMaxVersion
|
||||
appVersionsInInputs
|
||||
)
|
||||
|
||||
checkTransactionSize(ltx, resolvedNetworkParameters.maxTransactionSize, serializedResolvedInputs, serializedResolvedReferences)
|
||||
|
@ -32,7 +32,6 @@ 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
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.ledger
|
||||
import org.junit.*
|
||||
@ -94,7 +93,7 @@ class ConstraintsPropagationTests {
|
||||
packageOwnership = mapOf("net.corda.finance.contracts.asset" to hashToSignatureConstraintsKey),
|
||||
notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))
|
||||
) {
|
||||
override fun loadContractAttachment(stateRef: StateRef, forContractClassName: ContractClassName?) = servicesForResolution.loadContractAttachment(stateRef)
|
||||
override fun loadContractAttachment(stateRef: StateRef) = servicesForResolution.loadContractAttachment(stateRef)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ class TransactionSerializationTests {
|
||||
|
||||
val megaCorpServices = object : MockServices(listOf("net.corda.core.serialization"), MEGA_CORP.name, mock(), testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))), MEGA_CORP_KEY) {
|
||||
//override mock implementation with a real one
|
||||
override fun loadContractAttachment(stateRef: StateRef, forContractClassName: ContractClassName?): Attachment = servicesForResolution.loadContractAttachment(stateRef, forContractClassName)
|
||||
override fun loadContractAttachment(stateRef: StateRef): Attachment = servicesForResolution.loadContractAttachment(stateRef)
|
||||
}
|
||||
val notaryServices = MockServices(listOf("net.corda.core.serialization"), DUMMY_NOTARY.name, rigorousMock(), DUMMY_NOTARY_KEY)
|
||||
lateinit var tx: TransactionBuilder
|
||||
|
@ -40,29 +40,33 @@ data class ServicesForResolutionImpl(
|
||||
}
|
||||
|
||||
@Throws(TransactionResolutionException::class, AttachmentResolutionException::class)
|
||||
override fun loadContractAttachment(stateRef: StateRef, forContractClassName: ContractClassName?): Attachment {
|
||||
val coreTransaction = validatedTransactions.getTransaction(stateRef.txhash)?.coreTransaction
|
||||
?: throw TransactionResolutionException(stateRef.txhash)
|
||||
when (coreTransaction) {
|
||||
is WireTransaction -> {
|
||||
val transactionState = coreTransaction.outRef<ContractState>(stateRef.index).state
|
||||
for (attachmentId in coreTransaction.attachments) {
|
||||
val attachment = attachments.openAttachment(attachmentId)
|
||||
if (attachment is ContractAttachment && (forContractClassName ?: transactionState.contract) in attachment.allContracts) {
|
||||
return attachment
|
||||
override fun loadContractAttachment(stateRef: StateRef): Attachment {
|
||||
// We may need to recursively chase transactions if there are notary changes.
|
||||
fun inner(stateRef: StateRef, forContractClassName: String?): Attachment {
|
||||
val ctx = validatedTransactions.getTransaction(stateRef.txhash)?.coreTransaction
|
||||
?: throw TransactionResolutionException(stateRef.txhash)
|
||||
when (ctx) {
|
||||
is WireTransaction -> {
|
||||
val transactionState = ctx.outRef<ContractState>(stateRef.index).state
|
||||
for (attachmentId in ctx.attachments) {
|
||||
val attachment = attachments.openAttachment(attachmentId)
|
||||
if (attachment is ContractAttachment && (forContractClassName ?: transactionState.contract) in attachment.allContracts) {
|
||||
return attachment
|
||||
}
|
||||
}
|
||||
throw AttachmentResolutionException(stateRef.txhash)
|
||||
}
|
||||
throw AttachmentResolutionException(stateRef.txhash)
|
||||
is ContractUpgradeWireTransaction -> {
|
||||
return attachments.openAttachment(ctx.upgradedContractAttachmentId) ?: throw AttachmentResolutionException(stateRef.txhash)
|
||||
}
|
||||
is NotaryChangeWireTransaction -> {
|
||||
val transactionState = SerializedStateAndRef(resolveStateRefBinaryComponent(stateRef, this)!!, stateRef).toStateAndRef().state
|
||||
// TODO: check only one (or until one is resolved successfully), max recursive invocations check?
|
||||
return ctx.inputs.map { inner(it, transactionState.contract) }.firstOrNull() ?: throw AttachmentResolutionException(stateRef.txhash)
|
||||
}
|
||||
else -> throw UnsupportedOperationException("Attempting to resolve attachment for index ${stateRef.index} of a ${ctx.javaClass} transaction. This is not supported.")
|
||||
}
|
||||
is ContractUpgradeWireTransaction -> {
|
||||
return attachments.openAttachment(coreTransaction.upgradedContractAttachmentId) ?: throw AttachmentResolutionException(stateRef.txhash)
|
||||
}
|
||||
is NotaryChangeWireTransaction -> {
|
||||
val transactionState = SerializedStateAndRef(resolveStateRefBinaryComponent(stateRef, this)!!, stateRef).toStateAndRef().state
|
||||
//TODO check only one (or until one is resolved successfully), max recursive invocations check?
|
||||
return coreTransaction.inputs.map { loadContractAttachment(it, transactionState.contract) }.firstOrNull() ?: throw AttachmentResolutionException(stateRef.txhash)
|
||||
}
|
||||
else -> throw UnsupportedOperationException("Attempting to resolve attachment ${stateRef.index} of a ${coreTransaction.javaClass} transaction. This is not supported.")
|
||||
}
|
||||
return inner(stateRef, null)
|
||||
}
|
||||
}
|
||||
|
@ -352,7 +352,7 @@ open class MockServices private constructor(
|
||||
override fun loadStates(stateRefs: Set<StateRef>) = servicesForResolution.loadStates(stateRefs)
|
||||
|
||||
/** Returns a dummy Attachment, in context of signature constrains non-downgrade rule this default to contract class version `1`. */
|
||||
override fun loadContractAttachment(stateRef: StateRef, forContractClassName: ContractClassName?) = dummyAttachment
|
||||
override fun loadContractAttachment(stateRef: StateRef) = dummyAttachment
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user