Configuration¶
UMNode is configured from two places that compose:
- A per-network YAML file (default
cfg/ethereum.yaml) describing each chain UMNode can join. - CLI flags that override file values at runtime.
Settings precedence¶
Every runtime setting can come from three sources. They resolve in this order:
flowchart LR
A[Explicit CLI flag] -->|wins if set| R[Effective value]
B[Per-network YAML value] -->|used if flag not set| R
C[Built-in flag default] -->|fallback| R
- An explicitly-set command-line flag always wins.
- Otherwise the per-network YAML value is used, if present.
- Otherwise the flag's built-in default applies.
In the code (cmd/root.go) this is the resolve* family of helpers: a config
value is used only when the flag was not changed on the command line.
The config file¶
Networks are described in a YAML file, keyed by network name. Select one at
runtime with --network <name>.
mainnet:
NetworkId: 1
# BootstrapNodes seed Kademlia discovery only; they are not kept as peers.
BootstrapNodes:
- "enode://d860a01f…@18.138.108.67:30303"
- "enode://22a8232c…@3.209.45.79:30303"
# StaticNodes are always dialed and reconnected; count against --max-peers.
# StaticNodes: []
# TrustedNodes are like static but bypass the max-peers cap.
# TrustedNodes: []
GenesisHash: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
GenesisTime: 0
# Post-merge total difficulty, frozen at the merge. Without it we look like a
# zero-state peer and get dropped immediately after the handshake.
TotalDifficulty: "58750003716598352816469"
# Optional per-network runtime settings (override the flag defaults). An
# explicit flag on the command line still wins. Durations are strings.
MaxPeers: 20
DiscoveryV4: true
DiscoveryV5: true
# NoDiscovery: false
# DropFaultyPeers: true # disconnect peers that repeatedly serve undecodable/invalid data
# RequireForkMatch: false # reject peers whose fork hash differs from ours at the handshake (strict; opt-in)
# RequireServedRange: false # only ask an eth/69+ peer for a block its advertised range covers (opt-in)
StorageReportEvery: "10s"
# HeadPollEvery: "4s"
# Realtime: true # write upcoming blocks to the store (needs a block store + head polling)
# Backfill: true # fill chain history (needs a block store)
# BackfillForwardDirection: true # fill forward (genesis-first, in-order); false = reverse (head-down)
# BackfillReportEvery: "10s" # how often to log backfill progress (fill %, import rate); 0 disables
# RequestTimeout: "15s" # per-peer timeout for header/body/receipt requests
# FetchBatch: 128 # blocks per header/body/receipt request batch
# FetchConcurrency: 16 # max parallel fetch batches (scales to peer count up to this cap)
# FetchRetries: 3 # fetch attempts per batch before giving up
# MaxCatchUp: 4096 # blocks filled per catch-up chunk (the worker loops to the head)
# RPCPeers: # JSON-RPC endpoints of existing nodes to fetch block data from,
# - "http://127.0.0.1:8545" # tried before the p2p peers; each must serve this genesis
# RPCPeerConcurrency: 4 # requests in flight per RPC peer
# RPCPeerBatch: 16 # blocks per bodies/receipts request to an RPC peer
# Block-store layout. SegmentSize, BlockGroupSize and PendingBackend are read
# ONLY when the database is first created and are then LOCKED forever (see
# Block store). Compression, CompressionLevel and SealCompressionLevel may be
# retuned anytime; they affect only new segments (every segment names its codec). SealConfirmations is a
# runtime policy and may change between runs.
# SegmentSize: 50000
# BlockGroupSize: 64 # blocks per group; larger = smaller store, slower random single-block reads
# Compression: "s2"
# CompressionLevel: 1 # block-IMPORT level only (re-packed at seal); lower = faster import
# SealCompressionLevel: 6 # FINAL sealed size; 0 = inherit CompressionLevel; 6-9 = better tier (the default), 10+ = best
# PendingBackend: "pebble"
# SealConfirmations: 256
# SealProgressEvery: "30s" # how often the background seal re-pack logs progress (0 silences)
# Index: true # block-hash / tx-hash index for eth_getBlockByHash / …ByHash
# IndexKeyBytes: 8 # leading hash bytes per index key (4..32; 32 = whole hash); LOCKED at creation
# ReindexReportEvery: "30s" # how often the background reindexer logs progress (fill %, rate, ETA); 0 silences
# Sync mode and segment archive (see Segment archive). SyncMode "snap" (the
# default) restores the archive's newest state snapshot into an empty state
# store and executes on from its head, downloading only the blocks the node
# keeps; "full" builds the state from genesis. ArchiveSource is the archive
# the node downloads from — sealed block segments and the snapshot — as an
# http(s)://, s3://, r2:// or file:// root used as given; empty = the public
# archive, "none" = peers only. Backup backs up every sealed segment and the
# state snapshots to an s3://bucket[/prefix], r2://bucket[/prefix] (Cloudflare
# R2) or file:///dir root as they seal; empty backs up nothing (ArchiveUpload,
# its former name, is still read). S3 credentials come from the AWS
# environment variables, R2 credentials from R2_ACCESS_KEY_ID /
# R2_SECRET_ACCESS_KEY (else the AWS ones).
# SyncMode: "snap"
# ArchiveSource: "" # empty = https://umn.zhurko.de/<network id>-<genesis prefix>
# Backup: "s3://my-bucket/umnode/mainnet"
# S3Endpoint: "" # e.g. "http://minio.local:9000"; empty = AWS S3
# S3Region: "" # empty = AWS_REGION, else us-east-1
# S3PathStyle: true # default: on with S3Endpoint, off for AWS
# R2AccountID: "" # Cloudflare account id for r2:// URLs; empty = R2_ACCOUNT_ID
# R2Jurisdiction: "" # "eu" or "fedramp" for buckets created in that jurisdiction
# Fork schedule (EIP-2124). Pre-merge forks use `block`; post-merge use `time`.
Forks:
- { name: Homestead, block: 1150000 }
- { name: London, block: 12965000 }
- { name: Shanghai, time: 1681338455 }
- { name: Cancun, time: 1710338135 }
- { name: Prague, time: 1746612311 }
UMNode refuses to start if the named network is missing, or if it has no source to start from: no p2p node (bootstrap, static or trusted) and no RPC peer.
Bundled networks¶
The shipped cfg/ethereum.yaml describes Ethereum mainnet, three Ethereum
testnets, and BNB Smart Chain mainnet and testnet.
Every store under data/ is bound to the genesis it was created with, so run
each network with its own directories (--block-db, --state-db, --node-db,
--inventory-db) rather than the shared defaults; UMNode refuses to open a
store that belongs to another chain.
| Network | --network |
Chain id | Genesis / allocation | Fork schedule |
|---|---|---|---|---|
| Ethereum mainnet | mainnet |
1 | built into go-ethereum | in the YAML |
| Sepolia | sepolia |
11155111 | built into go-ethereum | in the YAML (the post-merge MergeNetsplit block fork plus the timestamp forks) |
| Hoodi | hoodi |
560048 | built into go-ethereum | in the YAML (merged at genesis; only the post-Cancun timestamp forks) |
| Ephemery | ephemery |
39438000 + iteration | cfg/ephemery/genesis.json |
derived from the genesis file |
| BNB Smart Chain mainnet | bnb-mainnet |
56 | cfg/bnb/mainnet.json |
derived from the genesis file, BSC forks included |
| BNB Smart Chain testnet (Chapel) | bnb-testnet |
97 | cfg/bnb/testnet.json |
derived from the genesis file, BSC forks included |
Sepolia past Glamsterdam (Amsterdam, 2026-10-06 13:53:36 UTC)
The Sepolia schedule includes the Amsterdam fork (1791294816), so the node
advertises the post-fork fork ID (0x6c1d9423) that upgraded peers require;
without it, only stale pre-fork nodes accept the connection and the head
stays at the last pre-fork block. Syncing blocks past the fork works, but
executing them needs the Amsterdam rules (block-level access lists per
EIP-7928, the EIP-8037 gas accounting), which the executor does not
implement yet: it refuses the first Amsterdam block with an explicit error
and execution parks there. The five bundled EF Sepolia bootnodes no longer
answer discovery either; peers come from the all.sepolia.ethdisco.net
DNS node list, on by default.
Ephemery¶
Ephemery resets to a fresh genesis every 28 days. Each
iteration has a new chain id, genesis hash, genesis time and fork schedule, so
the config entry pins none of them — only the long-lived bootnodes and a
GenesisFile — and the identity is derived from that file when the network is
selected (see GenesisFile). The bundled file is the
iteration current when this version shipped; the chain id and genesis hash in
use are logged at start (Starting umnode … networkId=… genesis=…).
After a reset:
make ephemery-genesisdownloads the latest release'sgenesis.jsonfrom ephemery-testnet/ephemery-genesis intocfg/ephemery/(setEPHEMERY_GENESIS_URLto fetch a specific release's asset instead).- Start from empty data directories: the block, state and p2p stores are bound to the previous iteration's genesis and UMNode refuses to reuse them.
BNB Smart Chain¶
The two BNB networks are Parlia chains (Consensus: parlia, detected from the
genesis file). Their identity, fork schedule — bsc's own forks included — and
total difficulty come from the bundled genesis files, which carry bsc's full
fork schedule. See BNB Smart Chain for the wire and execution
differences, the BNB Beacon Chain bridge validators included.
Field reference¶
Identity & chain¶
| Key | Type | Meaning |
|---|---|---|
NetworkId |
int | The chain's network id, checked during the eth handshake. Derived from the chain id of GenesisFile when unset. |
GenesisHash |
hex string | Genesis block hash; binds the block store to one chain. Derived from GenesisFile when unset; one of the two is required. |
GenesisTime |
int | Genesis timestamp (seconds). Derived from GenesisFile when unset. |
TotalDifficulty |
string | Total difficulty advertised in the eth/68 handshake: the terminal PoW block's for a merged chain (a big integer, as a string). For a chain merged at genesis it is the genesis difficulty, and is derived from GenesisFile when unset. |
Consensus |
string | The chain's consensus family: parlia for BNB Smart Chain, which selects bsc's wire handshakes and the Parlia execution rules (BNB Smart Chain); empty (the default) is Ethereum. Set automatically when GenesisFile carries a parlia engine section. |
GenesisFile |
path | geth-format genesis.json (chain config + allocation) for the execution subsystem. Networks with a built-in genesis — mainnet, sepolia, hoodi, matched by GenesisHash — may omit it. The file must hash to GenesisHash. Without any genesis definition, execution (on by default) disables itself with a warning unless --execute was passed explicitly. When set, it also completes whatever identity the YAML leaves out — NetworkId, GenesisHash, GenesisTime, Forks and (merged-at-genesis chains) TotalDifficulty — so a chain can be described by its genesis file and bootnodes alone; explicit values win. Relative paths resolve against the working directory. |
Peer sources¶
| Key | Type | Meaning |
|---|---|---|
BootstrapNodes |
list of enode URLs | Seed nodes for Kademlia discovery. Not kept as peers. |
DNSDiscovery |
list of enrtree:// URLs |
EIP-1459 node lists peers are discovered from alongside the DHT. Absent: go-ethereum's known tree for the chain (mainnet, Sepolia, Hoodi — all.<network>.ethdisco.net), none for other chains. Empty list: DNS discovery off for the network. --dns-discovery=false turns it off for a run. |
StaticNodes |
list of enode URLs | Always dialed and reconnected; count against MaxPeers. |
TrustedNodes |
list of enode URLs | Like static, but bypass the MaxPeers cap. |
RPCPeers |
list of JSON-RPC endpoints | Existing nodes — synced or partially synced — to fetch block data from over JSON-RPC (http(s)://, ws(s):// or an IPC path), tried before the p2p peers. Not p2p peers: they never count against MaxPeers. See RPC peers. |
A chain reachable only via static peers (no DHT) would set NoDiscovery: true
with StaticNodes populated and no BootstrapNodes. A network with RPCPeers
and no p2p nodes at all runs from those endpoints alone.
Fork schedule¶
Forks is a list of activations driving EIP-2124 fork-id computation. Each entry
sets exactly one of:
block:— pre-merge forks, activated at a block height.time:— post-merge forks, activated at a timestamp.
The Merge (Paris) activates by total difficulty, not a block or time, so it does
not contribute to the fork id and is omitted from the list. Forks active from
genesis — block 0, or a time at or before the genesis timestamp — are part of
the genesis ruleset and contribute nothing either; leave them out (Sepolia's
pre-London forks, everything through Cancun on Hoodi).
When Forks is absent and GenesisFile is set, the schedule is derived from
the file's chain config with exactly go-ethereum's rules (every …Block and
…Time activation, genesis-ruleset forks dropped, shared activations folded),
so the fork id matches what peers of that chain compute. On a Parlia chain the
derivation reads the raw config, so BSC's own forks — which go-ethereum's chain
config has no fields for — are folded in as bsc folds them.
Optional runtime settings¶
These mirror CLI flags and may be set per-network. A nil/absent value means
"unset", so the resolver falls back to the flag default.
| Key | Flag equivalent | Default |
|---|---|---|
MaxPeers |
--max-peers |
30 |
DiscoveryV4 |
--discovery-v4 |
true |
DiscoveryV5 |
--discovery-v5 |
false |
NoDiscovery |
--no-discovery |
false |
DropFaultyPeers |
--drop-faulty-peers |
true |
RequireForkMatch |
--require-fork-match |
false |
RequireServedRange |
--require-served-range |
false |
StorageReportEvery |
--storage-report-every |
"10s" |
HeadPollEvery |
--head-poll-every |
"4s" |
Realtime |
--realtime |
true |
Backfill |
--backfill |
true |
BackfillForwardDirection |
--backfill-forward-direction |
true |
BackfillReportEvery |
--backfill-report-every |
"10s" |
RequestTimeout |
--request-timeout |
"15s" |
FetchBatch |
--fetch-batch |
128 |
FetchConcurrency |
--fetch-concurrency |
16 |
FetchRetries |
--fetch-retries |
3 |
MaxCatchUp |
--max-catch-up |
4096 |
RPCPeers |
--rpc-peers |
(none) |
RPCPeerConcurrency |
--rpc-peer-concurrency |
4 |
RPCPeerBatch |
--rpc-peer-batch |
16 |
CompactionEvery |
--compaction-interval |
"1h" |
SegmentSize |
--segment-size |
50000 |
BlockGroupSize |
--block-group-size |
64 |
Compression |
--compression |
"s2" |
CompressionLevel |
--compression-level |
1 |
SealCompressionLevel |
--seal-compression-level |
6 |
PendingBackend |
--pending-db |
"pebble" |
SealConfirmations |
--seal-confirmations |
256 |
Index |
--index |
true |
IndexKeyBytes |
--index-key-bytes |
8 |
ReindexReportEvery |
--reindex-report-every |
"30s" |
SealProgressEvery |
--seal-progress-every |
"30s" |
SyncMode |
--syncmode |
"snap" |
ArchiveSource |
--archive-source |
(public archive) |
Backup (formerly ArchiveUpload) |
--backup |
(none) |
S3Endpoint |
--s3-endpoint |
(AWS) |
S3Region |
--s3-region |
(env, else us-east-1) |
S3PathStyle |
--s3-path-style |
auto |
R2AccountID |
--r2-account-id |
(env) |
R2Jurisdiction |
--r2-jurisdiction |
(none) |
Four block-store settings are locked at creation
SegmentSize, BlockGroupSize, Compression and PendingBackend are read
only when the database is first created, recorded in its MANIFEST, and
cannot change afterward — UMNode refuses to start if a later run requests
different values. Choose them before the first run. SealConfirmations, by
contrast, is a runtime policy and may change freely between runs. See
Block store.
Durations are YAML strings such as "4s" or "10s"; "0s" disables the
relevant periodic feature.
See CLI flags for the flag side of each setting.