@@ -2065,3 +2065,185 @@ func TestCustomBlockTimeMining(t *testing.T) {
20652065 require .LessOrEqual (t , blocksMinedCount , maxExpectedBlocks ,
20662066 fmt .Sprintf ("Too many blocks mined. Expected at most %d, got %d" , maxExpectedBlocks , blocksMinedCount ))
20672067}
2068+
2069+ // TestLateBlockNotEmpty tests that when a parent block is sealed late,
2070+ // blocks still have sufficient time to include transactions.
2071+ // This verifies the fix for empty blocks caused by late parent blocks.
2072+ func TestLateBlockNotEmpty (t * testing.T ) {
2073+ t .Parallel ()
2074+ log .SetDefault (log .NewLogger (log .NewTerminalHandlerWithLevel (os .Stderr , log .LevelInfo , true )))
2075+ fdlimit .Raise (2048 )
2076+
2077+ faucets := make ([]* ecdsa.PrivateKey , 128 )
2078+ for i := 0 ; i < len (faucets ); i ++ {
2079+ faucets [i ], _ = crypto .GenerateKey ()
2080+ }
2081+
2082+ genesis := InitGenesis (t , faucets , "./testdata/genesis_2val.json" , 16 )
2083+ genesis .Config .Bor .Period = map [string ]uint64 {"0" : 2 }
2084+ genesis .Config .Bor .Sprint = map [string ]uint64 {"0" : 16 }
2085+ genesis .Config .Bor .RioBlock = big .NewInt (0 )
2086+
2087+ // Start a single miner node
2088+ stack , ethBackend , err := InitMiner (genesis , keys [0 ], true )
2089+ require .NoError (t , err )
2090+ defer stack .Close ()
2091+
2092+ for stack .Server ().NodeInfo ().Ports .Listener == 0 {
2093+ time .Sleep (250 * time .Millisecond )
2094+ }
2095+
2096+ // Start mining
2097+ err = ethBackend .StartMining ()
2098+ require .NoError (t , err )
2099+
2100+ // Wait for initial blocks
2101+ log .Info ("Waiting for initial blocks..." )
2102+ for {
2103+ time .Sleep (500 * time .Millisecond )
2104+ if ethBackend .BlockChain ().CurrentBlock ().Number .Uint64 () >= 3 {
2105+ break
2106+ }
2107+ }
2108+
2109+ // Stop mining and wait for it to fully stop
2110+ ethBackend .StopMining ()
2111+ time .Sleep (500 * time .Millisecond )
2112+
2113+ // Capture parent block
2114+ parentBlock := ethBackend .BlockChain ().CurrentBlock ()
2115+ parentNumber := parentBlock .Number .Uint64 ()
2116+ parentTime := parentBlock .Time
2117+
2118+ log .Info ("Parent block" , "number" , parentNumber , "time" , parentTime )
2119+
2120+ // Add transactions BEFORE waiting (they should be in pool when we resume)
2121+ txpool := ethBackend .TxPool ()
2122+ senderKey := pkey1
2123+ senderAddr := crypto .PubkeyToAddress (senderKey .PublicKey )
2124+ recipientAddr := crypto .PubkeyToAddress (pkey2 .PublicKey )
2125+ nonce := txpool .Nonce (senderAddr )
2126+ signer := types .LatestSignerForChainID (genesis .Config .ChainID )
2127+
2128+ // Start goroutine to continuously add transactions
2129+ stopTxs := make (chan struct {})
2130+ txNonce := nonce
2131+ go func () {
2132+ ticker := time .NewTicker (100 * time .Millisecond )
2133+ defer ticker .Stop ()
2134+ for {
2135+ select {
2136+ case <- stopTxs :
2137+ return
2138+ case <- ticker .C :
2139+ // Add 5 transactions every 100ms
2140+ for i := 0 ; i < 5 ; i ++ {
2141+ tx := types .NewTransaction (
2142+ txNonce ,
2143+ recipientAddr ,
2144+ big .NewInt (1000 ),
2145+ 21000 ,
2146+ big .NewInt (30000000000 ),
2147+ nil ,
2148+ )
2149+ signedTx , _ := types .SignTx (tx , signer , senderKey )
2150+ if txpool .Add ([]* types.Transaction {signedTx }, true )[0 ] == nil {
2151+ txNonce ++
2152+ }
2153+ }
2154+ }
2155+ }
2156+ }()
2157+ defer close (stopTxs )
2158+
2159+ // Wait a bit for initial transactions to be added
2160+ time .Sleep (300 * time .Millisecond )
2161+
2162+ pending , queued := txpool .Stats ()
2163+ log .Info ("Txpool after initial txs" , "pending" , pending , "queued" , queued )
2164+ require .Greater (t , pending , 0 , "Expected transactions in pending" )
2165+
2166+ // Wait for parent to become older than block period (simulates late parent)
2167+ blockPeriod := time .Duration (genesis .Config .Bor .Period ["0" ]) * time .Second
2168+
2169+ // Wait until parent age > blockPeriod (not just equal)
2170+ var parentAge int64
2171+ for {
2172+ parentBlock = ethBackend .BlockChain ().CurrentBlock ()
2173+ parentNumber = parentBlock .Number .Uint64 ()
2174+ parentTime = parentBlock .Time
2175+ parentAge = time .Now ().Unix () - int64 (parentTime )
2176+
2177+ if parentAge > int64 (blockPeriod .Seconds ()) {
2178+ log .Info ("Parent is now late" , "number" , parentNumber , "age" , parentAge , "blockPeriod" , blockPeriod .Seconds ())
2179+ break
2180+ }
2181+
2182+ time .Sleep (100 * time .Millisecond )
2183+ }
2184+
2185+ // Resume mining
2186+ err = ethBackend .StartMining ()
2187+ require .NoError (t , err )
2188+
2189+ // Wait for blocks to be mined and check that they ALL contain transactions
2190+ log .Info ("Waiting for blocks after resume..." )
2191+ blocksToCheck := uint64 (3 )
2192+ maxWait := 10 * time .Second
2193+ deadline := time .Now ().Add (maxWait )
2194+ allBlocksChecked := false
2195+
2196+ var currentNumber uint64
2197+ for time .Now ().Before (deadline ) {
2198+ time .Sleep (500 * time .Millisecond )
2199+ currentNumber = ethBackend .BlockChain ().CurrentBlock ().Number .Uint64 ()
2200+
2201+ if currentNumber >= parentNumber + blocksToCheck {
2202+ allBlocksChecked = true
2203+ break
2204+ }
2205+ }
2206+
2207+ require .True (t , allBlocksChecked , "Expected %d blocks to be mined" , blocksToCheck )
2208+
2209+ // Verify ALL blocks after parent contain transactions
2210+ totalTxsInBlocks := 0
2211+ actualNow := time .Now ().Unix ()
2212+ parentAge = actualNow - int64 (parentTime ) // Update parent age for error messages
2213+
2214+ for i := uint64 (1 ); i <= blocksToCheck ; i ++ {
2215+ block := ethBackend .BlockChain ().GetBlockByNumber (parentNumber + i )
2216+ require .NotNil (t , block )
2217+ txCount := len (block .Transactions ())
2218+ totalTxsInBlocks += txCount
2219+
2220+ // Calculate expected build time
2221+ expectedMinTime := int64 (parentTime ) + int64 (blockPeriod .Seconds ())
2222+ actualBuildTime := int64 (block .Time ()) - actualNow
2223+ timeFromParent := int64 (block .Time ()) - int64 (parentTime )
2224+
2225+ log .Info ("Block check" ,
2226+ "number" , block .Number ().Uint64 (),
2227+ "txCount" , txCount ,
2228+ "blockTime" , block .Time (),
2229+ "actualNow" , actualNow ,
2230+ "buildTime" , actualBuildTime ,
2231+ "timeFromParent" , timeFromParent ,
2232+ "expectedMin" , expectedMinTime )
2233+
2234+ // KEY ASSERTION: With the fix, ALL blocks should contain transactions
2235+ // when there are pending transactions in the pool
2236+ require .Greater (t , txCount , 0 ,
2237+ "Block %d is empty! With late block fix, all blocks should include " +
2238+ "transactions when txpool has pending txs. Parent age was %d seconds. " +
2239+ "Block time: %d, Now: %d, Build time available: %d seconds." ,
2240+ block .Number ().Uint64 (), parentAge , block .Time (), actualNow , actualBuildTime )
2241+ }
2242+
2243+ log .Info ("SUCCESS: All blocks after late parent contain transactions" ,
2244+ "blocksChecked" , blocksToCheck ,
2245+ "totalTxs" , totalTxsInBlocks ,
2246+ "parentAge" , parentAge )
2247+
2248+ ethBackend .StopMining ()
2249+ }
0 commit comments