Skip to content

fix(dns): rejoin TXT character-strings for EIP-1459 entries over 255 bytes - #26602

Merged
mattsse merged 1 commit into
paradigmxyz:mainfrom
MysticRyuujin:fix/dns-txt-multistring
Aug 5, 2026
Merged

mattsse merged 1 commit into
paradigmxyz:mainfrom
MysticRyuujin:fix/dns-txt-multistring

Conversation

@MysticRyuujin

Copy link
Copy Markdown
Contributor

Description

DNS discovery currently resolves zero nodes from every real enrtree, including
all.mainnet.ethdisco.net.

lookup_txt reads only the first <character-string> of a TXT record. RFC 1035 caps a single
<character-string> at 255 bytes, while an enrtree entry is bounded by the 512 byte DNS UDP limit
that EIP-1459 budgets against, and EIP-778 permits a 300 byte ENR, about 404 characters once
base64url encoded. Long entries are therefore normal rather than unusual: go-ethereum deliberately
sizes branch records up to 370 bytes, and 219 of 300 sampled records on the mainnet tree span several
character-strings. hickory returns one list element per character-string, so they have to be rejoined
with no separator, as Go's resolver does (net/lookup.go: "Multiple strings in one TXT record need
to be concatenated without separator").

The loss is total rather than partial because verify_entry_hash from #22582 works correctly: a
truncated record does not hash to its subdomain, so the entry is rejected and its whole subtree is
dropped. Mainnet's first branch record is short enough to survive, but the level-2 branches are 370
bytes, so both subtrees die before any ENR is reached. The failure is only visible as HashMismatch
debug logs, and discv4/discv5 keep supplying peers, which is presumably why it went unnoticed.

Measured against live trees, deduplicating ENRs until the service stayed idle:

tree before after go-ethereum
all.mainnet.ethdisco.net at seq 8393 0 3000 3000
a 25 node tree with multi-string records, second publisher 0 25 25
its 25 node snap subtree 0 25 25

Change

txt_entry concatenates all of txt_data and performs a single UTF-8 decode over the joined bytes.
Decoding per chunk would be wrong in principle, since a multi-byte sequence can straddle a chunk
boundary. No new dependencies.

Tests

  • txt_entry_joins_character_strings covers the join directly.
  • lookup_txt_reads_record_split_over_character_strings is end to end: a stub UDP nameserver bound
    on 127.0.0.1:0 encodes a real DNS response with hickory_proto, and a resolver built via
    ResolverConfig::from_parts resolves through it, so hickory's actual wire decoder is exercised
    rather than a hand-built TXT value.

Both fail without the change. Reverting txt_entry to txt_data.first() gives:

test resolver::tests::txt_entry_joins_character_strings ... FAILED
test resolver::tests::lookup_txt_reads_record_split_over_character_strings ... FAILED
  left: Some("enrtree-branch:XQ3H...,2M7CLAE7ZKRLNEAFM6ASQUST")
 right: Some("enrtree-branch:XQ3H...,2M7CLAE7ZKRLNEAFM6ASQUSTTU,WLT3...,JXQOS2YKRCDOIKRMGW2X36FJ2U")

The truncation cuts off mid-base32-label at byte 255, which is what makes the record fail its hash
check.

cargo test -p reth-dns-discovery: 22 passed, 1 ignored (the pre-existing live-network test), 2
doctests. cargo +nightly fmt --check and cargo clippy -p reth-dns-discovery --all-targets are
clean.

Before this change the crate could not represent a multi-string record at all. MapResolver stores
one already-joined String per name, so the split is unrepresentable, and the impl on
hickory_resolver::Resolver<P> carrying the bug had no test coverage of any kind. The stub
nameserver added here is the first test infrastructure in the crate that can express it.

Scope

One adjacent issue is out of scope, pre-existing and not made worse by this change: lookup_txt
selects the first TXT record at a name via answers().iter().find_map(...), whereas go-ethereum
iterates every TXT record and takes the first that parses as a known entry. A name serving an
unrelated TXT record ahead of the enrtree entry would still fail. Every public tree apex checked,
all.mainnet.ethdisco.net, all.sepolia.ethdisco.net and all.hoodi.ethdisco.net, serves exactly
one TXT record, so the present impact is nil, and fixing it means either teaching lookup_txt the
enrtree grammar or widening the Resolver trait to return every record, neither of which belongs in
a truncation fix.

@Rjected Rjected left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm

@github-project-automation github-project-automation Bot moved this from Backlog to In Progress in Reth Tracker Aug 5, 2026

@mattsse mattsse left a comment

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.

ty

@mattsse
mattsse added this pull request to the merge queue Aug 5, 2026
Merged via the queue into paradigmxyz:main with commit db18362 Aug 5, 2026
35 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Reth Tracker Aug 5, 2026
@mattsse mattsse added C-bug An unexpected or incorrect behavior M-changelog This change should be included in the changelog labels Aug 5, 2026
teyrebaz33 pushed a commit to teyrebaz33/tempo that referenced this pull request Aug 25, 2026
Automated nightly update of reth dependencies from `paradigmxyz/reth`
main branch.

## Upstream reth changes


[`10aa6a5...00ff650`](paradigmxyz/reth@10aa6a5...00ff650)

🔗 Amp thread:
https://ampcode.com/threads/T-01a036f1-9f58-7640-b789-4e3dd9778ebe
- **Engine**
- Improved payload building across canonical ancestors, finality,
persistence handoffs, pending resolution, and responsive cancellation
([#26559](paradigmxyz/reth#26559),
[#26567](paradigmxyz/reth#26567),
[#26580](paradigmxyz/reth#26580),
[#26708](paradigmxyz/reth#26708),
[#26759](paradigmxyz/reth#26759)).
- Added Bogota Engine API support and fork-time validation, plus FOCIL
inclusion-list construction and stubs
([#26682](paradigmxyz/reth#26682),
[#26706](paradigmxyz/reth#26706),
[#26711](paradigmxyz/reth#26711),
[#26737](paradigmxyz/reth#26737)).
- Tightened payload and block-access-list validation, including
state-gas admission and malformed BAL rejection
([#26651](paradigmxyz/reth#26651),
[#26694](paradigmxyz/reth#26694),
[#26719](paradigmxyz/reth#26719)).
- Fixed little-endian cell bitvectors, Osaka `getBlobsV4`, and
prewarm-worker shutdown
([#26650](paradigmxyz/reth#26650),
[#26703](paradigmxyz/reth#26703),
[#26768](paradigmxyz/reth#26768)).
- Reused scratch buffers for faster BAL hash encoding
([#26701](paradigmxyz/reth#26701)).

- **RPC**
- Added `debug_traceChain`, Alloy trace-chain result types, block-level
EVM reuse, and raw block transactions on the auth server
([#26582](paradigmxyz/reth#26582),
[#26614](paradigmxyz/reth#26614),
[#26669](paradigmxyz/reth#26669),
[#26760](paradigmxyz/reth#26760)).
- Added configurable response compression and request decompression
([#26668](paradigmxyz/reth#26668),
[#20277](paradigmxyz/reth#20277)).
- Added Bogota Engine API stubs and Amsterdam system contracts to
`eth_config` ([#26691](paradigmxyz/reth#26691),
[#26705](paradigmxyz/reth#26705)).
- Fixed cancellation of payload-hash and blocking-I/O work
([#26569](paradigmxyz/reth#26569),
[#26776](paradigmxyz/reth#26776)).
- Corrected execution-witness block identifiers, optional receipt
conversion/caching, and access-list environment preparation
([#26572](paradigmxyz/reth#26572),
[#26596](paradigmxyz/reth#26596),
[#26599](paradigmxyz/reth#26599),
[#26598](paradigmxyz/reth#26598)).
- Fixed testing block gas limits, transaction gas-limit preservation,
timestamp overflow, and chain-ID validation
([#26632](paradigmxyz/reth#26632),
[#26743](paradigmxyz/reth#26743),
[#26767](paradigmxyz/reth#26767),
[#26782](paradigmxyz/reth#26782)).
- Added network-specific log responses and generic testing RPC handlers
([#26491](paradigmxyz/reth#26491),
[#26547](paradigmxyz/reth#26547)).

- **Networking**
- Added ingress limits, configurable no-op client versions, and outbound
`GetCells` support
([#26660](paradigmxyz/reth#26660),
[#26652](paradigmxyz/reth#26652),
[#26673](paradigmxyz/reth#26673)).
- Improved handshake and protocol safety through ECIES identity checks,
message-ID validation, negotiated-protocol assertions, and bad-message
handling ([#26639](paradigmxyz/reth#26639),
[#26654](paradigmxyz/reth#26654),
[#26659](paradigmxyz/reth#26659),
[#26671](paradigmxyz/reth#26671)).
- Fixed ping/pong validation and pacing
([#26698](paradigmxyz/reth#26698),
[#26702](paradigmxyz/reth#26702)).
- Made eth/72 blob-cell announcements interoperable with geth while
preserving availability masks
([#26573](paradigmxyz/reth#26573),
[#26670](paradigmxyz/reth#26670)).
- Shared the snap/2 slim-account codec between client and server
([#26587](paradigmxyz/reth#26587)).

- **Txpool**
- Added blob-cell availability tracking and exposure on pooled
transactions ([#25463](paradigmxyz/reth#25463),
[#26642](paradigmxyz/reth#26642)).
- Included blob-pool transactions in queued counts and listings
([#26677](paradigmxyz/reth#26677),
[#26679](paradigmxyz/reth#26679)).
- Allowed senders with empty code hashes, added consensus encoding, and
converted sender accessors to iterators
([#26644](paradigmxyz/reth#26644),
[#26739](paradigmxyz/reth#26739),
[#26681](paradigmxyz/reth#26681)).

- **Trie & State**
- Added partial trie unwind and persistence support, including
changeset-cache handling
([#26543](paradigmxyz/reth#26543),
[#26612](paradigmxyz/reth#26612)).
- Corrected witness construction to use depth-first node order
([#26707](paradigmxyz/reth#26707)).
- Simplified `HashedPostState` wipe handling and added trie-data
reference collectors
([#26524](paradigmxyz/reth#26524),
[#26752](paradigmxyz/reth#26752)).

- **Storage & Providers**
- Removed `ConsistentDbView`, relocated `OverlayStateProvider`, and
simplified provider bounds
([#26581](paradigmxyz/reth#26581),
[#26611](paradigmxyz/reth#26611),
[#26591](paradigmxyz/reth#26591)).
- Fixed storage-wipe handling during batched persistence
([#26750](paradigmxyz/reth#26750)).
- Made RocksDB tolerate unknown column families
([#26647](paradigmxyz/reth#26647)).
- Updated static-file consistency checks to respect prune checkpoints
([#26565](paradigmxyz/reth#26565)).

- **Chainspec & Consensus**
- Added Bogota hardfork support and validated block-access-list hashes
during import ([#26686](paradigmxyz/reth#26686),
[#26696](paradigmxyz/reth#26696)).
- Honored the genesis `slotNumber` instead of hardcoding zero
([#26680](paradigmxyz/reth#26680)).
- Defaulted unspecified payload attributes
([#26684](paradigmxyz/reth#26684)).

- **DNS**
- Fixed EIP-1459 discovery records by rejoining long TXT character
strings and ignoring unrelated TXT records
([#26602](paradigmxyz/reth#26602),
[#26603](paradigmxyz/reth#26603)).

- **Snapshots & CLI**
- Added base-URL resolution and exposed prepared snapshot context
([#26576](paradigmxyz/reth#26576),
[#26777](paradigmxyz/reth#26777)).
- Fixed history downloads when the final snapshot chunk is partial
([#26607](paradigmxyz/reth#26607)).
- Added bootnode configuration to `reth.toml`
([#26551](paradigmxyz/reth#26551)).

- **Testing & Development**
- Improved engine reorg tests with explicit finality management and
expanded execute-blob Hive coverage
([#26584](paradigmxyz/reth#26584),
[#26606](paradigmxyz/reth#26606)).
- Added a persistent-datadir testing node and made dev-mined blocks
canonical immediately
([#26774](paradigmxyz/reth#26774),
[#26761](paradigmxyz/reth#26761)).

- **Bench**
- Restored metrics visibility in benchmark run configurations
([#26461](paradigmxyz/reth#26461)).

- **Dependencies & Releases**
- Updated Alloy to 2.4.x, `alloy-hardforks`/`alloy-eip7928` to 0.4.8,
and released Reth 2.5.0–2.5.1
([#26663](paradigmxyz/reth#26663),
[#26666](paradigmxyz/reth#26666),
[#26685](paradigmxyz/reth#26685),
[#26687](paradigmxyz/reth#26687),
[#26700](paradigmxyz/reth#26700),
[#26771](paradigmxyz/reth#26771)).

## Migrations

🔗 Amp thread:
https://ampcode.com/threads/T-01a036f2-23b2-77c0-8959-5c3f0f9df6c5
- Upgraded Reth to `00ff650`, Alloy to `2.4.1`, and related dependencies
to match their latest APIs.
- Renamed workspace lint keys from kebab-case to snake_case for updated
Cargo lint syntax.
- Removed the no-longer-needed crate recursion limit.
- Migrated hashed storage construction from the removed `from_iter` API
to direct struct initialization.
- Reused Reth’s prepared snapshot manifest, base URL, and data
directory, removing Tempo’s duplicate manifest discovery, fetching,
parsing, and path-resolution logic.
- Updated snapshot planning to handle Reth’s new `(plan, prepared)`
return value and execution’s optional prepared manifest.
- Updated `HashedPostStateProvider` implementations and callers for its
new fallible `ProviderResult` return type.
- Added the required RPC log associated type and identity `convert_log`
implementation for the updated receipt converter trait.
- Updated pooled transaction construction for the reordered transaction
field and new `blob_cell_availability` field.

[GitHub
Workflow](https://github.com/tempoxyz/tempo/actions/runs/32804636065)

---------

Co-authored-by: Alexey Shekhirin <github@shekhirin.com>
Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com>
Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com>
Co-authored-by: Richard Janis Goldschmidt <701177+SuperFluffy@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-bug An unexpected or incorrect behavior M-changelog This change should be included in the changelog

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants