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:
Tudor Malene 2019-02-17 12:17:45 +00:00 committed by Mike Hearn
parent c2ad64ccde
commit b0cf41ef58
2 changed files with 55 additions and 9 deletions

View File

@ -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
}
/**

View File

@ -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
}
}
}