Skip to content

core/txpool/blobpool: nil pointer dereference in Cache due to initialization race with BlobPool.Init #35508

Description

@aweneagle

Description:

BlobPool.Cache can panic with a nil pointer dereference on p.store due to a race between NewCache() starting its background goroutine and BlobPool.Init() setting p.store.

Root Cause:

In eth/backend.go, the initialization order is:

  eth.blobTxPool = blobpool.New(...)       // line 332: creates BlobPool, store is nil
  eth.blobCache = blobpool.NewCache(...)   // line 333: starts cache loop goroutine immediately
  eth.txPool, err = txpool.New(...)        // line 335: calls BlobPool.Init(), which sets p.store

Inside Init(), p.head is set (line 641) before p.store (line 673):

  p.head.Store(head)                                    // line 641: head is now non-nil
  // ...
  store, err := billy.Open(..., index)                  // line 669: scans disk, populates p.lookup/p.index via parseTransaction
  p.store = store                                       // line 673: store finally assigned

The cache goroutine started at line 333 calls triggerTopK() → selectTopTxs(), which has a guard:

  head := p.head.Load()
  if head == nil {
      return nil  // early exit
  }

This guard fails to protect once p.head is set at line 641. During billy.Open() (line 669), p.lookup and p.index are populated via the index callback → parseTransaction() → trackTransaction(), but p.store is
still nil. If selectTopTxs() runs in this window, it reads from p.index/p.lookup, returns vhashes, and update() spawns a goroutine that calls getByVhash() → p.store.Get() → panic.

Stack Trace:

  panic: runtime error: invalid memory address or nil pointer dereference                                                                                                  
  [signal SIGSEGV: segmentation violation code=0x1 addr=0x28 pc=0x1349b32]

  goroutine 11789 [running]:
  github.com/ethereum/go-ethereum/core/txpool/blobpool.(*BlobPool).getByVhash(...)
        core/txpool/blobpool/blobpool.go:1303 +0x192
  github.com/ethereum/go-ethereum/core/txpool/blobpool.(*Cache).update.func1()
        core/txpool/blobpool/cache.go:429 +0x1ca
  created by (*Cache).update in goroutine 11786
        core/txpool/blobpool/cache.go:415 +0x51e

Impact:

On nodes with existing blobpool data on disk, this race is highly reproducible. In our case, the node crashed 81 times in CrashLoopBackOff before one startup got lucky (empty Pending() result in the race
window). The more data on disk, the longer billy.Open() takes, and the wider the race window.

Possible Fix:

Move NewCache() to after txpool.New() returns (i.e., after Init() completes), or defer starting the cache loop until Init() signals readiness (e.g., add a Ready() channel that Init() closes after p.store is
set).

Affected Versions:

Confirmed present on glamsterdam-devnet-7 (HEAD bdf6e17) and glamsterdam-devnet-8. Also present on master.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions