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:
Ross Nicoll
2017-09-08 17:27:01 +01:00
committed by GitHub
parent 0edaea81d2
commit 4e18937326
4 changed files with 86 additions and 72 deletions

View File

@ -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
}
}

View File

@ -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)
}
}