Extension DHT bridge for a native beacon node—not a beacon itself.
This project does not sync slots, attest, gossip blocks, or run consensus. It only bridges discovery so nodes that reach each other over web3:// / CoNET-L0D can still find and dial peers the way a normal beacon DHT would, without advertising public IP multiaddrs.
Repository: github.com/CoNET-project/web3-DHT-Beacon
| Is | An extension DHT beside the native Prysm (or other) beacon |
| Is | A bridge: web3:// wallet locators ↔ bootstrap / peer records the operator (or adapter) feeds into the native beacon / L0d |
| Is not | A beacon-chain, validator, checkpoint sync server, or public-IP discv5 replacement |
| Does not | Implement beacon P2P gossip, fork choice, or execution engine APIs |
Native beacon keeps all consensus and libp2p duties. web3-beacon-dht only solves discovery naming when peers are wallet-addressed.
Native beacon DHT (discv5 ENR) binds reachability to IP (/ip4/…/tcp/… or /udp/…).
CoNET nodes that only peer through conet-l0d publish wallet locators. Classic DHT cannot introduce them to each other without leaking public IPs.
| Surface | Transport | Bridge config / API | Default port |
|---|---|---|---|
| TCP duplex / beacon libp2p | conet-l0d proxy_duplex / client_duplex |
logical_port, bootstrap_nodes |
4210 |
| UDP discv5 + QUIC | LayerMinus Chat mailbox (not L0d udp_ports, not duplex) |
logical_udp_port, discovery_nodes, discv5_quic_enabled |
4300 |
Multiaddrs (naming only — UDP bytes do not ride L0d datagram overlay):
/web3/<wallet>/tcp/4210/p2p/<peer_id> # TCP duplex
/web3/<wallet>/udp/4300/p2p/<peer_id> # UDP discovery locator
URI forms:
web3://<wallet>:4210#peer_id=<id> # bootstrap (TCP)
web3://<wallet>:4300#peer_id=<id>&transport=udp # discovery (UDP)
Set logical_udp_port = 0 to disable the UDP surface (TCP-only hubs).
When discv5_quic_enabled = true:
- Egress: intercept local Prysm UDP → encrypt to recipient user PGP →
POST { "data": armor }to a healthy entry (one-shot Chat, not a long-lived duplex). - Ingress: adapter / lab
POST /web3/v1/dht/udp-datagram(JSON datagram or armoreddata) → inject tobeacon_udp_inject(local Prysm discv5/QUIC listen). - Announce may include
user_public_key_armoredso peers know the Chat encrypt target.
Lab-only: mailbox_loopback = true hairpins without SI.
┌─────────────────────────┐
native beacon │ web3-beacon-dht │
(unchanged) │ (extension / bridge) │
│ │
--bootstrap-node │ wallet-keyed records │
←── web3 TCP │ TCP 4210 + UDP 4300 │
+ UDP disc │ (never public IP ENR) │
└───────────┬─────────────┘
┌───────────┴─────────────┐
▼ ▼
conet-l0d duplex (TCP) Chat mailbox (UDP discs)
│ │
▼ ▼
remote peer TCP remote peer discv5/QUIC
- Peers announce to the bridge: wallet +
peer_id+ TCP and/or UDPweb3://…(never an IP host). - Operators read
/web3/v1/dht/bootstrap:- Default (dual):
bootstrap_nodes(TCP duplex) +discovery_nodes(UDP discv5/QUIC). - UDP-only product (
bootstrap_udp_only = true): onlydiscovery_nodes;bootstrap_nodesandlab_prysm_bootstrap_enrsare empty. Hub.30lab uses this mode.
- Default (dual):
- Actual CL traffic still runs on the native beacon; the bridge helps peers discover who to dial over
web3://, and optionally carry UDP discovery bytes (HTTP peer / Chat mailbox).
| Must | Must not |
|---|---|
Every bootstrap_nodes[] / discovery_nodes[] entry is web3://… |
Classic discv5 enr:… |
Announce stores only web3:// multiaddrs (/tcp/ and /udp/) |
/ip4/… / /dns4/… public multiaddrs |
| Hub + peer locators hide public IPs from learners | Returning a hub/public ENR that teaches spoke nodes a dialable public address |
UDP-only hubs: spokes consume discovery_nodes only |
Product path using lab_prysm_bootstrap_enrs / --peer / classic ENR |
classic_enr on announce is rejected. Stored snapshots strip any legacy ENR on load. Bootstrap never prefers ENR over web3://.
Production-oriented bridge: signed announce (v1 TCP / v2 TCP+UDP), split TCP/UDP bootstrap, one-shot pull CLI. Debian/apt packaging and a tighter native-beacon bootstrap adapter are planned.
| Area | Behavior |
|---|---|
| Role | Extension DHT bridge only |
| Identity | Peers keyed by wallet (0x + 40 hex) or exact tag — real EOA, not hash labels |
| Addresses | /web3/<host>/{tcp|udp}/<port>/p2p/<peer_id> — IP hosts rejected |
| Announce | EIP-191 personal_sign — v2 when UDP published; v1 still accepted for TCP-only |
| Bootstrap | GET /web3/v1/dht/bootstrap — hub first, deduped; dual or bootstrap_udp_only (UDP discovery_nodes only) |
| Pull | CLI pull-bootstrap prints TCP then UDP; udp-only hubs require non-empty discovery_nodes |
| Persist | Optional JSON snapshot |
| Method | Path | Purpose |
|---|---|---|
GET |
/eth/v1/node/identity |
Bridge hub identity (TCP + UDP discovery addresses) |
GET |
/eth/v1/node/peer_count |
Registered record count |
GET |
/eth/v1/node/version |
web3-beacon-dht/0.2.1 |
POST |
/web3/v1/dht/announce |
Publish / refresh (signature required when enforced) |
GET |
/web3/v1/dht/peers |
List or lookup by ?wallet= |
GET |
/web3/v1/dht/bootstrap |
One-shot TCP + UDP locators |
POST |
/web3/v1/dht/udp-datagram |
Inject discv5/QUIC datagram (requires discv5_quic_enabled) |
Announce body (JSON):
{
"wallet": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"peer_id": "16Uiu2HAmExamplePeerId",
"logical_port": 4210,
"logical_udp_port": 4300,
"web3_uris": ["web3://0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:4210"],
"web3_udp_uris": ["web3://0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:4300"],
"user_public_key_armored": "-----BEGIN PGP PUBLIC KEY BLOCK-----\\n…",
"seq": 1,
"ttl_secs": 86400,
"signature": "0x…65-byte EIP-191 personal_sign…"
}Signed messages:
# v1 (TCP-only / legacy)
web3-beacon-dht-announce:v1:<wallet_lower>:<peer_id>:<logical_port>:<seq>
# v2 (TCP + UDP discovery surface — preferred when logical_udp_port is set)
web3-beacon-dht-announce:v2:<wallet_lower>:<peer_id>:<tcp_port>:<udp_port>:<seq>
Bootstrap response shape (dual track):
{
"bootstrap_nodes": ["web3://0x…:4210#peer_id=…"],
"discovery_nodes": ["web3://0x…:4300#peer_id=…&transport=udp"],
"bootstrap_udp_only": false,
"tcp_ports": [4210],
"udp_ports": [4300],
"discv5_quic_enabled": false,
"udp_transport": "chat_mailbox",
"count": 1,
"discovery_count": 1
}UDP-only product hub (bootstrap_udp_only = true in toml; e.g. deploy/30/):
{
"bootstrap_nodes": [],
"discovery_nodes": ["web3://0x…:4310#peer_id=…&transport=udp"],
"bootstrap_udp_only": true,
"lab_prysm_bootstrap_enrs": [],
"tcp_ports": [],
"udp_ports": [4310],
"count": 0,
"discovery_count": 1
}Lab deploy notes: deploy/30/ (hub), deploy/45/ (spoke consumer), deploy/98/ (UDP announce).
rustup show # toolchain pinned in rust-toolchain.toml (1.87.0)
cargo build --release
./target/release/web3-beacon-dht check-config --config config/web3-beacon-dht.example.toml
./target/release/web3-beacon-dht resolve 'web3://0x4E72cC4EfC8c87d3297d07b7D8f225E355F6Ee47:4210'
./target/release/web3-beacon-dht pull-bootstrap --url http://127.0.0.1:4120
./target/release/web3-beacon-dht start --config config/web3-beacon-dht.example.tomlSee config/web3-beacon-dht.example.toml and production deploy/30/web3-beacon-dht.toml.
wallet = "0x4E72cC4EfC8c87d3297d07b7D8f225E355F6Ee47"
peer_id = "16Uiu2HAmReplaceWithStableHubPeerId"
listen = "127.0.0.1:4120"
logical_port = 4210
logical_udp_port = 4300
dht_server_enabled = true
discv5_quic_enabled = false
persist_path = "/var/lib/web3-beacon-dht/peers.json"
evict_interval_secs = 300
require_announce_sig = trueKeep the HTTP listen address on loopback or a private overlay. TCP peer reachability stays with native beacon + conet-l0d duplex. UDP discv5/QUIC (when enabled) uses Chat mailbox one-shot POST, not L0d udp_ports / duplex.
Example unit: systemd/web3-beacon-dht.service.
sudo install -m 0755 target/release/web3-beacon-dht /usr/bin/web3-beacon-dht
sudo install -m 0644 config/web3-beacon-dht.example.toml /etc/web3-beacon-dht.toml
sudo install -m 0644 systemd/web3-beacon-dht.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now web3-beacon-dhtFuture apt-get install web3-beacon-dht packaging will use the same unit layout.
| Component | Role |
|---|---|
| Native beacon (Prysm, …) | Consensus + libp2p; unchanged |
| CoNET-L0D | web3:// duplex for TCP (4210) |
| LayerMinus / CoNET-SI Chat | Mailbox for UDP discv5/QUIC datagrams (4300 logical) |
| web3-DHT-Beacon (this repo) | Extension DHT bridge — TCP bootstrap + UDP discovery naming + optional Chat UDP proxy |
Classic IP DHT :4110 |
Still used by IP-native L1 hubs (not wallet overlay) |
MIT — see LICENSE.