Skip to content

fix: derive the advertised port from --port, not from --ip - #455

Open
samlaf wants to merge 2 commits into
mainfrom
sl/fix-advertised-port
Open

fix: derive the advertised port from --port, not from --ip#455
samlaf wants to merge 2 commits into
mainfrom
sl/fix-advertised-port

Conversation

@samlaf

@samlaf samlaf commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

--ip parsed its value as a SocketAddr, making its port a second copy of --port that silently won on one side of the node. The listen address comes from wildcard_listen_for(our_ip, flags.port), which takes only the address family from --ip and the port from --port, while the address handed to the P2P config is --ip verbatim. So --ip 203.0.113.7:18551 with --port 26000 leaves the node listening on 26000 and signing a gossiped peer record that tells the cohort to dial 18551. Peers dial a port nothing accepts on, the node looks healthy locally, and no validation anywhere compares the two.

Take --ip as an IpAddr and pair it with --port, which is what the public-IP fallback in get_node_ip already does for the third resolution path. One port, from one flag, and the mismatch stops being representable. This mirrors the shape of --prom-ip/--prom-port and --rpc-ip/--rpc-port, drops the IPv6 bracketing burden on callers (2001:db8::7 with a port appended never parsed), and lets clap reject a malformed value as a usage error instead of panicking on a later parse.

The flag matters most to a node joining an existing network: it has no genesis committee entry to read its address from, so without --ip it resolves its public IP by asking external IP-echo services and signs whatever they return. An operator who already knows the address — a deployment that assigns it, or one whose execution client is advertising that same address — can now supply just the host.

Found while wiring Seismic's TEE node images, which are the first caller that needs --ip. A TDX node receives its public address in the boot configuration it is sent on every boot, and tdx-init hands that one address to both reth and summit:
https://github.com/SeismicSystems/enclave/tree/seismic/bin/tdx-init Nothing in-tree passes --ip today (the RunFlags builders in the testnet and e2e binaries leave it None), so this was a latent footgun rather than a live defect.

--ip parsed its value as a SocketAddr, making its port a second copy of
--port that silently won on one side of the node. The listen address
comes from wildcard_listen_for(our_ip, flags.port), which takes only the
address family from --ip and the port from --port, while the address
handed to the P2P config is --ip verbatim. So --ip 203.0.113.7:18551
with --port 26000 leaves the node listening on 26000 and signing a
gossiped peer record that tells the cohort to dial 18551. Peers dial a
port nothing accepts on, the node looks healthy locally, and no
validation anywhere compares the two.

Take --ip as an IpAddr and pair it with --port, which is what the
public-IP fallback in get_node_ip already does for the third resolution
path. One port, from one flag, and the mismatch stops being
representable. This mirrors the shape of --prom-ip/--prom-port and
--rpc-ip/--rpc-port, drops the IPv6 bracketing burden on callers
(2001:db8::7 with a port appended never parsed), and lets clap reject a
malformed value as a usage error instead of panicking on a later parse.

The flag matters most to a node joining an existing network: it has no
genesis committee entry to read its address from, so without --ip it
resolves its public IP by asking external IP-echo services and signs
whatever they return. An operator who already knows the address — a
deployment that assigns it, or one whose execution client is advertising
that same address — can now supply just the host.

Found while wiring Seismic's TEE node images, which are the first caller
that needs --ip. A TDX node receives its public address in the boot
configuration it is sent on every boot, and tdx-init hands that one
address to both reth and summit:
https://github.com/SeismicSystems/enclave/tree/seismic/bin/tdx-init
Nothing in-tree passes --ip today (the RunFlags builders in the testnet
and e2e binaries leave it None), so this was a latent footgun rather
than a live defect.
The four e2e binaries that pin an advertised address each wrote the port
into --ip as well, and each wrote the same one get_node_flags had already
put in --port: 26600 + slot * 10, spelled 127.0.0.1:26640 for the joining
node at slot 4 and computed from OBSERVER_SLOT for the observer. Pass the
bare loopback address and let --port carry the port, which leaves every
one of them advertising exactly the port it did before.

Four hand-maintained copies of one port is the duplication --ip's
SocketAddr invited, so these are the callers the parent change is for.
samlaf added a commit to SeismicSystems/enclave that referenced this pull request Aug 18, 2026
Write summit.env with SUMMIT_ADVERTISED_ADDR, this node's external_ip at
the consensus port, for summit's unit to splice into --ip.

An address is not part of validator identity in summit: its genesis
carries the founding cohort's addresses outside the config digest, and a
validator's consensus-state record holds none at all. Each node instead
signs its own address and gossips that record, so the address a node
declares is what the whole cohort dials it on. Nothing was delivering
that declaration, which leaves it to summit's fallbacks: a founding node
reads its own genesis entry, but a node joining an existing network has
no entry there and resolves its address by asking public IP-echo
services, then signs and gossips whatever they return.

Sourcing it from [node].external_ip is what keeps the two planes
agreeing on where the machine is — the same field reth advertises via
--nat extip, so no second delivered address can skew from it. It also
keeps a founding node truthful across an address change, which its
founding-era genesis entry cannot follow (those entries are deliberately
outside the config digest, so IP churn never re-founds a network).

The value is a SocketAddr rather than a textually composed string: its
Display brackets IPv6, which is the form summit parses. Composing
"<external_ip>:<port>" would emit 2001:db8::7:18551 for an IPv6 node,
which summit rejects at startup.

The consensus port is named here until summit takes --ip as a bare
address and pairs it with its own --port
(SeismicSystems/summit#455); the constant
carries a TODO to drop it and the bracketing along with it.
@samlaf
samlaf requested a review from matthias-wright August 18, 2026 21:15
@linear-code

linear-code Bot commented Aug 18, 2026

Copy link
Copy Markdown

SEI-306

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.

1 participant