Automated system that downloads the latest
all.json crawl from the
Ethereum discv4-dns-lists repository, filters nodes by EVM-compatible chain,
selects only those advertising the dominant (current-head) fork ID, ranks them
by score and recency, and writes clean JSON files of the top bootstrap peers
for use with your discv4 peer-discovery library.
The generator writes three kinds of JSON artifacts:
output/
fork_ids.json
chain_enodes.json
ethereum-mainnet.json
ethereum-sepolia.json
ethereum-holesky.json
ethereum-hoodi.json
bnb-smart-chain.json
bnb-smart-chain-testnet.json
polygon-mainnet.json
polygon-amoy.json
base-mainnet.json
base-sepolia.json
gnosis-chain.json
fork_ids.json is the fork metadata source of truth. Each top-level key is a
configured chain name. Fork IDs are EIP-2124 tuples: forkId is the CRC32 fork
hash and forkNext is the next fork block/timestamp encoded as lowercase hex,
or "0" when no future fork is configured.
{
"ethereum-mainnet": {
"chainId": 1,
"genesisHex": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3",
"current": {
"forkId": "07c9462e",
"forkNext": "0"
},
"upcoming": {
"at": "695db057",
"forkId": "12345678",
"forkNext": "0"
},
"source": "https://raw.githubusercontent.com/ethereum/go-ethereum/master/params/config.go",
"generatedAt": "2026-05-15T00:00:00Z",
"forks": [
{ "forkId": "fc64ec04", "forkNext": "118c30" },
{ "forkId": "07c9462e", "forkNext": "0" }
]
}
}Fields:
| Field | Description |
|---|---|
chainId |
EVM chain ID. |
genesisHex |
32-byte genesis hash used as the fork ID CRC32 seed. |
current |
Current fork tuple used for node filtering and output metadata. |
upcoming |
Optional scheduled next fork metadata. Present when current.forkNext != "0". |
upcoming.at |
The scheduled fork block/timestamp from current.forkNext. |
upcoming.forkId / upcoming.forkNext |
Optional post-fork tuple when it can be derived from the known schedule. |
source |
GitHub/config source used to derive the fork schedule. |
generatedAt |
UTC generation timestamp. |
forks |
Known fork tuple progression from the source, ordered by activation. |
Each chain also produces a file containing chain metadata plus nodes sorted by
score (descending) then lastResponse (most recent first), capped at topN
(default 100):
{
"networkId": 1,
"genesisHex": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3",
"forkId": "07c9462e",
"forkNext": "0",
"upcomingFork": {
"at": "695db057",
"forkId": "12345678",
"forkNext": "0",
"nodes": []
},
"nodes": [
{
"enr": "enr:-...",
"enode": "enode://<128-hex-pubkey>@162.55.86.114:30311",
"pubkey": "<128-hex-secp256k1-pubkey>",
"score": 3371,
"lastResponse": "2026-03-14T20:54:48Z",
"forkId": "07c9462e",
"forkNext": "0",
"ip": "162.55.86.114",
"port": 30311
}
]
}pubkey, enode, ip, and port are derived from the decoded ENR itself.
pubkey is the 64-byte uncompressed secp256k1 public key (hex, without 0x04).
port prefers the ENR TCP port and falls back to UDP only when TCP is absent.
forkId and forkNext at the chain level mirror fork_ids.json.current.
upcomingFork is omitted when no future fork is configured. When present,
upcomingFork.nodes contains peers already advertising the post-fork tuple.
If the schedule only identifies the activation point, upcomingFork.at is set
and nodes may be empty.
chain_enodes.json is the combined output consumed by clients. It maps each
configured chain name to the same schema as the per-chain {chain}.json files.
When signing is enabled, it also includes top-level signature and
signerAddress fields:
{
"ethereum-mainnet": {
"networkId": 1,
"genesisHex": "...",
"forkId": "07c9462e",
"forkNext": "0",
"nodes": []
},
"gnosis-chain": {
"networkId": 100,
"genesisHex": "...",
"forkId": "06000064",
"forkNext": "0",
"nodes": []
},
"signature": "0x<65-byte-secp256k1-signature>",
"signerAddress": "0x<ethereum-address>"
}A GitHub Actions workflow runs every 6 hours
but only regenerates output when all.json has actually changed (SHA-256
comparison against output/.state). It also triggers on pushes to main
that change source files, and can be run on-demand.
# Build
go build -o filter_nodes .
# Run (downloads all.json from the internet)
./filter_nodes
# Use a local copy of all.json (faster for development / testing)
./filter_nodes -input /path/to/all.json
# Discovery mode: print ranked fork-hash summary to identify unknown chains
./filter_nodes -discover -v
# Combine: discovery on a local file
./filter_nodes -input /path/to/all.json -discover -v| Strategy | Description |
|---|---|
geth_network |
Filters Ethereum-family ENRs against the current tuple from fork_ids.json. Supported networks: mainnet, sepolia, holesky, hoodi. |
enr_field |
Accepts nodes that carry a specific ENR key (e.g. bsc for BNB Smart Chain). |
fork_hash_list |
Accepts nodes whose eth fork hash is in the configured forkHashes list. |
bootnodes_yaml |
Loads a chain from an external YAML list of ENR bootnodes via sourceUrl. |
bootnodes_go |
Loads a chain from a named []string bootnode slice in an external Go source file via sourceUrl + sourceKey. |
Fork IDs are generated before node filtering and written to output/fork_ids.json.
The normal run then reloads that file and uses it for filtering and output
metadata. Current source providers:
| Provider | Chains | Source |
|---|---|---|
ethereum_geth |
Ethereum mainnet/testnets | ethereum/go-ethereum params/config.go from the configured forkSourceRef (currently master). |
bsc |
BNB Smart Chain mainnet/testnet | bnb-chain/bsc params/config.go from a pinned release tag. |
polygon_bor |
Polygon PoS / Amoy | 0xPolygon/bor params/config.go, including Bor-specific fork blocks. |
op_stack_superchain |
Base mainnet/sepolia | ethereum-optimism/superchain-registry superchain hardfork config. |
Providers compute the EIP-2124 tuple from genesis hash plus already-passed fork
blocks/timestamps. For block forks, the generator reads eth_blockNumber from
the configured rpcUrl; for timestamp forks it uses the current UTC time.
Set both enrField and forkHashes together with either "enr_field" or
"fork_hash_list" as filterType to require both conditions simultaneously.
This is used to distinguish BNB Smart Chain testnet (Chapel) from mainnet —
both advertise the bsc ENR key but at different fork hashes.
| Chain | Chain ID | Filter |
|---|---|---|
| Ethereum Mainnet | 1 | geth_network: mainnet |
| Ethereum Sepolia | 11155111 | geth_network: sepolia |
| Ethereum Holesky | 17000 | geth_network: holesky |
| Ethereum Hoodi | 560048 | geth_network: hoodi |
| BNB Smart Chain | 56 | enr_field: bsc |
| BNB Smart Chain Testnet | 97 | enr_field: bsc + fork_hash_list (compound) |
| Polygon PoS Mainnet | 137 | bootnodes_enrtree + explicit bootnodes_go |
| Polygon Amoy Testnet | 80002 | bootnodes_enrtree + explicit bootnodes_go |
| Base Mainnet | 8453 | bootnodes_go |
| Base Sepolia Testnet | 84532 | bootnodes_go |
| Gnosis Chain (xDai) | 100 | bootnodes_go |
- Edit
chains_config.json. - Run
./filter_nodes -discover(or-input+-discover) to print a ranked table of all fork hashes observed in the crawl. Chain-specific ENR keys (e.g.bsc(2709)) help identify which hash belongs to which chain. - For
fork_hash_list, cross-reference the top hashes with the chain's release notes. - Add the hash(es) to the chain's
forkHashesarray, or forbootnodes_yaml/bootnodes_goset the external source fields. - Re-run to verify results.
| Chain | Fork | Hash | Timestamp / Block |
|---|---|---|---|
| Mainnet | Cancun | 9f3d2254 |
1710338135 |
| Mainnet | Prague | c376cf8b |
1746612311 |
| Mainnet | Osaka | 5167e2a6 |
1764798551 |
| Mainnet | BPO1 | cba2a1c0 |
1765290071 |
| Mainnet | BPO2 | 07c9462e |
1767747671 (current) |
| Sepolia | Cancun | 88cf81d9 |
1706655072 |
| Sepolia | BPO2 | 268956b6 |
current |
| Holesky | Cancun | 9b192ad0 |
1707305664 |
| Holesky | BPO2 | 9bc6cb31 |
current |
| Hoodi | Prague | 0929e24e |
1742999832 (current) |
github.com/ethereum/go-ethereum— for ENR decoding (p2p/enode,p2p/enr), fork ID filtering (core/forkid), and chain configurations (params).
The chain_enodes.json file can be cryptographically signed to prove you generated it.
Use ECDSA (secp256k1, Ethereum-compatible) signing with a private key.
Quick Start:
-
Generate a signing key:
./generate_signing_key.sh
-
Export the key and run:
export SIGNING_KEY='<base64-encoded-key>' ./filter_nodes
-
The
output/chain_enodes.jsonwill now include:signature: ECDSA signature over the entire JSON documentsignerAddress: Ethereum address of the signer
For full setup and verification instructions, see SIGNING.md.