From bca071a1ddf151ebb712cd01a28557b09d333d9d Mon Sep 17 00:00:00 2001 From: lightclient Date: Fri, 19 Sep 2025 07:35:31 -0600 Subject: [PATCH] core/txpool/blobpool: add test for migration of existing txs at osaka boundary --- core/txpool/blobpool/blobpool.go | 7 +- core/txpool/blobpool/blobpool_test.go | 94 +++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 423d2593aa..9e4c1eaa48 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -339,7 +339,9 @@ type BlobPool struct { signer types.Signer // Transaction signer to use for sender recovery chain BlockChain // Chain object to access the state through - cQueue *conversionQueue // The queue for performing legacy sidecar conversion + cQueue *conversionQueue // The queue for performing legacy sidecar conversion (TODO: remove after Osaka) + + sidecarMigrationDoneCh chan struct{} // Channel for signaling when all txs at the boundary have been converted (TODO: remove after Osaka) head atomic.Pointer[types.Header] // Current head of the chain state *state.StateDB // Current state at the head of the chain @@ -946,6 +948,9 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) { } } } + if p.sidecarMigrationDoneCh != nil { + close(p.sidecarMigrationDoneCh) + } log.Info("Completed the blob transaction conversion", "discarded", fail, "injected", success, "elapsed", common.PrettyDuration(time.Since(start))) }() } diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 75a87940bd..b717d9c5e2 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -2098,6 +2098,98 @@ func TestGetBlobs(t *testing.T) { pool.Close() } +// TestSidecarConversion will verify that after the Osaka fork, all legacy +// sidecars in the pool are successfully convert to v1 sidecars. +func TestSidecarConversion(t *testing.T) { + // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelDebug, true))) + + // Create a temporary folder for the persistent backend + storage := t.TempDir() + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + + var ( + count = 10 + keys = make([]*ecdsa.PrivateKey, count) + addrs = make([]common.Address, count) + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + ) + for i := 0; i < count; i++ { + keys[i], _ = crypto.GenerateKey() + addrs[i] = crypto.PubkeyToAddress(keys[i].PublicKey) + statedb.AddBalance(addrs[i], uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + } + statedb.Commit(0, true, false) + + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + LondonBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + CancunTime: newUint64(0), + PragueTime: newUint64(0), + OsakaTime: newUint64(1), + BlobScheduleConfig: params.DefaultBlobSchedule, + } + chain := &testBlockChain{ + config: config, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + + before := chain.CurrentBlock() + before.Time = 0 + after := chain.CurrentBlock() + after.Time = 1 + + pool := New(Config{Datadir: storage}, chain, nil) + if err := pool.Init(1, before, newReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + + for i, key := range keys { + tx := makeMultiBlobTx(0, 1, 1000, 100, 2, 0, key, types.BlobSidecarVersion0) + if errs := pool.Add([]*types.Transaction{tx}, true); errs[0] != nil { + t.Errorf("failed to insert blob tx from %s: %s", addrs[i], errs[0]) + } + } + + // Should kick off migration. + pool.sidecarMigrationDoneCh = make(chan struct{}) + pool.Reset(before, after) + <-pool.sidecarMigrationDoneCh + + // Verify all transactions in the pool were converted and verify the + // subsequent cell proofs. + pending, _ := pool.Stats() + if pending != count { + t.Errorf("expected pending count to match initial tx count: pending=%d, expected=%d", pending, count) + } + for addr, acc := range pool.index { + for _, m := range acc { + if m.version != types.BlobSidecarVersion1 { + t.Errorf("expected sidecar to have been converted: from %s, hash %s", addr, m.hash) + } + tx := pool.Get(m.hash) + if tx == nil { + t.Errorf("failed to get tx by hash: %s", m.hash) + } + sc := tx.BlobTxSidecar() + if err := kzg4844.VerifyCellProofs(sc.Blobs, sc.Commitments, sc.Proofs); err != nil { + t.Errorf("failed to verify cell proofs for tx %s after conversion: %s", m.hash, err) + } + } + } + + // Verify all the calculated pool internals. Interestingly, this is **not** + // a duplication of the above checks, this actually validates the verifier + // using the above already hard coded checks. + // + // Do not remove this, nor alter the above to be generic. + verifyPoolInternals(t, pool) + + pool.Close() +} + // fakeBilly is a billy.Database implementation which just drops data on the floor. type fakeBilly struct { billy.Database @@ -2180,3 +2272,5 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) { } } } + +func newUint64(val uint64) *uint64 { return &val }