mirror of
https://github.com/corda/corda.git
synced 2024-12-20 21:43:14 +00:00
CORDA-499: Restructure TransactionGraphSearch to be Dokka-friendly (#1409)
* Remove internal state of TransactionGraphSearch from being publicly visible. * Add Dokka comments for TransactionGraphSearch.Query values. * Move query into TransactionGraphSearch constructor as it should always be set except for test cases. * Move TransactionGraphSearch into trader demo
This commit is contained in:
parent
0edaea81d2
commit
4e18937326
@ -1,61 +0,0 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.services.TransactionStorage
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import java.util.*
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
/**
|
||||
* Given a map of transaction id to [SignedTransaction], performs a breadth first search of the dependency graph from
|
||||
* the starting point down in order to find transactions that match the given query criteria.
|
||||
*
|
||||
* Currently, only one kind of query is supported: find any transaction that contains a command of the given type.
|
||||
*
|
||||
* In future, this should support restricting the search by time, and other types of useful query.
|
||||
*
|
||||
* @param transactions map of transaction id to [SignedTransaction].
|
||||
* @param startPoints transactions to use as starting points for the search.
|
||||
*/
|
||||
class TransactionGraphSearch(val transactions: TransactionStorage,
|
||||
val startPoints: List<WireTransaction>) : Callable<List<WireTransaction>> {
|
||||
class Query(
|
||||
val withCommandOfType: Class<out CommandData>? = null,
|
||||
val followInputsOfType: Class<out ContractState>? = null
|
||||
)
|
||||
|
||||
var query: Query = Query()
|
||||
|
||||
override fun call(): List<WireTransaction> {
|
||||
val q = query
|
||||
|
||||
val alreadyVisited = HashSet<SecureHash>()
|
||||
val next = ArrayList<WireTransaction>(startPoints)
|
||||
|
||||
val results = ArrayList<WireTransaction>()
|
||||
|
||||
while (next.isNotEmpty()) {
|
||||
val tx = next.removeAt(next.lastIndex)
|
||||
|
||||
if (q.matches(tx))
|
||||
results += tx
|
||||
|
||||
val inputsLeadingToUnvisitedTx: Iterable<StateRef> = tx.inputs.filter { it.txhash !in alreadyVisited }
|
||||
val unvisitedInputTxs: Map<SecureHash, SignedTransaction> = inputsLeadingToUnvisitedTx.map { it.txhash }.toHashSet().map { transactions.getTransaction(it) }.filterNotNull().associateBy { it.id }
|
||||
val unvisitedInputTxsWithInputIndex: Iterable<Pair<SignedTransaction, Int>> = inputsLeadingToUnvisitedTx.filter { it.txhash in unvisitedInputTxs.keys }.map { Pair(unvisitedInputTxs[it.txhash]!!, it.index) }
|
||||
next += (unvisitedInputTxsWithInputIndex.filter { q.followInputsOfType == null || it.first.tx.outputs[it.second].data.javaClass == q.followInputsOfType }
|
||||
.map { it.first }.filter { alreadyVisited.add(it.id) }.map { it.tx })
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
private fun Query.matches(tx: WireTransaction): Boolean {
|
||||
if (withCommandOfType != null) {
|
||||
if (tx.commands.any { it.value.javaClass.isAssignableFrom(withCommandOfType) })
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package net.corda.traderdemo
|
||||
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.services.TransactionStorage
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import java.util.*
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
/**
|
||||
* Given a map of transaction id to [SignedTransaction], performs a breadth first search of the dependency graph from
|
||||
* the starting point down in order to find transactions that match the given query criteria.
|
||||
*
|
||||
* Currently, only one kind of query is supported: find any transaction that contains a command of the given type.
|
||||
*
|
||||
* In future, this should support restricting the search by time, and other types of useful query.
|
||||
*
|
||||
* @property transactions map of transaction id to [SignedTransaction].
|
||||
* @property startPoints transactions to use as starting points for the search.
|
||||
* @property query query to test transactions within the graph for matching.
|
||||
*/
|
||||
class TransactionGraphSearch(private val transactions: TransactionStorage,
|
||||
private val startPoints: List<WireTransaction>,
|
||||
private val query: Query) : Callable<List<WireTransaction>> {
|
||||
/**
|
||||
* Query criteria to match transactions against.
|
||||
*
|
||||
* @property withCommandOfType contract command class to restrict matches to, or null for no filtering by command. Matches the class or
|
||||
* any subclass.
|
||||
* @property followInputsOfType contract output state class to follow the corresponding inputs to. Matches this exact class only.
|
||||
*/
|
||||
data class Query(
|
||||
val withCommandOfType: Class<out CommandData>? = null,
|
||||
val followInputsOfType: Class<out ContractState>? = null
|
||||
) {
|
||||
/**
|
||||
* Test if the given transaction matches this query. Currently only supports checking if the transaction that
|
||||
* contains a command of the given type.
|
||||
*/
|
||||
fun matches(tx: WireTransaction): Boolean {
|
||||
if (withCommandOfType != null) {
|
||||
if (tx.commands.any { it.value.javaClass.isAssignableFrom(withCommandOfType) })
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun call(): List<WireTransaction> {
|
||||
val alreadyVisited = HashSet<SecureHash>()
|
||||
val next = ArrayList<WireTransaction>(startPoints)
|
||||
|
||||
val results = ArrayList<WireTransaction>()
|
||||
|
||||
while (next.isNotEmpty()) {
|
||||
val tx = next.removeAt(next.lastIndex)
|
||||
|
||||
if (query.matches(tx))
|
||||
results += tx
|
||||
|
||||
val inputsLeadingToUnvisitedTx: Iterable<StateRef> = tx.inputs.filter { it.txhash !in alreadyVisited }
|
||||
val unvisitedInputTxs: Map<SecureHash, SignedTransaction> = inputsLeadingToUnvisitedTx.map { it.txhash }.toHashSet().map { transactions.getTransaction(it) }.filterNotNull().associateBy { it.id }
|
||||
val unvisitedInputTxsWithInputIndex: Iterable<Pair<SignedTransaction, Int>> = inputsLeadingToUnvisitedTx.filter { it.txhash in unvisitedInputTxs.keys }.map { Pair(unvisitedInputTxs[it.txhash]!!, it.index) }
|
||||
next += (unvisitedInputTxsWithInputIndex.filter { (stx, idx) ->
|
||||
query.followInputsOfType == null || stx.tx.outputs[idx].data.javaClass == query.followInputsOfType
|
||||
}.map { it.first }.filter { stx -> alreadyVisited.add(stx.id) }.map { it.tx })
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package net.corda.traderdemo.flow
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.TransactionGraphSearch
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.identity.Party
|
||||
@ -14,6 +13,7 @@ import net.corda.core.utilities.unwrap
|
||||
import net.corda.finance.contracts.CommercialPaper
|
||||
import net.corda.finance.contracts.getCashBalances
|
||||
import net.corda.finance.flows.TwoPartyTradeFlow
|
||||
import net.corda.traderdemo.TransactionGraphSearch
|
||||
import java.util.*
|
||||
|
||||
@InitiatedBy(SellerFlow::class)
|
||||
@ -53,9 +53,12 @@ class BuyerFlow(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
|
||||
private fun logIssuanceAttachment(tradeTX: SignedTransaction) {
|
||||
// Find the original CP issuance.
|
||||
val search = TransactionGraphSearch(serviceHub.validatedTransactions, listOf(tradeTX.tx))
|
||||
search.query = TransactionGraphSearch.Query(withCommandOfType = CommercialPaper.Commands.Issue::class.java,
|
||||
followInputsOfType = CommercialPaper.State::class.java)
|
||||
// TODO: This is potentially very expensive, and requires transaction details we may no longer have once
|
||||
// SGX is enabled. Should be replaced with including the attachment on all transactions involving
|
||||
// the state.
|
||||
val search = TransactionGraphSearch(serviceHub.validatedTransactions, listOf(tradeTX.tx),
|
||||
TransactionGraphSearch.Query(withCommandOfType = CommercialPaper.Commands.Issue::class.java,
|
||||
followInputsOfType = CommercialPaper.State::class.java))
|
||||
val cpIssuance = search.call().single()
|
||||
|
||||
// Buyer will fetch the attachment from the seller automatically when it resolves the transaction.
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.contracts
|
||||
package net.corda.traderdemo
|
||||
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
@ -53,8 +54,7 @@ class TransactionGraphSearchTests : TestDependencyInjectionBase() {
|
||||
@Test
|
||||
fun `return empty from empty`() {
|
||||
val storage = buildTransactions(DummyContract.Commands.Create())
|
||||
val search = TransactionGraphSearch(storage, emptyList())
|
||||
search.query = TransactionGraphSearch.Query()
|
||||
val search = TransactionGraphSearch(storage, emptyList(), TransactionGraphSearch.Query())
|
||||
val expected = emptyList<WireTransaction>()
|
||||
val actual = search.call()
|
||||
|
||||
@ -64,8 +64,7 @@ class TransactionGraphSearchTests : TestDependencyInjectionBase() {
|
||||
@Test
|
||||
fun `return empty from no match`() {
|
||||
val storage = buildTransactions(DummyContract.Commands.Create())
|
||||
val search = TransactionGraphSearch(storage, listOf(storage.inputTx.tx))
|
||||
search.query = TransactionGraphSearch.Query()
|
||||
val search = TransactionGraphSearch(storage, listOf(storage.inputTx.tx), TransactionGraphSearch.Query())
|
||||
val expected = emptyList<WireTransaction>()
|
||||
val actual = search.call()
|
||||
|
||||
@ -75,8 +74,7 @@ class TransactionGraphSearchTests : TestDependencyInjectionBase() {
|
||||
@Test
|
||||
fun `return origin on match`() {
|
||||
val storage = buildTransactions(DummyContract.Commands.Create())
|
||||
val search = TransactionGraphSearch(storage, listOf(storage.inputTx.tx))
|
||||
search.query = TransactionGraphSearch.Query(DummyContract.Commands.Create::class.java)
|
||||
val search = TransactionGraphSearch(storage, listOf(storage.inputTx.tx), TransactionGraphSearch.Query(DummyContract.Commands.Create::class.java))
|
||||
val expected = listOf(storage.originTx.tx)
|
||||
val actual = search.call()
|
||||
|
Loading…
Reference in New Issue
Block a user