diff --git a/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt b/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt index b454556bf3..bffff0abf1 100644 --- a/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt +++ b/confidential-identities/src/main/kotlin/net/corda/confidential/IdentitySyncFlow.kt @@ -53,7 +53,7 @@ object IdentitySyncFlow { } private fun extractOurConfidentialIdentities(): Map { - val states: List = (tx.inputs.map { serviceHub.loadState(it) }.requireNoNulls().map { it.data } + tx.outputs.map { it.data }) + val states: List = (serviceHub.loadStates(tx.inputs.toSet()).map { it.state.data } + tx.outputs.map { it.data }) val identities: Set = states.flatMap(ContractState::participants).toSet() // Filter participants down to the set of those not in the network map (are not well known) val confidentialIdentities = identities diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index 9c2cbee976..78dd436eb6 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -63,7 +63,7 @@ class NotaryFlow { protected fun checkTransaction(): Party { val notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary") check(serviceHub.networkMapCache.isNotary(notaryParty)) { "$notaryParty is not a notary on the network" } - check(stx.inputs.all { stateRef -> serviceHub.loadState(stateRef).notary == notaryParty }) { + check(serviceHub.loadStates(stx.inputs.toSet()).all { it.state.notary == notaryParty }) { "Input states must have the same Notary" } diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 0d2f36ff43..80e1065c99 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -26,6 +26,10 @@ interface StateLoader { /** * Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]. * + * *WARNING* Do not use this method unless you really only want a single state - any batch loading should + * go through [loadStates] as repeatedly calling [loadState] can lead to repeat deserialsiation work and + * severe performance degradation. + * * @throws TransactionResolutionException if [stateRef] points to a non-existent transaction. */ @Throws(TransactionResolutionException::class) @@ -39,9 +43,7 @@ interface StateLoader { // TODO: future implementation to use a Vault state ref -> contract state BLOB table and perform single query bulk load // as the existing transaction store will become encrypted at some point @Throws(TransactionResolutionException::class) - fun loadStates(stateRefs: Set): Set> { - return stateRefs.map { StateAndRef(loadState(it), it) }.toSet() - } + fun loadStates(stateRefs: Set): Set> } /** diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt index 25053adb67..07240e9f30 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt @@ -1,9 +1,7 @@ package net.corda.core.node.services import net.corda.core.DoNotImplement -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TransactionResolutionException -import net.corda.core.contracts.TransactionState +import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.messaging.DataFeed import net.corda.core.node.StateLoader @@ -26,6 +24,15 @@ interface TransactionStorage : StateLoader { return stx.resolveBaseTransaction(this).outputs[stateRef.index] } + @Throws(TransactionResolutionException::class) + override fun loadStates(stateRefs: Set): Set> { + return stateRefs.groupBy { it.txhash }.flatMap { + val stx = getTransaction(it.key) ?: throw TransactionResolutionException(it.key) + val baseTx = stx.resolveBaseTransaction(this) + it.value.map { StateAndRef(baseTx.outputs[it.index], it) } + }.toSet() + } + /** * Get a synchronous Observable of updates. When observations are pushed to the Observer, the vault will already * incorporate the update. diff --git a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt index 332718fd94..f0a6c7cc18 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt @@ -42,9 +42,7 @@ data class NotaryChangeWireTransaction( fun resolve(services: ServiceHub, sigs: List) = resolve(services as StateLoader, sigs) fun resolve(stateLoader: StateLoader, sigs: List): NotaryChangeLedgerTransaction { - val resolvedInputs = inputs.map { ref -> - stateLoader.loadState(ref).let { StateAndRef(it, ref) } - } + val resolvedInputs = stateLoader.loadStates(inputs.toSet()).toList() return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs) } } diff --git a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt index 9d18c2d3a5..28e98035c7 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt @@ -97,7 +97,7 @@ object TwoPartyTradeFlow { val signTransactionFlow = object : SignTransactionFlow(otherSideSession, VERIFYING_AND_SIGNING.childProgressTracker()) { override fun checkTransaction(stx: SignedTransaction) { // Verify that we know who all the participants in the transaction are - val states: Iterable = stx.tx.inputs.map { serviceHub.loadState(it).data } + stx.tx.outputs.map { it.data } + val states: Iterable = serviceHub.loadStates(stx.tx.inputs.toSet()).map { it.state.data } + stx.tx.outputs.map { it.data } states.forEach { state -> state.participants.forEach { anon -> require(serviceHub.identityService.wellKnownPartyFromAnonymous(anon) != null) { diff --git a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt index 4a911cb0d3..166056f0bf 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/AttachmentLoadingTests.kt @@ -2,10 +2,7 @@ package net.corda.node.services import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever -import net.corda.core.contracts.Contract -import net.corda.core.contracts.PartyAndReference -import net.corda.core.contracts.StateRef -import net.corda.core.contracts.TransactionState +import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider import net.corda.core.flows.FlowLogic import net.corda.core.flows.UnexpectedFlowEndException @@ -87,6 +84,7 @@ class AttachmentLoadingTests { private val services = object : ServicesForResolution { override fun loadState(stateRef: StateRef): TransactionState<*> = throw NotImplementedError() + override fun loadStates(stateRefs: Set): Set> = throw NotImplementedError() override val identityService = rigorousMock().apply { doReturn(null).whenever(this).partyFromKey(DUMMY_BANK_A.owningKey) } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt index bf2745f8e3..24ba8921c8 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt @@ -77,6 +77,9 @@ data class TestTransactionDSLInterpreter private constructor( val services = object : ServicesForResolution by ledgerInterpreter.services { override fun loadState(stateRef: StateRef) = ledgerInterpreter.resolveStateRef(stateRef) + override fun loadStates(stateRefs: Set): Set> { + return stateRefs.map { StateAndRef(loadState(it), it) }.toSet() + } override val cordappProvider: CordappProvider = ledgerInterpreter.services.cordappProvider } diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt index bd33ea9efc..68c85a65bf 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/GeneratedLedger.kt @@ -50,6 +50,13 @@ data class GeneratedLedger( override fun loadState(stateRef: StateRef): TransactionState<*> { return hashTransactionMap[stateRef.txhash]?.outputs?.get(stateRef.index) ?: throw TransactionResolutionException(stateRef.txhash) } + + override fun loadStates(stateRefs: Set): Set> { + return stateRefs.groupBy { it.txhash }.flatMap { + val outputs = hashTransactionMap[it.key]?.outputs ?: throw TransactionResolutionException(it.key) + it.value.map { StateAndRef(outputs[it.index], it) } + }.toSet() + } override val identityService = rigorousMock().apply { doAnswer { identityMap[it.arguments[0]] }.whenever(this).partyFromKey(any()) }