Skip to content

fix(net): validate peer transactions before mempool insertion#87

Open
ace-degen wants to merge 1 commit into
LayerTwo-Labs:masterfrom
ace-degen:fix/p2p-validate-transactions
Open

fix(net): validate peer transactions before mempool insertion#87
ace-degen wants to merge 1 commit into
LayerTwo-Labs:masterfrom
ace-degen:fix/p2p-validate-transactions

Conversation

@ace-degen

Copy link
Copy Markdown
Contributor

Summary

The P2P NewTransaction handler in lib/node/net_task.rs inserted peer-supplied
transactions into the mempool via MemPool::put without first running
State::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:

  • network-wide mempool poisoning / wasted bandwidth (each node accepts and relays),
  • non-mining nodes never run the template builder, so the junk stays resident in
    their mempools indefinitely (unbounded growth from free, keyless traffic).

(The block-template path is self-healing: Node::get_transactions re-validates and
drops 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 a
validation step) followed by mempool.put (which only guards against double-spends
within the mempool). Neither verifies signatures, address binding, input existence,
or fees.

Fix

Run State::validate_transaction in the NewTransaction handler before
MemPool::put, mirroring the RPC path.

Test

Adds lib/mempool.rs::p2p_validation_bypass_tests, a runtime regression test that
builds a transaction spending a funded UTXO but signs it with the wrong key, then
asserts:

  1. State::validate_transaction rejects it with an Authorization error, but
  2. MemPool::put on its own accepts it and the invalid transaction is left resident
    in the mempool.
running 1 test
[validate_transaction(forged tx) => Err(Authorization)]
test mempool::p2p_validation_bypass_tests::p2p_path_accepts_transaction_that_validation_rejects ... ok
test result: ok. 1 passed; 0 failed

@ace-degen ace-degen force-pushed the fix/p2p-validate-transactions branch from d05b5aa to 0c755b6 Compare June 10, 2026 07:34
Comment thread lib/node/net_task.rs Outdated
let _: bitcoin::Amount = self
.ctxt
.state
.validate_transaction(&rwtxn, &new_tx)?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@ace-degen ace-degen force-pushed the fix/p2p-validate-transactions branch from 0c755b6 to f1d8df4 Compare June 12, 2026 09:06
@ace-degen

Copy link
Copy Markdown
Contributor Author

Updated the PR to address the review feedback more directly.

Changes in the amended commit:

  • Split State::validate_transaction errors with error-fatality, so ordinary transaction validation failures are non-fatal while DB errors still propagate.
  • Updated the P2P NewTransaction and peer PushTransaction paths to reject invalid peer transactions without stopping their tasks.
  • Moved validation before Utreexo proof regeneration, so missing/stale inputs hit the non-fatal validation path first.
  • Treat mempool double-spends as normal peer rejections instead of fatal net-task errors.
  • Added validation that each supplied UTXO hash matches the referenced outpoint output before proof regeneration.
  • Cleaned up and extended the regression test around the validator/mempool boundary.

All local checks pass:
cargo fmt --check
cargo check --lib
cargo test --lib
cargo clippy --lib

@ace-degen

Copy link
Copy Markdown
Contributor Author

sorry, i will change the PRs on the other projects when this is accepted and merged

@Ash-L2L

Ash-L2L commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants