mirror of
https://github.com/corda/corda.git
synced 2025-01-31 00:24:59 +00:00
core: Fix tx resolution bug by topological sorting of downloaded transactions
This commit is contained in:
parent
287326b118
commit
d6f82637ad
@ -54,11 +54,13 @@ class DummyContract : Contract {
|
|||||||
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
|
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Create(), owner.party.owningKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun move(prior: StateAndRef<DummyContract.SingleOwnerState>, newOwner: PublicKey): TransactionBuilder {
|
fun move(prior: StateAndRef<DummyContract.SingleOwnerState>, newOwner: PublicKey) = move(listOf(prior), newOwner)
|
||||||
val priorState = prior.state.data
|
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)
|
val (cmd, state) = priorState.withNewOwner(newOwner)
|
||||||
return TransactionType.General.Builder(notary = prior.state.notary).withItems(
|
return TransactionType.General.Builder(notary = priors[0].state.notary).withItems(
|
||||||
/* INPUT */ prior,
|
/* INPUTS */ *priors.toTypedArray(),
|
||||||
/* COMMAND */ Command(cmd, priorState.owner),
|
/* COMMAND */ Command(cmd, priorState.owner),
|
||||||
/* OUTPUT */ state
|
/* OUTPUT */ state
|
||||||
)
|
)
|
||||||
|
@ -32,6 +32,44 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun dependencyIDs(wtx: WireTransaction) = wtx.inputs.map { it.txhash }.toSet()
|
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()
|
class ExcessivelyLargeTransactionGraph() : Exception()
|
||||||
@ -60,7 +98,7 @@ class ResolveTransactionsProtocol(private val txHashes: Set<SecureHash>,
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): List<LedgerTransaction> {
|
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
|
// 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
|
// depth-first order, we should not encounter any verification failures due to missing data. If we fail
|
||||||
|
@ -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
|
@Test
|
||||||
fun attachment() {
|
fun attachment() {
|
||||||
val id = a.services.storageService.attachments.importAttachment("Some test file".toByteArray().opaque().open())
|
val id = a.services.storageService.attachments.importAttachment("Some test file".toByteArray().opaque().open())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user