mirror of
https://github.com/corda/corda.git
synced 2025-01-30 08:04:16 +00:00
CORDA-2615 - add fix for serialization failure on relevancy check.
CORDA-2615 - add fix for serialization failure on relevancy check. CORDA-2615 - add fix for serialization failure on relevancy check. CORDA-2615 - add fix for serialization failure on relevancy check. CORDA-2615 - fix test Create reconnecting proxies
This commit is contained in:
parent
c2ad64ccde
commit
b0cf41ef58
@ -28,12 +28,22 @@ import java.util.function.Predicate
|
||||
* - Deserialising the output states.
|
||||
*
|
||||
* All the above refer to inputs using a (txhash, output index) pair.
|
||||
*
|
||||
* Usage notes:
|
||||
*
|
||||
* [LedgerTransaction] is an abstraction that is meant to be used during the transaction verification stage.
|
||||
* It needs full access to input states that might be in transactions that are encrypted and unavailable for code running outside the secure enclave.
|
||||
* Also, it might need to deserialize states with code that might not be available on the classpath.
|
||||
*
|
||||
* Because of this, trying to create or use a [LedgerTransaction] for any other purpose then transaction verification can result in unexpected exceptions,
|
||||
* which need de be handled.
|
||||
*
|
||||
* [LedgerTransaction]s should never be instantiated directly from client code, but rather via WireTransaction.toLedgerTransaction
|
||||
*/
|
||||
@KeepForDJVM
|
||||
@CordaSerializable
|
||||
class LedgerTransaction
|
||||
@ConstructorForDeserialization
|
||||
// LedgerTransaction is not meant to be created directly from client code, but rather via WireTransaction.toLedgerTransaction
|
||||
private constructor(
|
||||
// DOCSTART 1
|
||||
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
|
||||
@ -67,7 +77,6 @@ private constructor(
|
||||
private var serializedReferences: List<SerializedStateAndRef>? = null
|
||||
|
||||
init {
|
||||
checkBaseInvariants()
|
||||
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
|
||||
checkNotaryWhitelisted()
|
||||
}
|
||||
@ -133,6 +142,9 @@ private constructor(
|
||||
// Switch thread local deserialization context to using a cached attachments classloader. This classloader enforces various rules
|
||||
// like no-overlap, package namespace ownership and (in future) deterministic Java.
|
||||
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments + extraAttachments, getParamsWithGoo(), id) { 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.
|
||||
// This artifice is required to preserve backwards compatibility.
|
||||
Verifier(createLtxForVerification(), transactionClassLoader)
|
||||
}
|
||||
}
|
||||
@ -163,12 +175,17 @@ private constructor(
|
||||
return FlowLogic.currentTopLevel?.serviceHub?.networkParameters
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the [LedgerTransaction] instance that will be used by contract verification.
|
||||
*
|
||||
* This method needs to run in the special transaction attachments classloader context.
|
||||
*/
|
||||
private fun createLtxForVerification(): LedgerTransaction {
|
||||
val serializedInputs = this.serializedInputs
|
||||
val serializedReferences = this.serializedReferences
|
||||
val componentGroups = this.componentGroups
|
||||
|
||||
return if (serializedInputs != null && serializedReferences != null && componentGroups != null) {
|
||||
val transaction= if (serializedInputs != null && serializedReferences != null && componentGroups != null) {
|
||||
// Deserialize all relevant classes in the transaction classloader.
|
||||
val deserializedInputs = serializedInputs.map { it.toStateAndRef() }
|
||||
val deserializedReferences = serializedReferences.map { it.toStateAndRef() }
|
||||
@ -198,6 +215,12 @@ private constructor(
|
||||
"The result of the verify method might not be accurate.")
|
||||
this
|
||||
}
|
||||
|
||||
// This check accesses input states and must be run in this context.
|
||||
// It must run on the instance that is verified, not on the outer LedgerTransaction shell.
|
||||
transaction.checkBaseInvariants()
|
||||
|
||||
return transaction
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -224,14 +224,35 @@ class NodeVaultService(
|
||||
}
|
||||
|
||||
private fun makeUpdates(batch: Iterable<CoreTransaction>, statesToRecord: StatesToRecord): List<Vault.Update<ContractState>> {
|
||||
|
||||
fun <T> withValidDeserialization(list: List<T>, txId: SecureHash): Map<Int, T> = (0 until list.size).mapNotNull { idx ->
|
||||
try {
|
||||
idx to list[idx]
|
||||
} catch (e: TransactionDeserialisationException) {
|
||||
// When resolving transaction dependencies we might encounter contracts we haven't installed locally.
|
||||
// This will cause a failure as we can't deserialize such states in the context of the `appClassloader`.
|
||||
// For now we ignore these states.
|
||||
// In the future we will use the AttachmentsClassloader to correctly deserialize and asses the relevancy.
|
||||
log.debug { "Could not deserialize state $idx from transaction $txId. Cause: $e" }
|
||||
null
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
// Returns only output states that can be deserialised successfully.
|
||||
fun WireTransaction.deserializableOutputStates(): Map<Int, TransactionState<ContractState>> = withValidDeserialization(this.outputs, this.id)
|
||||
|
||||
// Returns only reference states that can be deserialised successfully.
|
||||
fun LedgerTransaction.deserializableRefStates(): Map<Int, StateAndRef<ContractState>> = withValidDeserialization(this.references, this.id)
|
||||
|
||||
fun makeUpdate(tx: WireTransaction): Vault.Update<ContractState>? {
|
||||
val outputs: Map<Int, TransactionState<ContractState>> = tx.deserializableOutputStates()
|
||||
val ourNewStates = when (statesToRecord) {
|
||||
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
||||
StatesToRecord.ONLY_RELEVANT -> tx.outputs.withIndex().filter {
|
||||
isRelevant(it.value.data, keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } }).toSet())
|
||||
StatesToRecord.ONLY_RELEVANT -> outputs.filter { (_, value) ->
|
||||
isRelevant(value.data, keyManagementService.filterMyKeys(outputs.values.flatMap { it.data.participants.map { it.owningKey } }).toSet())
|
||||
}
|
||||
StatesToRecord.ALL_VISIBLE -> tx.outputs.withIndex()
|
||||
}.map { tx.outRef<ContractState>(it.index) }
|
||||
StatesToRecord.ALL_VISIBLE -> outputs
|
||||
}.map { (idx, _) -> tx.outRef<ContractState>(idx) }
|
||||
|
||||
// Retrieve all unconsumed states for this transaction's inputs.
|
||||
val consumedStates = loadStates(tx.inputs)
|
||||
@ -250,12 +271,14 @@ class NodeVaultService(
|
||||
val newReferenceStateAndRefs = if (tx.references.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
when (statesToRecord) {
|
||||
when (statesToRecord) {
|
||||
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
||||
StatesToRecord.ALL_VISIBLE, StatesToRecord.ONLY_RELEVANT -> {
|
||||
val notSeenReferences = tx.references - loadStates(tx.references).map { it.ref }
|
||||
// TODO: This is expensive - is there another way?
|
||||
tx.toLedgerTransaction(servicesForResolution).references.filter { it.ref in notSeenReferences }
|
||||
tx.toLedgerTransaction(servicesForResolution).deserializableRefStates()
|
||||
.filter { (_, stateAndRef) -> stateAndRef.ref in notSeenReferences }
|
||||
.values
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user