mirror of
https://github.com/corda/corda.git
synced 2025-06-16 14:18:20 +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:
@ -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
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.MockTransactionStorage
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class TransactionGraphSearchTests : TestDependencyInjectionBase() {
|
||||
class GraphTransactionStorage(val originTx: SignedTransaction, val inputTx: SignedTransaction) : MockTransactionStorage() {
|
||||
init {
|
||||
addTransaction(originTx)
|
||||
addTransaction(inputTx)
|
||||
}
|
||||
}
|
||||
|
||||
fun random31BitValue(): Int = Math.abs(newSecureRandom().nextInt())
|
||||
|
||||
/**
|
||||
* Build a pair of transactions. The first issues a dummy output state, and has a command applied, the second then
|
||||
* references that state.
|
||||
*
|
||||
* @param command the command to add to the origin transaction.
|
||||
* @param signer signer for the two transactions and their commands.
|
||||
*/
|
||||
fun buildTransactions(command: CommandData): GraphTransactionStorage {
|
||||
val megaCorpServices = MockServices(MEGA_CORP_KEY)
|
||||
val notaryServices = MockServices(DUMMY_NOTARY_KEY)
|
||||
|
||||
val originBuilder = TransactionBuilder(DUMMY_NOTARY)
|
||||
.addOutputState(DummyState(random31BitValue()))
|
||||
.addCommand(command, MEGA_CORP_PUBKEY)
|
||||
|
||||
val originPtx = megaCorpServices.signInitialTransaction(originBuilder)
|
||||
val originTx = notaryServices.addSignature(originPtx)
|
||||
|
||||
val inputBuilder = TransactionBuilder(DUMMY_NOTARY)
|
||||
.addInputState(originTx.tx.outRef<DummyState>(0))
|
||||
.addCommand(dummyCommand(MEGA_CORP_PUBKEY))
|
||||
|
||||
val inputPtx = megaCorpServices.signInitialTransaction(inputBuilder)
|
||||
val inputTx = megaCorpServices.addSignature(inputPtx)
|
||||
|
||||
return GraphTransactionStorage(originTx, inputTx)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `return empty from empty`() {
|
||||
val storage = buildTransactions(DummyContract.Commands.Create())
|
||||
val search = TransactionGraphSearch(storage, emptyList())
|
||||
search.query = TransactionGraphSearch.Query()
|
||||
val expected = emptyList<WireTransaction>()
|
||||
val actual = search.call()
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@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 expected = emptyList<WireTransaction>()
|
||||
val actual = search.call()
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@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 expected = listOf(storage.originTx.tx)
|
||||
val actual = search.call()
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user