Skip to content

mempool: keep accept time across restart; net: rate limit addrv2 before AddrMan - #1034

Open
bkeroack wants to merge 3 commits into
reardencode:masterfrom
bkeroack:mempool-addr/reload-time-addr-rate
Open

bkeroack wants to merge 3 commits into
reardencode:masterfrom
bkeroack:mempool-addr/reload-time-addr-rate

Conversation

@bkeroack

Copy link
Copy Markdown
Contributor

Fixes #1029. Fixes #1030. One commit each.

Mempool entries keep their accept time across a restart (#1029)

Schema bump: this moves the mempool sidecar from schema 3 to 4. docs/ORIENT.md lists a schema bump under "Ask first", so please weigh in on that part in particular. The main store (SCHEMA_VERSION 26) is untouched.

  • Each packed record now stores accept_secs after sigops (fee‖weight‖sigops‖accept_secs‖txid‖wtxid‖tx‖vin aux). SIGOP_COST_OFF is unchanged.
  • Clock: the mempool stamps the time in finish_commit from the hub's mock clock. The mock_now atomic is now an Arc shared with ActiveMempool, so setmocktime reaches it without another step added to the fan-out clock: one mockable node-time source instead of per-subsystem mock clocks #821 describes.
  • AcceptResult.accept_secs carries the value, and the hub indexes admits with it, so RAM and disk agree.
  • Open: the hub takes the loaded (txid, wtxid, accept_secs) and indexes each entry still live. Loaded entries are not added to age_inv, so a restart queues no INVs, as before.
  • Migration: a schema 1–3 sidecar converts on open. Its records get the migrate's wall-clock second, so expiry for those entries counts from the upgrade.
  • Downgrade: an older binary refuses schema 4 as an unreadable image, moves it aside under mempool/torn-<unix>/ and starts with an empty mempool. Class A is not affected.

Tests:

  • reopen_keeps_accept_time_and_expiry (hub): admit under mocktime, reopen, then check accept_time_txid and that the entry expires on schedule. It fails on master, where the time after reopen is None.
  • schema3_converts_on_open_with_migrate_accept_time: sigops are kept, the time is stamped once and stays durable.
  • The packed codec tests cover the v2, v3 and v4 layouts.

Addresses are rate limited before AddrMan (#1030)

AddrTokenBucket follows Core v31.1 ProcessMessage ADDR:

  • It starts with one token, then refills at 0.1 per second up to 1000.
  • on_addrv2 takes tokens before learn_addrv2. Only the admitted prefix is learned and relayed; the rest are dropped.
  • The bucket runs on the peer's node clock, the same clock that sets connected_at.
  • IBD peers spend their own bucket (IbdWorkState::addr_buckets, dropped on peer death).
  • A peer with the addr whitelist permission is not limited (p2p_addrv2_relay.py runs with -whitelist=addr@127.0.0.1).
  • getpeerinfo gains addr_processed and addr_rate_limited.

Not included: Core credits 1000 tokens when it sends getaddr. Here only addr-fetch peers get getaddr, and on_addrv2 drops their multi-address reply before learning, so the credit would be dead code. If outbound full-relay peers start getting a getaddr (Core sends one at handshake), the credit belongs with that change. Core also shuffles the list before spending tokens; this takes the prefix.

Tests:

  • addrv2_flood_is_rate_limited_before_addrman: a fresh inbound peer's 1000-entry addrv2 teaches one address, and the counters are (1, 999). With addr@ all 1000 are learned. It fails on master, where 1000 addresses reach AddrMan.
  • ibd_peer_addr_flood_takes_one_token.
  • The hostile relay journey now advances node time 10,000 s to refill the bucket, instead of relying on a full bucket at connect.

Checks

  • Local: fmt, clippy -D warnings, deny, ast-grep.sh, cargo test --workspace and ci-os-smoke.sh all pass.
  • Core functional, run locally against this build: mempool_expiry, mempool_limit, mempool_unbroadcast, p2p_tx_privacy, p2p_addrv2_relay, p2p_addrfetch and p2p_addr_selfannouncement all pass.

🤖 Generated with Claude Code

bkeroack and others added 3 commits October 15, 2026 22:37
Entry time lived only in the hub's RAM maps, filled on admit. Hub open
loaded the live set but never indexed it, so after a restart
getmempoolentry.time was 0 and -mempoolexpiry never removed a loaded
entry.

The mempool sidecar is schema 4: each packed record stores its accept
time after sigops. The mempool stamps it from the hub's mock clock
(now shared, so setmocktime still reaches it), AcceptResult carries it,
and the hub indexes admits with that same value. At open the hub
indexes loaded entries with their stored time, without queueing an
INV. Schema 1-3 records migrate on open with the migrate's wall-clock
second.

Refs reardencode#1029

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The addr token bucket gated only relay: on_addrv2 learned every row
first, and the bucket started full at 1000. One peer could replace
every never-connected address with nine 1000-entry messages. IBD
peers' addresses were not limited at all.

AddrTokenBucket follows Core v31.1: one token at connect, 0.1 per
second up to 1000. Tokens are taken before learn_addrv2, and only the
admitted prefix is learned and relayed. IBD peers spend a bucket of
their own. A peer with the addr permission is not limited.
getpeerinfo reports addr_processed and addr_rate_limited.

Core credits 1000 tokens when it sends getaddr. Here only addr-fetch
peers get getaddr, and their multi-address reply is dropped before
learning, so there is no credit to add.

Refs reardencode#1030

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Loaded entries were indexed with relay sequence 0. A new peer starts
at last_inv_sequence 1, so is_relay_servable let it getdata a restored
tx before any INV; before the reload fix such a tx had no sequence and
was not servable. Core's load assigns a fresh sequence; do the same.

Refs reardencode#1029

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

net: addrv2 addresses enter AddrMan before the rate limit is applied mempool: transactions loaded at startup have no entry time and never expire

1 participant