mirror of
https://github.com/corda/corda.git
synced 2025-02-06 11:09:18 +00:00
CORDA-2694: Prevent Node Explorer from crashing should it receive unknown transaction objects. (#4842)
* CORDA-2694: Prevent Node Explorer from crashing should it receive unknown transaction objects. Also ensure that LazyMappedList can only handle TransactionDeserialisationExceptions. * CORDA-2694: Add unit tests for eager LazyMappedList behaviour. * CORDA-2694: Hide LazyMappedList from the client:jfx module. * CORDA-2694: Create an unknown transaction state that has the correct notary.
This commit is contained in:
parent
cfccfd075e
commit
fae74eecde
@ -119,19 +119,19 @@ class NodeMonitorModel : AutoCloseable {
|
|||||||
}.toSet()
|
}.toSet()
|
||||||
val consumedStates = statesSnapshot.states.toSet() - unconsumedStates
|
val consumedStates = statesSnapshot.states.toSet() - unconsumedStates
|
||||||
val initialVaultUpdate = Vault.Update(consumedStates, unconsumedStates, references = emptySet())
|
val initialVaultUpdate = Vault.Update(consumedStates, unconsumedStates, references = emptySet())
|
||||||
vaultUpdates.startWith(initialVaultUpdate).subscribe({ vaultUpdatesSubject.onNext(it) }, {})
|
vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject::onNext, {})
|
||||||
|
|
||||||
// Transactions
|
// Transactions
|
||||||
val (transactions, newTransactions) = proxy.internalVerifiedTransactionsFeed()
|
val (transactions, newTransactions) = proxy.internalVerifiedTransactionsFeed()
|
||||||
newTransactions.startWith(transactions).subscribe({ transactionsSubject.onNext(it) }, {})
|
newTransactions.startWith(transactions).subscribe(transactionsSubject::onNext, {})
|
||||||
|
|
||||||
// SM -> TX mapping
|
// SM -> TX mapping
|
||||||
val (smTxMappings, futureSmTxMappings) = proxy.stateMachineRecordedTransactionMappingFeed()
|
val (smTxMappings, futureSmTxMappings) = proxy.stateMachineRecordedTransactionMappingFeed()
|
||||||
futureSmTxMappings.startWith(smTxMappings).subscribe({ stateMachineTransactionMappingSubject.onNext(it) }, {})
|
futureSmTxMappings.startWith(smTxMappings).subscribe(stateMachineTransactionMappingSubject::onNext, {})
|
||||||
|
|
||||||
// Parties on network
|
// Parties on network
|
||||||
val (parties, futurePartyUpdate) = proxy.networkMapFeed()
|
val (parties, futurePartyUpdate) = proxy.networkMapFeed()
|
||||||
futurePartyUpdate.startWith(parties.map { MapChange.Added(it) }).subscribe({ networkMapSubject.onNext(it) }, {})
|
futurePartyUpdate.startWith(parties.map(MapChange::Added)).subscribe(networkMapSubject::onNext, {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,11 +5,24 @@ import net.corda.client.jfx.utils.distinctBy
|
|||||||
import net.corda.client.jfx.utils.lift
|
import net.corda.client.jfx.utils.lift
|
||||||
import net.corda.client.jfx.utils.map
|
import net.corda.client.jfx.utils.map
|
||||||
import net.corda.client.jfx.utils.recordInSequence
|
import net.corda.client.jfx.utils.recordInSequence
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.internal.eagerDeserialise
|
||||||
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
|
import java.math.BigInteger.ZERO
|
||||||
|
|
||||||
|
private class Unknown : Contract {
|
||||||
|
override fun verify(tx: LedgerTransaction) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
object State : ContractState {
|
||||||
|
override val participants: List<AbstractParty> = emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is
|
* [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is
|
||||||
@ -41,10 +54,24 @@ data class PartiallyResolvedTransaction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val DUMMY_NOTARY = Party(CordaX500Name("Dummy Notary", "Nowhere", "ZZ"), entropyToKeyPair(ZERO).public)
|
||||||
|
|
||||||
fun fromSignedTransaction(
|
fun fromSignedTransaction(
|
||||||
transaction: SignedTransaction,
|
transaction: SignedTransaction,
|
||||||
inputTransactions: Map<StateRef, SignedTransaction?>
|
inputTransactions: Map<StateRef, SignedTransaction?>
|
||||||
): PartiallyResolvedTransaction {
|
): PartiallyResolvedTransaction {
|
||||||
|
/**
|
||||||
|
* Forcibly deserialize our transaction outputs up-front.
|
||||||
|
* Replace any [TransactionState] objects that fail to
|
||||||
|
* deserialize with a dummy transaction state that uses
|
||||||
|
* the transaction's notary.
|
||||||
|
*/
|
||||||
|
val unknownTransactionState = TransactionState(
|
||||||
|
data = Unknown.State,
|
||||||
|
contract = Unknown::class.java.name,
|
||||||
|
notary = transaction.notary ?: DUMMY_NOTARY
|
||||||
|
)
|
||||||
|
transaction.coreTransaction.outputs.eagerDeserialise { _, _ -> unknownTransactionState }
|
||||||
return PartiallyResolvedTransaction(
|
return PartiallyResolvedTransaction(
|
||||||
transaction = transaction,
|
transaction = transaction,
|
||||||
inputs = transaction.inputs.map { stateRef ->
|
inputs = transaction.inputs.map { stateRef ->
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.isFulfilledBy
|
|
||||||
import net.corda.core.crypto.keys
|
import net.corda.core.crypto.keys
|
||||||
import net.corda.core.internal.cordapp.CordappImpl
|
import net.corda.core.internal.cordapp.CordappImpl
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
|
@ -524,6 +524,7 @@ fun <E> MutableSet<E>.toSynchronised(): MutableSet<E> = Collections.synchronized
|
|||||||
/**
|
/**
|
||||||
* List implementation that applies the expensive [transform] function only when the element is accessed and caches calculated values.
|
* List implementation that applies the expensive [transform] function only when the element is accessed and caches calculated values.
|
||||||
* Size is very cheap as it doesn't call [transform].
|
* Size is very cheap as it doesn't call [transform].
|
||||||
|
* Used internally by [net.corda.core.transactions.TraversableTransaction].
|
||||||
*/
|
*/
|
||||||
class LazyMappedList<T, U>(val originalList: List<T>, val transform: (T, Int) -> U) : AbstractList<U>() {
|
class LazyMappedList<T, U>(val originalList: List<T>, val transform: (T, Int) -> U) : AbstractList<U>() {
|
||||||
private val partialResolvedList = MutableList<U?>(originalList.size) { null }
|
private val partialResolvedList = MutableList<U?>(originalList.size) { null }
|
||||||
@ -532,6 +533,15 @@ class LazyMappedList<T, U>(val originalList: List<T>, val transform: (T, Int) ->
|
|||||||
return partialResolvedList[index]
|
return partialResolvedList[index]
|
||||||
?: transform(originalList[index], index).also { computed -> partialResolvedList[index] = computed }
|
?: transform(originalList[index], index).also { computed -> partialResolvedList[index] = computed }
|
||||||
}
|
}
|
||||||
|
internal fun eager(onError: (TransactionDeserialisationException, Int) -> U?) {
|
||||||
|
for (i in 0 until size) {
|
||||||
|
try {
|
||||||
|
get(i)
|
||||||
|
} catch (ex: TransactionDeserialisationException) {
|
||||||
|
partialResolvedList[i] = onError(ex, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -540,6 +550,17 @@ class LazyMappedList<T, U>(val originalList: List<T>, val transform: (T, Int) ->
|
|||||||
*/
|
*/
|
||||||
fun <T, U> List<T>.lazyMapped(transform: (T, Int) -> U): List<U> = LazyMappedList(this, transform)
|
fun <T, U> List<T>.lazyMapped(transform: (T, Int) -> U): List<U> = LazyMappedList(this, transform)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over a [LazyMappedList], forcing it to transform all of its elements immediately.
|
||||||
|
* This transformation is assumed to be "deserialisation". Does nothing for any other kind of [List].
|
||||||
|
* WARNING: Any changes made to the [LazyMappedList] contents are PERMANENT!
|
||||||
|
*/
|
||||||
|
fun <T> List<T>.eagerDeserialise(onError: (TransactionDeserialisationException, Int) -> T? = { ex, _ -> throw ex }) {
|
||||||
|
if (this is LazyMappedList<*, T>) {
|
||||||
|
eager(onError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private const val MAX_SIZE = 100
|
private const val MAX_SIZE = 100
|
||||||
private val warnings = Collections.newSetFromMap(createSimpleCache<String, Boolean>(MAX_SIZE)).toSynchronised()
|
private val warnings = Collections.newSetFromMap(createSimpleCache<String, Boolean>(MAX_SIZE)).toSynchronised()
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP
|
|||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
|
|
||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
|
@ -4,15 +4,12 @@ package net.corda.core.utilities
|
|||||||
|
|
||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.internal.LazyMappedList
|
|
||||||
import net.corda.core.internal.concurrent.get
|
import net.corda.core.internal.concurrent.get
|
||||||
import net.corda.core.internal.createSimpleCache
|
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
package net.corda.core.utilities
|
package net.corda.core.utilities
|
||||||
|
|
||||||
|
import net.corda.core.contracts.ComponentGroupEnum.*
|
||||||
import net.corda.core.internal.lazyMapped
|
import net.corda.core.internal.lazyMapped
|
||||||
|
import net.corda.core.internal.TransactionDeserialisationException
|
||||||
|
import net.corda.core.internal.eagerDeserialise
|
||||||
|
import net.corda.core.serialization.MissingAttachmentsException
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.rules.ExpectedException
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class LazyMappedListTest {
|
class LazyMappedListTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val exception: ExpectedException = ExpectedException.none()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `LazyMappedList works`() {
|
fun `LazyMappedList works`() {
|
||||||
val originalList = (1 until 10).toList()
|
val originalList = (1 until 10).toList()
|
||||||
@ -33,4 +42,29 @@ class LazyMappedListTest {
|
|||||||
assertEquals(1, callCounter)
|
assertEquals(1, callCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMissingAttachments() {
|
||||||
|
exception.expect(MissingAttachmentsException::class.java)
|
||||||
|
exception.expectMessage("Uncatchable!")
|
||||||
|
|
||||||
|
val lazyList = (0 until 5).toList().lazyMapped<Int, Int> { _, _ ->
|
||||||
|
throw MissingAttachmentsException(emptyList(), "Uncatchable!")
|
||||||
|
}
|
||||||
|
|
||||||
|
lazyList.eagerDeserialise { _, _ -> -999 }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialisationExceptions() {
|
||||||
|
val lazyList = (0 until 5).toList().lazyMapped<Int, Int> { _, index ->
|
||||||
|
throw TransactionDeserialisationException(
|
||||||
|
OUTPUTS_GROUP, index, IllegalStateException("Catch this!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
lazyList.eagerDeserialise { _, _ -> -999 }
|
||||||
|
assertEquals(5, lazyList.size)
|
||||||
|
lazyList.forEachIndexed { idx, item ->
|
||||||
|
assertEquals(-999, item, "Item[$idx] mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user