From e05bf5b320d586d84ae5d5aea8a5064cb9756bb8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 24 Sep 2025 12:27:26 +0200 Subject: [PATCH] core/txpool/blobpool: wait for billy conversion to exit --- core/txpool/blobpool/blobpool.go | 6 ++- core/txpool/blobpool/conversion.go | 63 +++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 3869d3ebf6..679529b286 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -904,7 +904,9 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) { } } // Initiate the background conversion thread. - go p.convertLegacySidecars(ids, txs) + p.cQueue.launchBillyConversion(func() { + p.convertLegacySidecars(ids, txs) + }) } } @@ -913,7 +915,7 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) { // bulk conversion. If it fails for some reason, the subsequent txs won't be dropped // for simplicity which we assume it's very likely to happen. // -// The returned flag indicates whether the replacement succeeds or not. +// The returned flag indicates whether the replacement succeeded. func (p *BlobPool) compareAndSwap(address common.Address, hash common.Hash, blob []byte, oldID uint64, oldStorageSize uint32) bool { p.lock.Lock() defer p.lock.Unlock() diff --git a/core/txpool/blobpool/conversion.go b/core/txpool/blobpool/conversion.go index 5026892fc8..818997869c 100644 --- a/core/txpool/blobpool/conversion.go +++ b/core/txpool/blobpool/conversion.go @@ -32,8 +32,8 @@ import ( // with 6 blobs each) would consume approximately 1.5GB of memory. const maxPendingConversionTasks = 2048 -// cTask represents a conversion task with an attached legacy blob transaction. -type cTask struct { +// txConvert represents a conversion task with an attached legacy blob transaction. +type txConvert struct { tx *types.Transaction // Legacy blob transaction done chan error // Channel for signaling back if the conversion succeeds } @@ -43,7 +43,8 @@ type cTask struct { // it is performed in the background by a single thread, ensuring the main Geth // process is not overloaded. type conversionQueue struct { - tasks chan *cTask + tasks chan *txConvert + billy chan func() quit chan struct{} closed chan struct{} } @@ -51,7 +52,8 @@ type conversionQueue struct { // newConversionQueue constructs the conversion queue. func newConversionQueue() *conversionQueue { q := &conversionQueue{ - tasks: make(chan *cTask), + tasks: make(chan *txConvert), + billy: make(chan func()), quit: make(chan struct{}), closed: make(chan struct{}), } @@ -66,13 +68,23 @@ func newConversionQueue() *conversionQueue { func (q *conversionQueue) convert(tx *types.Transaction) error { done := make(chan error, 1) select { - case q.tasks <- &cTask{tx: tx, done: done}: + case q.tasks <- &txConvert{tx: tx, done: done}: return <-done case <-q.closed: return errors.New("conversion queue closed") } } +// launchBillyConversion starts a conversion task in the background. +func (q *conversionQueue) launchBillyConversion(fn func()) error { + select { + case q.billy <- fn: + return nil + case <-q.closed: + return errors.New("conversion queue closed") + } +} + // close terminates the conversion queue. func (q *conversionQueue) close() { select { @@ -85,7 +97,7 @@ func (q *conversionQueue) close() { } // run converts a batch of legacy blob txs to the new cell proof format. -func (q *conversionQueue) run(tasks []*cTask, done chan struct{}, interrupt *atomic.Int32) { +func (q *conversionQueue) run(tasks []*txConvert, done chan struct{}, interrupt *atomic.Int32) { defer close(done) for _, t := range tasks { @@ -110,42 +122,57 @@ func (q *conversionQueue) loop() { defer close(q.closed) var ( - done chan struct{} // Non-nil if background routine is active - interrupt *atomic.Int32 // Flag to signal conversion interruption + billyDone = make(chan struct{}) + billyTasks int // Count of running billy conversion tasks + done chan struct{} // Non-nil if background routine is active + interrupt *atomic.Int32 // Flag to signal conversion interruption // The pending tasks for sidecar conversion. We assume the number of legacy // blob transactions requiring conversion will not be excessive. However, // a hard cap is applied as a protective measure. - cTasks []*cTask + txTasks []*txConvert ) for { select { case t := <-q.tasks: - if len(cTasks) >= maxPendingConversionTasks { + if len(txTasks) >= maxPendingConversionTasks { t.done <- errors.New("conversion queue is overloaded") continue } - cTasks = append(cTasks, t) + txTasks = append(txTasks, t) // Launch the background conversion thread if it's idle if done == nil { done, interrupt = make(chan struct{}), new(atomic.Int32) - tasks := slices.Clone(cTasks) - cTasks = cTasks[:0] + tasks := slices.Clone(txTasks) + txTasks = txTasks[:0] go q.run(tasks, done, interrupt) } + case fn := <-q.billy: + billyTasks++ + go func() { + fn() + billyDone <- struct{}{} + }() + case <-done: done, interrupt = nil, nil + case <-billyDone: + billyTasks-- + case <-q.quit: - if done == nil { - return + if done != nil { + interrupt.Store(1) + log.Debug("Waiting for blob proof conversion to exit") + <-done + } + for billyTasks > 0 { + <-billyDone + billyTasks-- } - interrupt.Store(1) - log.Debug("Waiting for blob proof conversion to exit") - <-done return } }