mirror of
https://github.com/corda/corda.git
synced 2025-04-10 12:50:37 +00:00
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:
commit
52d406a7a0
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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())
|
||||
|
Loading…
x
Reference in New Issue
Block a user