miner: supply a slot number when synthesising pending block post-Amsterdam

getPending builds the pending block on demand via generateWork, but after
EIP-7843 (#33589) prepareWork rejects the call unless generateParams.slotNum
is non-nil once Amsterdam is active:

    if miner.chainConfig.IsAmsterdam(header.Number, header.Time) {
        if genParams.slotNum == nil {
            return nil, errors.New("no slot number set post-amsterdam")
        }
        header.SlotNumber = genParams.slotNum
    }

getPending never populated slotNum, so on any Amsterdam-activated chain
eth_getBalance(addr, "pending") (and every other RPC that resolves through
the pending state) fails with "pending state is not available", breaking
faucets and nonce trackers that poll pending.

Fix by synthesising a slot number from the parent header when available,
mirroring how the Shanghai branch above already conditionally populates
withdrawals. The pending block is empty post-merge so the exact slot value
is not user-visible; SlotNumber+1 (or zero) is sufficient to satisfy the
prepareWork invariant.

Observed on a bal-devnet-3 Geth build: every eth_getBalance(...,"pending")
returned -32000 "pending state is not available" until the faucet was
repointed at a non-Geth EL. Other clients (Nethermind, Besu, Reth, Erigon,
Ethrex) serve pending on the same chain without issue.
This commit is contained in:
Barnabas Busa 2026-04-22 14:00:35 +02:00 committed by MariusVanDerWijden
parent 4e6c9ec825
commit 73b84bb822

View file

@ -151,12 +151,24 @@ func (miner *Miner) getPending() *newPayloadResult {
return cached
}
var (
timestamp = uint64(time.Now().Unix())
withdrawal types.Withdrawals
timestamp = uint64(time.Now().Unix())
childNumber = new(big.Int).Add(header.Number, big.NewInt(1))
withdrawal types.Withdrawals
slotNum *uint64
)
if miner.chainConfig.IsShanghai(new(big.Int).Add(header.Number, big.NewInt(1)), timestamp) {
if miner.chainConfig.IsShanghai(childNumber, timestamp) {
withdrawal = []*types.Withdrawal{}
}
// Post-Amsterdam, prepareWork requires a slot number (EIP-7843). The pending
// block is synthetic and has no canonical slot, so derive one from the parent
// when available and fall back to zero otherwise.
if miner.chainConfig.IsAmsterdam(childNumber, timestamp) {
var n uint64
if header.SlotNumber != nil {
n = *header.SlotNumber + 1
}
slotNum = &n
}
ret := miner.generateWork(context.Background(),
&generateParams{
timestamp: timestamp,
@ -166,6 +178,7 @@ func (miner *Miner) getPending() *newPayloadResult {
random: common.Hash{},
withdrawals: withdrawal,
beaconRoot: nil,
slotNum: slotNum,
noTxs: false,
}, false) // we will never make a witness for a pending block
if ret.err != nil {