fix(net): validate peer transactions before mempool insertion#87
fix(net): validate peer transactions before mempool insertion#87ace-degen wants to merge 1 commit into
Conversation
d05b5aa to
0c755b6
Compare
| let _: bitcoin::Amount = self | ||
| .ctxt | ||
| .state | ||
| .validate_transaction(&rwtxn, &new_tx)?; |
There was a problem hiding this comment.
This would cause the networking task to stop, if validate_transaction(..) were to return an error.
We should distinguish between fatal (task/thread/app cannot continue) and non-fatal (transaction rejected) errors when validating txs, probably via something like error-fatality
There was a problem hiding this comment.
you right, will fix
The P2P NewTransaction handler inserted peer-supplied transactions into the mempool via MemPool::put without first running State::validate_transaction, unlike the RPC path. A transaction with an invalid signature or failing balance checks was therefore accepted into the mempool and re-broadcast to peers. Run validate_transaction in the handler before proof regeneration and mempool insertion, matching the RPC path. Distinguish fatal from non-fatal validation errors with error-fatality so invalid peer transactions are rejected without stopping the networking task or peer PushTransaction handler; only database errors are propagated as fatal. Also reject transactions whose supplied UTXO hash does not match the referenced outpoint output, and treat mempool double-spends as a normal peer rejection rather than a fatal net-task error. Update the regression test to cover the validator boundary without relying on debug output.
0c755b6 to
f1d8df4
Compare
|
Updated the PR to address the review feedback more directly. Changes in the amended commit:
All local checks pass: |
|
sorry, i will change the PRs on the other projects when this is accepted and merged |
|
I think the reported severity is incorrect. Peer-supplied txs are validated here, before they reach the networking task and the mempool. The unit test in this PR inserts invalid txs directly into the mempool, but peer txs are never inserted directly into the mempool before validating. My earlier comments are not relevant unless tx validation takes place in the networking task. It is fine if invalid txs cause peer connection tasks to complete with an error. This will cause an error to be logged, but the error does not propagate to other tasks, and so the node will continue to work as expected. |
Summary
The P2P
NewTransactionhandler inlib/node/net_task.rsinserted peer-suppliedtransactions into the mempool via
MemPool::putwithout first runningState::validate_transaction. The RPC submission path (Node::submit_transaction)does validate. Because of this asymmetry, a transaction received from a peer with
an invalid signature (or one that fails balance/fee checks) is accepted into the
mempool and re-broadcast to all peers, even though it can never be included in a
valid block.
Any peer can exploit this to flood the network's mempools with invalid transactions:
their mempools indefinitely (unbounded growth from free, keyless traffic).
(The block-template path is self-healing:
Node::get_transactionsre-validates anddrops invalid txs before building a template, so they never reach a block — the
impact is relay amplification and mempool bloat, not poisoned templates.)
(Severity: network-level DoS — no direct theft, since invalid transactions are still
rejected at block-connect time.)
Root cause
The handler called only
regenerate_proof(which generates a Utreexo proof, not avalidation step) followed by
mempool.put(which only guards against double-spendswithin the mempool). Neither verifies signatures, address binding, input existence,
or fees.
Fix
Run
State::validate_transactionin theNewTransactionhandler beforeMemPool::put, mirroring the RPC path.Test
Adds
lib/mempool.rs::p2p_validation_bypass_tests, a runtime regression test thatbuilds a transaction spending a funded UTXO but signs it with the wrong key, then
asserts:
State::validate_transactionrejects it with anAuthorizationerror, butMemPool::puton its own accepts it and the invalid transaction is left residentin the mempool.