work around billy slotter interface limitations

This commit is contained in:
Csaba Kiraly 2026-03-06 10:36:45 +01:00
parent 4daf354b2c
commit f8a0b6fa1c
No known key found for this signature in database
GPG key ID: 0FE274EE8C95166E
2 changed files with 53 additions and 20 deletions

View file

@ -346,10 +346,10 @@ type BlobPool struct {
reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools
hasPendingAuth func(common.Address) bool // Determine whether the specified address has a pending 7702-auth
store billy.Database // Persistent data store for the tx metadata and blobs
newSlotter func() billy.SlotSizeFn // Factory to create fresh slotter instances for slot size lookups
stored uint64 // Useful data size of all transactions on disk
limbo *limbo // Persistent data store for the non-finalized blobs
store billy.Database // Persistent data store for the tx metadata and blobs
slotter slotSizer // O(1) slot size calculator matching the active billy shelves
stored uint64 // Useful data size of all transactions on disk
limbo *limbo // Persistent data store for the non-finalized blobs
gapped map[common.Address][]*types.Transaction // Transactions that are currently gapped (nonce too high)
gappedSource map[common.Hash]common.Address // Source of gapped transactions to allow rechecking on inclusion
@ -437,16 +437,19 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
// Create new slotter for pre-Osaka blob configuration.
slotter := newSlotter(params.BlobTxMaxBlobs)
p.newSlotter = func() billy.SlotSizeFn { return newSlotter(params.BlobTxMaxBlobs) }
// See if we need to migrate the queue blob store after fusaka
slotter, err = tryMigrate(p.chain.Config(), slotter, queuedir)
if err != nil {
return err
}
// Update the slotter factory if Osaka is active
// Build an O(1) slot size calculator from the active slotter configuration.
// We need a fresh slotter instance since tryMigrate may have consumed the
// previous one, and billy.Open below will consume this one.
if p.chain.Config().OsakaTime != nil {
p.newSlotter = func() billy.SlotSizeFn { return newSlotterEIP7594(params.BlobTxMaxBlobs) }
p.slotter = newSlotSizer(newSlotterEIP7594(params.BlobTxMaxBlobs))
} else {
p.slotter = newSlotSizer(newSlotter(params.BlobTxMaxBlobs))
}
// Index all transactions on disk and delete anything unprocessable
var fails []uint64
@ -1593,7 +1596,7 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
// Create meta, in preparation of adding to the pool.
// Having the meta simplifies the check below for underpriced transactions.
// Note: the meta will be finalized with storage information after the transaction is stored
// Note: the meta will be finalized with storage information after the transaction is stored.
meta := newBlobTxMeta(tx)
// Calculate the eviction parameters for the transaction
@ -1627,9 +1630,9 @@ func (p *BlobPool) addLocked(tx *types.Transaction, checkGapped bool) (err error
log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
return err
}
newStorageSize, err := getSlotSize(p.newSlotter(), uint32(len(blob)))
newStorageSize, err := p.slotter.getSlotSize(uint32(len(blob)))
if err != nil {
// This should also not happen at this stage
// This should never happen, but better safe than sorry.
log.Warn("Dropping blob transaction due to size", "tx", tx.Hash(), "size", meta.size, "err", err)
return err
}

View file

@ -23,17 +23,47 @@ import (
"github.com/holiman/billy"
)
// getSlotSize return the storage size for a given transaction size based on the current slotter.
func getSlotSize(slotter billy.SlotSizeFn, size uint32) (uint32, error) {
for {
slotSize, done := slotter()
if size <= slotSize {
return slotSize, nil
}
if done {
return size, errors.New("size exceeds maximum slot size")
}
// slotSizer computes the storage shelf size for a given transaction size using
// O(1) arithmetic. Shelf sizes form an arithmetic sequence:
//
// base, base+step, base+2*step, ...
//
// This mirrors the progression in newSlotter and newSlotterEIP7594, but avoids
// creating and iterating a stateful closure on every lookup.
type slotSizer struct {
base uint32 // Size of the first shelf (txAvgSize)
step uint32 // Size increment per subsequent shelf
max uint32 // Largest valid shelf size
}
// newSlotSizer creates a slotSizer by consuming a slotter closure once to
// discover its base size, step size, and maximum shelf size.
func newSlotSizer(slotter billy.SlotSizeFn) slotSizer {
first, done := slotter()
if done {
return slotSizer{base: first, step: 0, max: first}
}
second, done := slotter()
step := second - first
last := second
for !done {
last, done = slotter()
}
return slotSizer{base: first, step: step, max: last}
}
// getSlotSize returns the shelf size that can store a transaction of the given
// byte size, or an error if it exceeds the largest shelf.
func (s slotSizer) getSlotSize(size uint32) (uint32, error) {
if size <= s.base {
return s.base, nil
}
// Round up to the nearest shelf: base + ⌈(size-base)/step⌉ * step
slot := s.base + ((size-s.base+s.step-1)/s.step)*s.step
if slot > s.max {
return 0, errors.New("size exceeds maximum slot size")
}
return slot, nil
}
// tryMigrate checks if the billy needs to be migrated and migrates if needed.