mirror of
https://github.com/corda/corda.git
synced 2025-01-30 16:14:39 +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.
|
* - Deserialising the output states.
|
||||||
*
|
*
|
||||||
* All the above refer to inputs using a (txhash, output index) pair.
|
* 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
|
@KeepForDJVM
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
class LedgerTransaction
|
class LedgerTransaction
|
||||||
@ConstructorForDeserialization
|
@ConstructorForDeserialization
|
||||||
// LedgerTransaction is not meant to be created directly from client code, but rather via WireTransaction.toLedgerTransaction
|
|
||||||
private constructor(
|
private constructor(
|
||||||
// DOCSTART 1
|
// DOCSTART 1
|
||||||
/** 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. */
|
||||||
@ -67,7 +77,6 @@ private constructor(
|
|||||||
private var serializedReferences: List<SerializedStateAndRef>? = null
|
private var serializedReferences: List<SerializedStateAndRef>? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
checkBaseInvariants()
|
|
||||||
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" }
|
||||||
checkNotaryWhitelisted()
|
checkNotaryWhitelisted()
|
||||||
}
|
}
|
||||||
@ -133,6 +142,9 @@ private constructor(
|
|||||||
// Switch thread local deserialization context to using a cached attachments classloader. This classloader enforces various rules
|
// 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.
|
// like no-overlap, package namespace ownership and (in future) deterministic Java.
|
||||||
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments + extraAttachments, getParamsWithGoo(), id) { transactionClassLoader ->
|
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)
|
Verifier(createLtxForVerification(), transactionClassLoader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,12 +175,17 @@ private constructor(
|
|||||||
return FlowLogic.currentTopLevel?.serviceHub?.networkParameters
|
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 {
|
private fun createLtxForVerification(): LedgerTransaction {
|
||||||
val serializedInputs = this.serializedInputs
|
val serializedInputs = this.serializedInputs
|
||||||
val serializedReferences = this.serializedReferences
|
val serializedReferences = this.serializedReferences
|
||||||
val componentGroups = this.componentGroups
|
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.
|
// Deserialize all relevant classes in the transaction classloader.
|
||||||
val deserializedInputs = serializedInputs.map { it.toStateAndRef() }
|
val deserializedInputs = serializedInputs.map { it.toStateAndRef() }
|
||||||
val deserializedReferences = serializedReferences.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.")
|
"The result of the verify method might not be accurate.")
|
||||||
this
|
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>> {
|
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>? {
|
fun makeUpdate(tx: WireTransaction): Vault.Update<ContractState>? {
|
||||||
|
val outputs: Map<Int, TransactionState<ContractState>> = tx.deserializableOutputStates()
|
||||||
val ourNewStates = when (statesToRecord) {
|
val ourNewStates = when (statesToRecord) {
|
||||||
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
||||||
StatesToRecord.ONLY_RELEVANT -> tx.outputs.withIndex().filter {
|
StatesToRecord.ONLY_RELEVANT -> outputs.filter { (_, value) ->
|
||||||
isRelevant(it.value.data, keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } }).toSet())
|
isRelevant(value.data, keyManagementService.filterMyKeys(outputs.values.flatMap { it.data.participants.map { it.owningKey } }).toSet())
|
||||||
}
|
}
|
||||||
StatesToRecord.ALL_VISIBLE -> tx.outputs.withIndex()
|
StatesToRecord.ALL_VISIBLE -> outputs
|
||||||
}.map { tx.outRef<ContractState>(it.index) }
|
}.map { (idx, _) -> tx.outRef<ContractState>(idx) }
|
||||||
|
|
||||||
// Retrieve all unconsumed states for this transaction's inputs.
|
// Retrieve all unconsumed states for this transaction's inputs.
|
||||||
val consumedStates = loadStates(tx.inputs)
|
val consumedStates = loadStates(tx.inputs)
|
||||||
@ -250,12 +271,14 @@ class NodeVaultService(
|
|||||||
val newReferenceStateAndRefs = if (tx.references.isEmpty()) {
|
val newReferenceStateAndRefs = if (tx.references.isEmpty()) {
|
||||||
emptyList()
|
emptyList()
|
||||||
} else {
|
} else {
|
||||||
when (statesToRecord) {
|
when (statesToRecord) {
|
||||||
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
||||||
StatesToRecord.ALL_VISIBLE, StatesToRecord.ONLY_RELEVANT -> {
|
StatesToRecord.ALL_VISIBLE, StatesToRecord.ONLY_RELEVANT -> {
|
||||||
val notSeenReferences = tx.references - loadStates(tx.references).map { it.ref }
|
val notSeenReferences = tx.references - loadStates(tx.references).map { it.ref }
|
||||||
// TODO: This is expensive - is there another way?
|
// 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