From 7162ee2f72c8bf294ceed7bf0f3d9681bdcf2bb3 Mon Sep 17 00:00:00 2001 From: RekCuy63 Date: Mon, 18 May 2026 13:46:51 +0800 Subject: [PATCH] core/txpool/blobpool: support legacy limbo blobs --- core/txpool/blobpool/blobpool_test.go | 48 +++++++++++++++++++++++++ core/txpool/blobpool/limbo.go | 50 ++++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 8032e21e8a..20a9b26d3f 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -1366,6 +1366,54 @@ func TestLegacyTxConversion(t *testing.T) { verifyPoolInternals(t, pool) } +// TestLegacyLimboBlobConversion verifies that limbo entries stored in the +// legacy *types.Transaction RLP format remain available after opening the limbo +// with the blobTxForPool storage format. +func TestLegacyLimboBlobConversion(t *testing.T) { + storage := t.TempDir() + limbodir := filepath.Join(storage, limboedTransactionStore) + if err := os.MkdirAll(limbodir, 0700); err != nil { + t.Fatalf("failed to create limbo dir: %v", err) + } + store, err := billy.Open(billy.Options{Path: limbodir}, newSlotter(params.BlobTxMaxBlobs), nil) + if err != nil { + t.Fatalf("failed to open limbo store: %v", err) + } + key, _ := crypto.GenerateKey() + tx := makeMultiBlobTx(0, 1, 1000, 100, 2, 0, key, types.BlobSidecarVersion0) + + legacy, err := rlp.EncodeToBytes(&legacyLimboBlob{ + TxHash: tx.Hash(), + Block: 7, + Tx: tx, + }) + if err != nil { + t.Fatalf("failed to encode legacy limbo blob: %v", err) + } + if _, err := store.Put(legacy); err != nil { + t.Fatalf("failed to put legacy limbo blob: %v", err) + } + store.Close() + + limbo, err := newLimbo(params.TestChainConfig, limbodir) + if err != nil { + t.Fatalf("failed to open limbo: %v", err) + } + defer limbo.Close() + + ptx, err := limbo.pull(tx.Hash()) + if err != nil { + t.Fatalf("failed to pull legacy limbo blob: %v", err) + } + got := ptx.ToTx() + if got.Hash() != tx.Hash() { + t.Fatalf("limbo tx hash mismatch: have %s, want %s", got.Hash(), tx.Hash()) + } + if got.BlobTxSidecar() == nil { + t.Fatalf("limbo tx lost sidecar") + } +} + // TestBlobCountLimit tests the blobpool enforced limits on the max blob count. func TestBlobCountLimit(t *testing.T) { var ( diff --git a/core/txpool/blobpool/limbo.go b/core/txpool/blobpool/limbo.go index b8bee2f22a..16933a68f2 100644 --- a/core/txpool/blobpool/limbo.go +++ b/core/txpool/blobpool/limbo.go @@ -36,6 +36,12 @@ type limboBlob struct { Ptx *blobTxForPool } +type legacyLimboBlob struct { + TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs + Block uint64 // Block in which the blob transaction was included + Tx *types.Transaction +} + // limbo is a light, indexed database to temporarily store recently included // blobs until they are finalized. The purpose is to support small reorgs, which // would require pulling back up old blobs (which aren't part of the chain). @@ -97,8 +103,8 @@ func (l *limbo) Close() error { // parseBlob is a callback method on limbo creation that gets called for each // limboed blob on disk to create the in-memory metadata index. func (l *limbo) parseBlob(id uint64, data []byte) error { - item := new(limboBlob) - if err := rlp.DecodeBytes(data, item); err != nil { + item, err := decodeLimboBlob(data) + if err != nil { // This path is impossible unless the disk data representation changes // across restarts. For that ever improbable case, recover gracefully // by ignoring this data entry. @@ -122,6 +128,42 @@ func (l *limbo) parseBlob(id uint64, data []byte) error { return nil } +func decodeLimboBlob(data []byte) (*limboBlob, error) { + item := new(limboBlob) + err := rlp.DecodeBytes(data, item) + if err == nil { + return validateLimboBlob(item) + } + legacy := new(legacyLimboBlob) + if legacyErr := rlp.DecodeBytes(data, legacy); legacyErr != nil { + return nil, errors.Join(err, legacyErr) + } + if legacy.Tx == nil { + return nil, errors.New("missing limbo transaction") + } + if legacy.Tx.Hash() != legacy.TxHash { + return nil, errors.New("limbo transaction hash mismatch") + } + if legacy.Tx.BlobTxSidecar() == nil { + return nil, errors.New("missing limbo blob sidecar") + } + return &limboBlob{ + TxHash: legacy.TxHash, + Block: legacy.Block, + Ptx: newBlobTxForPool(legacy.Tx), + }, nil +} + +func validateLimboBlob(item *limboBlob) (*limboBlob, error) { + if item.Ptx == nil || item.Ptx.Tx == nil { + return nil, errors.New("missing limbo transaction") + } + if item.Ptx.Tx.Hash() != item.TxHash { + return nil, errors.New("limbo transaction hash mismatch") + } + return item, nil +} + // finalize evicts all blobs belonging to a recently finalized block or older. func (l *limbo) finalize(final *types.Header) { // Just in case there's no final block yet (network not yet merged, weird @@ -222,8 +264,8 @@ func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) { if err != nil { return nil, err } - item := new(limboBlob) - if err = rlp.DecodeBytes(data, item); err != nil { + item, err := decodeLimboBlob(data) + if err != nil { return nil, err } delete(l.index, item.TxHash)