Skip to content

Configuration

UMNode is configured from two places that compose:

  1. A per-network YAML file (default cfg/ethereum.yaml) describing each chain UMNode can join.
  2. 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:

  1. make ephemery-genesis downloads the latest release's genesis.json from ephemery-testnet/ephemery-genesis into cfg/ephemery/ (set EPHEMERY_GENESIS_URL to fetch a specific release's asset instead).
  2. 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.