eth: don't read a blob pool that is still initialising (#35509)
Some checks are pending
/ Linux Build (arm) (push) Waiting to run
/ Keeper Build (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run
/ Linux Build (push) Waiting to run

`BlobPool.Init` builds the lookup, index and store without holding
`p.lock`, and `eth/backend.go` publishes the pool to `blobpool.NewCache`
before `txpool.New` runs Init. Cache.update spawns a goroutine calling
`getByVhash`, so it can read the pool mid-construction:

```
    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x28]
    blobpool.(*BlobPool).getByVhash(...)  blobpool.go:1303
    blobpool.(*Cache).update.func1()      cache.go:429
```

`Init` fills the lookup inside `billy.Open`, whose index callback runs
`parseTransaction` -> `trackTransaction`, and assigns `p.store` only
once `billy.Open` returns. For that window a lookup hit names a
transaction whose store does not exist yet, and `p.store.Get`
dereferences nil.

Build the cache after `txpool.New` so no reader exists while `Init` runs
-- `getByVhash` has exactly one caller -- and have `getByVhash` read the
store under the same lock as the lookup, returning nil when it is not
set.

A benchmark harness that restarts the client once per test hit this on
1-4% of starts; a node that starts once rarely will.
This commit is contained in:
Jochem Brouwer 2026-08-13 13:26:11 +02:00 committed by GitHub
parent 35fe1bf69c
commit 393555b097
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -329,12 +329,14 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
config.BlobPool.Datadir = stack.ResolvePath(config.BlobPool.Datadir)
}
eth.blobTxPool = blobpool.New(config.BlobPool, eth.blockchain, legacyPool.HasPendingAuth)
eth.blobCache = blobpool.NewCache(eth.blobTxPool)
eth.txPool, err = txpool.New(config.TxPool.PriceLimit, eth.blockchain, []txpool.SubPool{legacyPool, eth.blobTxPool})
if err != nil {
return nil, err
}
// Only after txpool.New has run the pool's Init: the cache reads the pool's
// lookup and store, which Init builds without holding the pool lock.
eth.blobCache = blobpool.NewCache(eth.blobTxPool)
if !config.TxPool.NoLocals {
rejournal := config.TxPool.Rejournal