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:
Chris Rankin
2019-03-05 08:49:23 +00:00
committed by Tommy Lillehagen
parent cfccfd075e
commit fae74eecde
7 changed files with 89 additions and 12 deletions

View File

@ -1,7 +1,6 @@
package net.corda.core.internal
import net.corda.core.contracts.*
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.crypto.keys
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.utilities.loggerFor

View File

@ -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.
* 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>() {
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]
?: 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)
/**
* 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 val warnings = Collections.newSetFromMap(createSimpleCache<String, Boolean>(MAX_SIZE)).toSynchronised()

View File

@ -9,7 +9,6 @@ import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP
import net.corda.core.crypto.*
import net.corda.core.identity.Party
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.ServiceHub
import net.corda.core.node.ServicesForResolution

View File

@ -4,15 +4,12 @@ package net.corda.core.utilities
import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.internal.LazyMappedList
import net.corda.core.internal.concurrent.get
import net.corda.core.internal.createSimpleCache
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.CordaSerializable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.time.Duration
import java.util.*
import java.util.concurrent.ExecutionException
import java.util.concurrent.Future
import kotlin.reflect.KProperty

View File

@ -1,11 +1,20 @@
package net.corda.core.utilities
import net.corda.core.contracts.ComponentGroupEnum.*
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.rules.ExpectedException
import kotlin.test.assertEquals
class LazyMappedListTest {
@get:Rule
val exception: ExpectedException = ExpectedException.none()
@Test
fun `LazyMappedList works`() {
val originalList = (1 until 10).toList()
@ -33,4 +42,29 @@ class LazyMappedListTest {
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")
}
}
}