diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt index 95caf1bb17..389ebc6194 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt @@ -119,19 +119,19 @@ class NodeMonitorModel : AutoCloseable { }.toSet() val consumedStates = statesSnapshot.states.toSet() - unconsumedStates val initialVaultUpdate = Vault.Update(consumedStates, unconsumedStates, references = emptySet()) - vaultUpdates.startWith(initialVaultUpdate).subscribe({ vaultUpdatesSubject.onNext(it) }, {}) + vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject::onNext, {}) // Transactions val (transactions, newTransactions) = proxy.internalVerifiedTransactionsFeed() - newTransactions.startWith(transactions).subscribe({ transactionsSubject.onNext(it) }, {}) + newTransactions.startWith(transactions).subscribe(transactionsSubject::onNext, {}) // SM -> TX mapping val (smTxMappings, futureSmTxMappings) = proxy.stateMachineRecordedTransactionMappingFeed() - futureSmTxMappings.startWith(smTxMappings).subscribe({ stateMachineTransactionMappingSubject.onNext(it) }, {}) + futureSmTxMappings.startWith(smTxMappings).subscribe(stateMachineTransactionMappingSubject::onNext, {}) // Parties on network 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, {}) } } diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt index 7d208748c8..7c16ab4425 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt @@ -5,11 +5,24 @@ import net.corda.client.jfx.utils.distinctBy import net.corda.client.jfx.utils.lift import net.corda.client.jfx.utils.map import net.corda.client.jfx.utils.recordInSequence -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.StateAndRef -import net.corda.core.contracts.StateRef +import net.corda.core.contracts.* +import net.corda.core.crypto.entropyToKeyPair +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.WireTransaction +import java.math.BigInteger.ZERO + +private class Unknown : Contract { + override fun verify(tx: LedgerTransaction) = throw UnsupportedOperationException() + + object State : ContractState { + override val participants: List = emptyList() + } +} /** * [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is @@ -41,10 +54,24 @@ data class PartiallyResolvedTransaction( } companion object { + private val DUMMY_NOTARY = Party(CordaX500Name("Dummy Notary", "Nowhere", "ZZ"), entropyToKeyPair(ZERO).public) + fun fromSignedTransaction( transaction: SignedTransaction, inputTransactions: Map ): 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( transaction = transaction, inputs = transaction.inputs.map { stateRef -> diff --git a/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt b/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt index 319771a7cb..0553184a27 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ConstraintsUtils.kt @@ -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 diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index a2ceee9210..65184d6547 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -524,6 +524,7 @@ fun MutableSet.toSynchronised(): MutableSet = 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(val originalList: List, val transform: (T, Int) -> U) : AbstractList() { private val partialResolvedList = MutableList(originalList.size) { null } @@ -532,6 +533,15 @@ class LazyMappedList(val originalList: List, 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(val originalList: List, val transform: (T, Int) -> */ fun List.lazyMapped(transform: (T, Int) -> U): List = 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 List.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(MAX_SIZE)).toSynchronised() diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index b13f8ec6b1..f27444c498 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -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 diff --git a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt index c21bd2d271..60e55b0745 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt @@ -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 diff --git a/core/src/test/kotlin/net/corda/core/utilities/LazyMappedListTest.kt b/core/src/test/kotlin/net/corda/core/utilities/LazyMappedListTest.kt index 1cd47f542d..c1dc7cf1cb 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/LazyMappedListTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/LazyMappedListTest.kt @@ -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 { _, _ -> + throw MissingAttachmentsException(emptyList(), "Uncatchable!") + } + + lazyList.eagerDeserialise { _, _ -> -999 } + } + + @Test + fun testDeserialisationExceptions() { + val lazyList = (0 until 5).toList().lazyMapped { _, 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") + } + } }