core: Fix tx resolution bug by topological sorting of downloaded transactions

This commit is contained in:
Andras Slemmer 2016-09-07 18:14:05 +01:00
parent 287326b118
commit d6f82637ad
3 changed files with 70 additions and 6 deletions

View File

@ -54,11 +54,13 @@ class DummyContract : Contract {
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
}
fun move(prior: StateAndRef<DummyContract.SingleOwnerState>, newOwner: PublicKey): TransactionBuilder {
val priorState = prior.state.data
fun move(prior: StateAndRef<DummyContract.SingleOwnerState>, newOwner: PublicKey) = move(listOf(prior), newOwner)
fun move(priors: List<StateAndRef<DummyContract.SingleOwnerState>>, newOwner: PublicKey): TransactionBuilder {
require(priors.size > 0)
val priorState = priors[0].state.data
val (cmd, state) = priorState.withNewOwner(newOwner)
return TransactionType.General.Builder(notary = prior.state.notary).withItems(
/* INPUT */ prior,
return TransactionType.General.Builder(notary = priors[0].state.notary).withItems(
/* INPUTS */ *priors.toTypedArray(),
/* COMMAND */ Command(cmd, priorState.owner),
/* OUTPUT */ state
)

View File

@ -32,6 +32,44 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
companion object {
private fun dependencyIDs(wtx: WireTransaction) = wtx.inputs.map { it.txhash }.toSet()
private fun topologicalSort(transactions: Iterable<SignedTransaction>): List<SignedTransaction> {
// Construct txhash -> dependent-txhashes map
val forwardGraph = HashMap<SecureHash, HashSet<SecureHash>>()
transactions.forEach { tx ->
tx.tx.inputs.forEach { input ->
forwardGraph.getOrPut(input.txhash) { HashSet() }.add(tx.id)
}
}
// Construct txhash -> tx map
val transactionMap = HashMap<SecureHash, SignedTransaction>()
transactions.forEach { transactionMap[it.tx.id] = it }
val visited = HashSet<SecureHash>()
val result = LinkedList<SignedTransaction>()
fun visit(transactionHash: SecureHash) {
if (transactionHash in visited) {
return
}
visited.add(transactionHash)
forwardGraph[transactionHash]?.forEach {
visit(it)
}
result.addFirst(transactionMap[transactionHash]!!)
}
transactions.forEach {
visit(it.tx.id)
}
require(result.size == transactionMap.size)
require(visited.size == transactionMap.size)
return result
}
}
class ExcessivelyLargeTransactionGraph() : Exception()
@ -60,7 +98,7 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
@Suspendable
override fun call(): List<LedgerTransaction> {
val newTxns: Iterable<SignedTransaction> = downloadDependencies(txHashes)
val newTxns: Iterable<SignedTransaction> = topologicalSort(downloadDependencies(txHashes))
// For each transaction, verify it and insert it into the database. As we are iterating over them in a
// depth-first order, we should not encounter any verification failures due to missing data. If we fail

View File

@ -97,6 +97,30 @@ class ResolveTransactionsProtocolTest {
}
}
@Test
fun `triangle of transactions resolves fine`() {
val stx1 = makeTransactions().first
val stx2 = DummyContract.move(stx1.tx.outRef(0), MINI_CORP_PUBKEY).run {
signWith(MEGA_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
toSignedTransaction()
}
val stx3 = DummyContract.move(listOf(stx1.tx.outRef(0), stx2.tx.outRef(0)), MINI_CORP_PUBKEY).run {
signWith(MEGA_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
toSignedTransaction()
}
a.services.recordTransactions(stx2, stx3)
val p = ResolveTransactionsProtocol(setOf(stx3.id), a.info.identity)
val future = b.services.startProtocol("resolve", p)
net.runNetwork()
future.get()
}
@Test
fun attachment() {
val id = a.services.storageService.attachments.importAttachment("Some test file".toByteArray().opaque().open())