Bor Deadlock Issue
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 1.1k
- Forks
- 606
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 38
Description
System information
- Bor client version:
v2.9.0(GitCommitad85c473567f411d7ddfd4739ef314064b2fdeb6, Go 1.26.3;bor versionreportsbor/matic-bor/v2.9.0/linux-amd64/go1.26.3) - Heimdall client version:
v0.9.0(Heimdall V2) - OS & Version: Linux (Ubuntu), running as an unprivileged LXC container on Proxmox VE (host kernel
7.0.6-2-pve) - Environment: Polygon Mainnet (ChainID 137)
- Type of node: Validator
- Additional Information:
syncmode=stateless,"db.engine"="pebble","state.scheme"="path",cache=8192,maxpeers=80. Note: the binary is built from a private fork (stack-trace paths read/home/runner/work/bor-private/bor-private/...); the affected code paths (eth/downloader,triedb/pathdb) are go-ethereum-derived.
Overview of the problem
A mainnet validator running Bor v2.9.0 in syncmode=stateless intermittently deadlocks its sync subsystem: the block head freezes, no new blocks are imported for hours, the process stays alive with 0% CPU (wchan=futex_do_wait) and RPC still responsive, and it does not self-recover — only a process restart clears it. On this node it recurs roughly every ~30 min–3 h of uptime, across multiple process lifetimes (not a one-off corrupt state).
The freeze correlates with a mode=stateless peer-synchronization timeout followed by a peer drop. A SIGQUIT goroutine dump captured during the wedge (taken twice, in two independent occurrences — same result) shows the three sync movers permanently parked: the header skeleton (eth/downloader/skeleton.go:259, select), the state fetcher (eth/downloader/statesync.go:48, select), and the pathdb trie generator (triedb/pathdb/generate.go:828, chan receive, starved).
Expected behaviour: after a peer-sync timeout Bor should drop the peer and continue syncing from other peers (it has ~79). Actual behaviour: the sync coordinator wedges and the head stays frozen until a restart.
Downstream effect: because the head is frozen, Bor cannot serve the block referenced by a checkpoint vote extension with 512 confirmations, so Heimdall logs bor block not found locally: end=… confirmations=512 target=… and the validator misses checkpoints until Bor is restarted and re-synced.
Reproduction Steps
- Run Bor
v2.9.0on mainnet withsyncmode=stateless(config excerpt below), behind a healthy Heimdallv0.9.0. - After some uptime (~30 min–3 h), observe a stateless peer-sync failure in the logs:
WARN Synchronisation failed, dropping peer err=timeout mode=stateless. - Shortly after,
eth_blockNumberstops advancing while the chain moves on;eth_syncingreports acurrentBlockthat never changes and ahighestBlockthat keeps creeping; CPU drops to 0%; the journal shows only aFetching state sync events from Heimdallpoll loop andWhitelisting milestone deferred err="chain out of sync"every ~2 s. - The node never recovers on its own.
systemctl restart bor(or a SIGQUIT-dump + restart) restores sync; the deadlock then recurs after the next uptime interval.
Logs / Traces / Output / Error Messages
Trigger (same minute the head starts lagging):
WARN [..] Network response id collision have=…eth/69:3 want=…eth/69:4
WARN [..] Synchronisation failed, dropping peer peer=a7a27f… err=timeout mode=stateless
Stuck state after the freeze (repeats indefinitely, no Imported lines):
INFO [..] Fetching state updates from Heimdall fromID=3,179,029 ...
INFO [..] Fetching state sync events queryParams="from_id=...&to_time=...&pagination.limit=50"
INFO [..] Whitelisting milestone deferred err="chain out of sync"
Goroutine dump (captured via systemctl kill -s QUIT while wedged; full dumps linked below) — the sync pipeline is parked for the entire stuck duration:
goroutine 469 [select, 218 minutes]:
runtime.selectgo
github.com/ethereum/go-ethereum/eth/downloader.(*skeleton).startup // skeleton.go:259
github.com/ethereum/go-ethereum/eth/downloader.newSkeleton.gowrap1 // skeleton.go:241
goroutine 470 [select, 201 minutes]:
github.com/ethereum/go-ethereum/eth/downloader.(*Downloader).stateFetcher // statesync.go:48
github.com/ethereum/go-ethereum/eth/downloader.New.gowrap1
goroutine 422 [chan receive, 218 minutes]:
runtime.chanrecv1
github.com/ethereum/go-ethereum/triedb/pathdb.(*generator).generate // generate.go:828
github.com/ethereum/go-ethereum/triedb/pathdb.(*generator).run.gowrap1 // generate.go:137
Full goroutine dumps (838 goroutines / ~15k lines each, captured in two independent occurrences — identical deadlock site):
- Dump 1 (2026-06-29T17:37Z): <PASTE GIST URL — upload
bor-2.9.0-stateless-deadlock-goroutines.txt> - Dump 2 (2026-06-29T18:40Z): <PASTE GIST URL — upload
dump-2026-06-29T18:40:01Z.txt>
Additional Information
-
bor.service(no modifications; standard):[Unit] Description=bor StartLimitIntervalSec=500 StartLimitBurst=5 [Service] Restart=on-failure RestartSec=5s ExecStart=/usr/bin/bor server -config "/var/lib/bor/config.toml" Type=simple KillSignal=SIGINT User=bor TimeoutStopSec=300 [Install] WantedBy=multi-user.targetRelevant
config.toml(secrets redacted):chain = "mainnet" syncmode = "stateless" "db.engine" = "pebble" "state.scheme" = "path" [cache] cache = 8192 [p2p] maxpeers = 80 maxpendpeers = 20 port = 30303 nodiscover = false nodekeyhex = "<REDACTED>" [p2p.discovery] dns = ["enrtree://AKUEZKN7PSKVNR65FZDHECMKOJQSGPARGTPPBI7WS2VUL4EGR6XPC@pos.polygon-peers.io"] bootnodes = [ <standard Polygon mainnet bootnodes> ] [heimdall] url = "http://<HEIMDALL_HOST>:1317" [jsonrpc.http] api = ["eth", "net", "web3", "txpool", "bor", "admin"]Chain config: mainnet, ChainID 137, Engine
bor. -
Syncing/peers/node info (IPs/IDs redacted):
maxpeers= 80;admin.peers.length≈ 79 (stable, healthy peer count throughout the stall).admin.nodeInfo:name: bor/matic-bor/v2.9.0/linux-amd64/go1.26.3 enode: enode://821e5bb017d4a2b37b056c…<ID redacted>@[<IP REDACTED>]:30303 listenAddr: [::]:30303 ports: discovery=30303, listener=30303 protocols: eth, snap, witeth.syncingwhile wedged (representative;currentBlockfrozen,highestBlockadvancing, no heal progress):{"currentBlock":"0x5535fce","startingBlock":"0x5535fce","highestBlock":"0x5536a3c", "healedTrienodes":"0x32c72831","healingTrienodes":"0x0","stateIndexRemaining":"0x0", ...}currentBlock(0x5535fce) does not change across repeated calls;highestBlockcreeps upward as the chain advances.
-
Heimdall config (
/var/lib/heimdall/config/app.toml, relevant lines, keys redacted):eth_rpc_url = "https://mainnet.infura.io/v3/<REDACTED>" bor_rpc_url = "http://<BOR_HOST>:8545" # local Bor bor_grpc_flag = "false" comet_bft_rpc_url = "http://0.0.0.0:26657"Heimdall is healthy throughout (
latest_block_timefresh within ~5 s,catching_up=false); all heimdall services (heimdalld, bridge, rest-server) running. The communication Bor→Heimdall works (Bor keeps fetching state-sync events); the failure is internal to Bor's sync coordinator. -
CURL: N/A — not an RPC-request bug. The behaviour is observable via
eth_blockNumber(frozen) /eth_syncing(non-advancingcurrentBlock) over time.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the stateless-sync stall on Bor v2.9.0 and inspect the synchronization paths named in eth/downloader/skeleton.go, eth/downloader/statesync.go, and triedb/pathdb/generate.go. Trace the peer-timeout and coordinator handoff; done means the node resumes importing blocks from another peer without a restart and the head continues advancing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend, distributed-systems, networking
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100