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 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 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 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 slotter slotSizer // O(1) slot size calculator matching the active billy shelves
stored uint64 // Useful data size of all transactions on disk stored uint64 // Useful data size of all transactions on disk
limbo *limbo // Persistent data store for the non-finalized blobs limbo *limbo // Persistent data store for the non-finalized blobs
gapped map[common.Address][]*types.Transaction // Transactions that are currently gapped (nonce too high) 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 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. // Create new slotter for pre-Osaka blob configuration.
slotter := newSlotter(params.BlobTxMaxBlobs) 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 // See if we need to migrate the queue blob store after fusaka
slotter, err = tryMigrate(p.chain.Config(), slotter, queuedir) slotter, err = tryMigrate(p.chain.Config(), slotter, queuedir)
if err != nil { if err != nil {
return err 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 { 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 // Index all transactions on disk and delete anything unprocessable
var fails []uint64 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. // Create meta, in preparation of adding to the pool.
// Having the meta simplifies the check below for underpriced transactions. // 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) meta := newBlobTxMeta(tx)
// Calculate the eviction parameters for the transaction // 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) log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
return err return err
} }
newStorageSize, err := getSlotSize(p.newSlotter(), uint32(len(blob))) newStorageSize, err := p.slotter.getSlotSize(uint32(len(blob)))
if err != nil { 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) log.Warn("Dropping blob transaction due to size", "tx", tx.Hash(), "size", meta.size, "err", err)
return err return err
} }

View file

@ -23,17 +23,47 @@ import (
"github.com/holiman/billy" "github.com/holiman/billy"
) )
// getSlotSize return the storage size for a given transaction size based on the current slotter. // slotSizer computes the storage shelf size for a given transaction size using
func getSlotSize(slotter billy.SlotSizeFn, size uint32) (uint32, error) { // O(1) arithmetic. Shelf sizes form an arithmetic sequence:
for { //
slotSize, done := slotter() // base, base+step, base+2*step, ...
if size <= slotSize { //
return slotSize, nil // This mirrors the progression in newSlotter and newSlotterEIP7594, but avoids
} // creating and iterating a stateful closure on every lookup.
if done { type slotSizer struct {
return size, errors.New("size exceeds maximum slot size") 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. // tryMigrate checks if the billy needs to be migrated and migrates if needed.