@@ -1684,6 +1684,110 @@ func TestAddOverPendingCostDynamicFee(t *testing.T) {
16841684 assert .EqualError (t , err , "tx rejected: insufficient energy for overall pending cost" )
16851685}
16861686
1687+ func TestWashDeferredTxPendingCostEnforcement (t * testing.T ) {
1688+ // This test asserts the correct behaviour: only 2 of 3 deferred txs
1689+ // should be promoted; the third exceeds the payer's energy and must be evicted.
1690+ now := uint64 (time .Now ().Unix () - time .Now ().Unix ()% 10 - 10 )
1691+ db := muxdb .NewMem ()
1692+
1693+ // energy = 42*10^18 covers exactly 2 txs at gas=21000, InitialBaseGasPrice=1e15.
1694+ energy , _ := new (big.Int ).SetString ("42000000000000000000" , 10 )
1695+
1696+ builder := new (genesis.Builder ).
1697+ GasLimit (thor .InitialGasLimit ).
1698+ ForkConfig (& thor .NoFork ).
1699+ Timestamp (now ).
1700+ State (func (st * state.State ) error {
1701+ if err := st .SetCode (builtin .Params .Address , builtin .Params .RuntimeBytecodes ()); err != nil {
1702+ return err
1703+ }
1704+ if err := st .SetCode (builtin .Prototype .Address , builtin .Prototype .RuntimeBytecodes ()); err != nil {
1705+ return err
1706+ }
1707+ st .SetEnergy (devAccounts [0 ].Address , energy , now )
1708+ return nil
1709+ })
1710+
1711+ setMethod , found := builtin .Params .ABI .MethodByName ("set" )
1712+ require .True (t , found )
1713+ var executor thor.Address
1714+ data , err := setMethod .EncodeInput (thor .KeyExecutorAddress , new (big.Int ).SetBytes (executor [:]))
1715+ require .NoError (t , err )
1716+ builder .Call (tx .NewClause (& builtin .Params .Address ).WithData (data ), thor.Address {})
1717+ data , err = setMethod .EncodeInput (thor .KeyLegacyTxBaseGasPrice , thor .InitialBaseGasPrice )
1718+ require .NoError (t , err )
1719+ builder .Call (tx .NewClause (& builtin .Params .Address ).WithData (data ), executor )
1720+
1721+ b0 , _ , _ , err := builder .Build (state .NewStater (db ))
1722+ require .NoError (t , err )
1723+
1724+ // Commit genesis state so the pool stater can read it.
1725+ st := state .New (db , trie.Root {Hash : b0 .Header ().StateRoot ()})
1726+ stage , err := st .Stage (trie.Version {Major : 1 })
1727+ require .NoError (t , err )
1728+ root , err := stage .Commit ()
1729+ require .NoError (t , err )
1730+
1731+ b1 := new (block.Builder ).
1732+ ParentID (b0 .Header ().ID ()).
1733+ StateRoot (root ).
1734+ TotalScore (100 ).
1735+ Timestamp (now + 10 ).
1736+ GasLimit (thor .InitialGasLimit ).
1737+ Build ()
1738+
1739+ repo , _ := chain .NewRepository (db , b0 )
1740+ require .NoError (t , repo .AddBlock (b1 , tx.Receipts {}, 0 , true ))
1741+
1742+ pool := New (repo , state .NewStater (db ), Options {
1743+ Limit : 50 , // non-executable cap = 50*2/10 = 10, enough for 3 deferred txs
1744+ LimitPerAccount : 10 ,
1745+ MaxLifetime : time .Hour ,
1746+ }, & thor .NoFork )
1747+ defer pool .Close ()
1748+
1749+ // BlockRef=3: deferred while best is b1 (nextBlockNum=2), executable when best is b2 (nextBlockNum=3).
1750+ deferredRef := tx .NewBlockRef (b1 .Header ().Number () + 2 )
1751+ tx1 := newTx (tx .TypeLegacy , pool .repo .ChainTag (), nil , 21000 , deferredRef , 100 , nil , tx .Features (0 ), devAccounts [0 ])
1752+ tx2 := newTx (tx .TypeLegacy , pool .repo .ChainTag (), nil , 21000 , deferredRef , 100 , nil , tx .Features (0 ), devAccounts [0 ])
1753+ tx3 := newTx (tx .TypeLegacy , pool .repo .ChainTag (), nil , 21000 , deferredRef , 100 , nil , tx .Features (0 ), devAccounts [0 ])
1754+
1755+ // All 3 are admitted: cost==nil for deferred txs so Add() skips the energy check.
1756+ require .NoError (t , pool .AddLocal (tx1 ))
1757+ require .NoError (t , pool .AddLocal (tx2 ))
1758+ require .NoError (t , pool .AddLocal (tx3 ))
1759+ require .Equal (t , 3 , pool .Len (), "all 3 deferred txs must be admitted without energy check" )
1760+
1761+ // Advance chain to block 2: nextBlockNum becomes 3, so BlockRef=3 is no longer deferred.
1762+ // Commit the same state at version 2 so wash() can read it at the new head.
1763+ st2 := state .New (db , trie.Root {Hash : root })
1764+ stage2 , err := st2 .Stage (trie.Version {Major : 2 })
1765+ require .NoError (t , err )
1766+ root2 , err := stage2 .Commit ()
1767+ require .NoError (t , err )
1768+ b2 := new (block.Builder ).
1769+ ParentID (b1 .Header ().ID ()).
1770+ StateRoot (root2 ).
1771+ TotalScore (200 ).
1772+ Timestamp (now + 20 ).
1773+ GasLimit (thor .InitialGasLimit ).
1774+ Build ()
1775+ require .NoError (t , repo .AddBlock (b2 , tx.Receipts {}, 0 , true ))
1776+
1777+ executables , _ , _ , err := pool .wash (repo .BestBlockSummary (), true )
1778+ require .NoError (t , err )
1779+
1780+ promoted := 0
1781+ for _ , etx := range executables {
1782+ if etx .ID () == tx1 .ID () || etx .ID () == tx2 .ID () || etx .ID () == tx3 .ID () {
1783+ promoted ++
1784+ }
1785+ }
1786+ // Payer energy covers only 2 txs; the 3rd must be evicted during promotion.
1787+ assert .Equal (t , 2 , promoted , "only 2 deferred txs should be promoted; payer energy is exhausted after 2" )
1788+ assert .Equal (t , 2 , pool .Len (), "over-budget tx should be evicted from the pool during wash" )
1789+ }
1790+
16871791func TestValidateTxBasics (t * testing.T ) {
16881792 pool := newPool (1 , LIMIT_PER_ACCOUNT , & thor.ForkConfig {GALACTICA : 0 })
16891793 defer pool .Close ()
0 commit comments