Merged in aslemmer-fix-tx-resolution-bug (pull request #341)

core: Fix tx resolution bug by topological sorting of downloaded transactions
This commit is contained in:
Andras Slemmer 2016-09-08 10:27:39 +01:00
commit 52d406a7a0
3 changed files with 67 additions and 8 deletions

View File

@ -54,14 +54,16 @@ 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,39 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
companion object {
private fun dependencyIDs(wtx: WireTransaction) = wtx.inputs.map { it.txhash }.toSet()
private fun topologicalSort(transactions: Collection<SignedTransaction>): List<SignedTransaction> {
// Construct txhash -> dependent-txs map
val forwardGraph = HashMap<SecureHash, HashSet<SignedTransaction>>()
transactions.forEach { tx ->
tx.tx.inputs.forEach { input ->
// Note that we use a LinkedHashSet here to make the traversal deterministic (as long as the input list is)
forwardGraph.getOrPut(input.txhash) { LinkedHashSet() }.add(tx)
}
}
val visited = HashSet<SecureHash>(transactions.size)
val result = ArrayList<SignedTransaction>(transactions.size)
fun visit(transaction: SignedTransaction) {
if (transaction.id !in visited) {
visited.add(transaction.id)
forwardGraph[transaction.id]?.forEach {
visit(it)
}
result.add(transaction)
}
}
transactions.forEach {
visit(it)
}
result.reverse()
require(result.size == transactions.size)
return result
}
}
class ExcessivelyLargeTransactionGraph() : Exception()
@ -60,7 +93,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
@ -96,7 +129,7 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
override val topic: String get() = throw UnsupportedOperationException()
@Suspendable
private fun downloadDependencies(depsToCheck: Set<SecureHash>): List<SignedTransaction> {
private fun downloadDependencies(depsToCheck: Set<SecureHash>): Collection<SignedTransaction> {
// Maintain a work queue of all hashes to load/download, initialised with our starting set. Then do a breadth
// first traversal across the dependency graph.
//
@ -146,7 +179,7 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
throw ExcessivelyLargeTransactionGraph()
}
return resultQ.values.reversed()
return resultQ.values
}
/**

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())