@@ -2098,6 +2098,185 @@ func TestGetBlobs(t *testing.T) {
20982098 pool .Close ()
20992099}
21002100
2101+ // TestSidecarConversion will verify that after the Osaka fork, all legacy
2102+ // sidecars in the pool are successfully convert to v1 sidecars.
2103+ func TestSidecarConversion (t * testing.T ) {
2104+ // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
2105+
2106+ // Create a temporary folder for the persistent backend
2107+ storage := t .TempDir ()
2108+ os .MkdirAll (filepath .Join (storage , pendingTransactionStore ), 0700 )
2109+
2110+ var (
2111+ preOsakaTxs = make (types.Transactions , 10 )
2112+ postOsakaTxs = make (types.Transactions , 3 )
2113+ keys = make ([]* ecdsa.PrivateKey , len (preOsakaTxs )+ len (postOsakaTxs ))
2114+ addrs = make ([]common.Address , len (preOsakaTxs )+ len (postOsakaTxs ))
2115+ statedb , _ = state .New (types .EmptyRootHash , state .NewDatabaseForTesting ())
2116+ )
2117+ for i := range keys {
2118+ keys [i ], _ = crypto .GenerateKey ()
2119+ addrs [i ] = crypto .PubkeyToAddress (keys [i ].PublicKey )
2120+ statedb .AddBalance (addrs [i ], uint256 .NewInt (1_000_000_000 ), tracing .BalanceChangeUnspecified )
2121+ }
2122+ for i := range preOsakaTxs {
2123+ preOsakaTxs [i ] = makeMultiBlobTx (0 , 1 , 1000 , 100 , 2 , 0 , keys [i ], types .BlobSidecarVersion0 )
2124+ }
2125+ for i := range postOsakaTxs {
2126+ if i == 0 {
2127+ // First has a v0 sidecar.
2128+ postOsakaTxs [i ] = makeMultiBlobTx (0 , 1 , 1000 , 100 , 1 , 0 , keys [len (preOsakaTxs )+ i ], types .BlobSidecarVersion0 )
2129+ }
2130+ postOsakaTxs [i ] = makeMultiBlobTx (0 , 1 , 1000 , 100 , 1 , 0 , keys [len (preOsakaTxs )+ i ], types .BlobSidecarVersion1 )
2131+ }
2132+ statedb .Commit (0 , true , false )
2133+
2134+ // Test plan:
2135+ // 1) Create a bunch v0 sidecar txs and add to pool before Osaka.
2136+ // 2) Pass in new Osaka header to activate the conversion thread.
2137+ // 3) Continue adding both v0 and v1 transactions to the pool.
2138+ // 4) Verify that as additional blocks come in, transactions involved in the
2139+ // migration are correctly discarded.
2140+
2141+ config := & params.ChainConfig {
2142+ ChainID : big .NewInt (1 ),
2143+ LondonBlock : big .NewInt (0 ),
2144+ BerlinBlock : big .NewInt (0 ),
2145+ CancunTime : newUint64 (0 ),
2146+ PragueTime : newUint64 (0 ),
2147+ OsakaTime : newUint64 (1 ),
2148+ BlobScheduleConfig : params .DefaultBlobSchedule ,
2149+ }
2150+ chain := & testBlockChain {
2151+ config : config ,
2152+ basefee : uint256 .NewInt (1050 ),
2153+ blobfee : uint256 .NewInt (105 ),
2154+ statedb : statedb ,
2155+ blocks : make (map [uint64 ]* types.Block ),
2156+ }
2157+
2158+ // Create 3 blocks:
2159+ // - the current block, before Osaka
2160+ // - the first block after Osaka
2161+ // - another post-Osaka block with several transactions in it
2162+ header0 := chain .CurrentBlock ()
2163+ header0 .Time = 0
2164+ chain .blocks [0 ] = types .NewBlockWithHeader (header0 )
2165+
2166+ header1 := chain .CurrentBlock ()
2167+ header1 .Number = big .NewInt (1 )
2168+ header1 .Time = 1
2169+ chain .blocks [1 ] = types .NewBlockWithHeader (header1 )
2170+
2171+ header2 := chain .CurrentBlock ()
2172+ header2 .Time = 2
2173+ header2 .Number = big .NewInt (2 )
2174+
2175+ // Make a copy of one of the pre-Osaka transactions and convert it to v1 here
2176+ // so that we can add it to the pool later and ensure a duplicate is not added
2177+ // by the conversion queue.
2178+ tx := preOsakaTxs [len (preOsakaTxs )- 1 ]
2179+ sc := * tx .BlobTxSidecar () // copy sidecar
2180+ sc .ToV1 ()
2181+ tx .WithBlobTxSidecar (& sc )
2182+
2183+ block2 := types .NewBlockWithHeader (header2 ).WithBody (types.Body {Transactions : append (postOsakaTxs , tx )})
2184+ chain .blocks [2 ] = block2
2185+
2186+ pool := New (Config {Datadir : storage }, chain , nil )
2187+ if err := pool .Init (1 , header0 , newReserver ()); err != nil {
2188+ t .Fatalf ("failed to create blob pool: %v" , err )
2189+ }
2190+
2191+ errs := pool .Add (preOsakaTxs , true )
2192+ for i , err := range errs {
2193+ if err != nil {
2194+ t .Errorf ("failed to insert blob tx from %s: %s" , addrs [i ], errs [i ])
2195+ }
2196+ }
2197+
2198+ // Kick off migration.
2199+ pool .Reset (header0 , header1 )
2200+
2201+ // Add the v0 sidecar tx, but don't block so we can keep doing other stuff
2202+ // while it converts the sidecar.
2203+ addDone := make (chan struct {})
2204+ go func () {
2205+ pool .Add (types.Transactions {postOsakaTxs [0 ]}, false )
2206+ close (addDone )
2207+ }()
2208+
2209+ // Add the post-Osaka v1 sidecar txs.
2210+ errs = pool .Add (postOsakaTxs [1 :], false )
2211+ for _ , err := range errs {
2212+ if err != nil {
2213+ t .Fatalf ("expected tx add to succeed: %v" , err )
2214+ }
2215+ }
2216+
2217+ // Wait for the first tx's conversion to complete, then check that all
2218+ // transactions added after Osaka can be accounted for in the pool.
2219+ <- addDone
2220+ pending := pool .Pending (txpool.PendingFilter {BlobTxs : true , BlobVersion : types .BlobSidecarVersion1 })
2221+ for _ , tx := range postOsakaTxs {
2222+ from , _ := pool .signer .Sender (tx )
2223+ if len (pending [from ]) != 1 || pending [from ][0 ].Hash != tx .Hash () {
2224+ t .Fatalf ("expected post-Osaka txs to be pending" )
2225+ }
2226+ }
2227+
2228+ // Now update the pool with the next block. This should cause the pool to
2229+ // clear out the post-Osaka txs since they were included in block 2. Since the
2230+ // test blockchain doesn't manage nonces, we'll just do that manually before
2231+ // the reset is called. Don't forget about the pre-Osaka transaction we also
2232+ // added to block 2!
2233+ for i := range postOsakaTxs {
2234+ statedb .SetNonce (addrs [len (preOsakaTxs )+ i ], 1 , tracing .NonceChangeEoACall )
2235+ }
2236+ statedb .SetNonce (addrs [len (preOsakaTxs )- 1 ], 1 , tracing .NonceChangeEoACall )
2237+ pool .Reset (header1 , block2 .Header ())
2238+
2239+ // Now verify no post-Osaka transactions are tracked by the pool.
2240+ for i , tx := range postOsakaTxs {
2241+ if pool .Get (tx .Hash ()) != nil {
2242+ t .Fatalf ("expected txs added post-osaka to have been placed in limbo due to inclusion in a block: index %d, hash %s" , i , tx .Hash ())
2243+ }
2244+ }
2245+
2246+ // Wait for the pool migration to complete.
2247+ <- pool .cQueue .anyBillyConversionDone
2248+
2249+ // Verify all transactions in the pool were converted and verify the
2250+ // subsequent cell proofs.
2251+ count , _ := pool .Stats ()
2252+ if count != len (preOsakaTxs )- 1 {
2253+ t .Errorf ("expected pending count to match initial tx count: pending=%d, expected=%d" , count , len (preOsakaTxs )- 1 )
2254+ }
2255+ for addr , acc := range pool .index {
2256+ for _ , m := range acc {
2257+ if m .version != types .BlobSidecarVersion1 {
2258+ t .Errorf ("expected sidecar to have been converted: from %s, hash %s" , addr , m .hash )
2259+ }
2260+ tx := pool .Get (m .hash )
2261+ if tx == nil {
2262+ t .Errorf ("failed to get tx by hash: %s" , m .hash )
2263+ }
2264+ sc := tx .BlobTxSidecar ()
2265+ if err := kzg4844 .VerifyCellProofs (sc .Blobs , sc .Commitments , sc .Proofs ); err != nil {
2266+ t .Errorf ("failed to verify cell proofs for tx %s after conversion: %s" , m .hash , err )
2267+ }
2268+ }
2269+ }
2270+
2271+ verifyPoolInternals (t , pool )
2272+
2273+ // Launch conversion a second time.
2274+ // This is just a sanity check to ensure we can handle it.
2275+ pool .Reset (header0 , header1 )
2276+
2277+ pool .Close ()
2278+ }
2279+
21012280// fakeBilly is a billy.Database implementation which just drops data on the floor.
21022281type fakeBilly struct {
21032282 billy.Database
@@ -2180,3 +2359,5 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
21802359 }
21812360 }
21822361}
2362+
2363+ func newUint64 (val uint64 ) * uint64 { return & val }
0 commit comments