diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index adbb80a42a..230678890e 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -1655,7 +1655,7 @@ func (pool *LegacyPool) truncateQueue() { // to trigger a re-heap is this function func (pool *LegacyPool) demoteUnexecutables() { // Iterate over all accounts and demote any non-executable transactions - gasLimit := pool.currentHead.Load().GasLimit + currentHeader := pool.currentHead.Load() for addr, list := range pool.pending { nonce := pool.currentState.GetNonce(addr) @@ -1667,7 +1667,7 @@ func (pool *LegacyPool) demoteUnexecutables() { log.Trace("Removed old pending transaction", "hash", hash) } // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later - drops, invalids := list.Filter(pool.currentState.GetBalance(addr), gasLimit) + drops, invalids := list.Filter(pool.currentState.GetBalance(addr), currentHeader.GasLimit) for _, tx := range drops { hash := tx.Hash() log.Trace("Removed unpayable pending transaction", "hash", hash) @@ -1683,7 +1683,7 @@ func (pool *LegacyPool) demoteUnexecutables() { pool.enqueueTx(hash, tx, false, false) } // Drop all transactions that no longer have valid TxOptions - txConditionalsRemoved := list.FilterTxConditional(pool.currentState) + txConditionalsRemoved := list.FilterTxConditional(pool.currentState, currentHeader) for _, tx := range txConditionalsRemoved { hash := tx.Hash() diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go index f68cf72d89..82a9643ca7 100644 --- a/core/txpool/legacypool/list.go +++ b/core/txpool/legacypool/list.go @@ -523,14 +523,26 @@ func (l *list) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions, return removed, invalids } -// FilterTxConditional returns the conditional transactions with invalid KnownAccounts -// TODO - We will also have to check block range and time stamp range! -func (l *list) FilterTxConditional(state *state.StateDB) types.Transactions { +// FilterTxConditional returns the conditional transactions with invalid PIP15 options +func (l *list) FilterTxConditional(state *state.StateDB, header *types.Header) types.Transactions { + if state == nil || header == nil { + return nil + } + removed := l.txs.filter(func(tx *types.Transaction) bool { if options := tx.GetOptions(); options != nil { - err := state.ValidateKnownAccounts(options.KnownAccounts) - if err != nil { - log.Error("Error while Filtering Tx Conditional", "err", err) + if err := state.ValidateKnownAccounts(options.KnownAccounts); err != nil { + log.Debug("Error while Filtering Tx Conditional's known accounts", "err", err) + return true + } + + if err := header.ValidateBlockNumberOptionsPIP15(options.BlockNumberMin, options.BlockNumberMax); err != nil { + log.Debug("Error while Filtering Tx Conditional's block number options", "err", err) + return true + } + + if err := header.ValidateTimestampOptionsPIP15(options.TimestampMin, options.TimestampMax); err != nil { + log.Debug("Error while Filtering Tx Conditional's timestamp options", "err", err) return true } diff --git a/core/txpool/legacypool/list_test.go b/core/txpool/legacypool/list_test.go index 15db484699..5848c75a6d 100644 --- a/core/txpool/legacypool/list_test.go +++ b/core/txpool/legacypool/list_test.go @@ -79,7 +79,7 @@ func BenchmarkListAdd(b *testing.B) { } } -func TestFilterTxConditional(t *testing.T) { +func TestFilterTxConditionalKnownAccounts(t *testing.T) { t.Parallel() // Create an in memory state db to test against. @@ -87,6 +87,10 @@ func TestFilterTxConditional(t *testing.T) { db := state.NewDatabase(memDb) state, _ := state.New(common.Hash{}, db, nil) + header := &types.Header{ + Number: big.NewInt(0), + } + // Create a private key to sign transactions. key, _ := crypto.GenerateKey() @@ -100,7 +104,7 @@ func TestFilterTxConditional(t *testing.T) { // There should be no drops at this point. // No state has been modified. - drops := list.FilterTxConditional(state) + drops := list.FilterTxConditional(state, header) count := len(drops) require.Equal(t, 0, count, "got %d filtered by TxOptions when there should not be any", count) @@ -122,7 +126,7 @@ func TestFilterTxConditional(t *testing.T) { list.Add(tx2, DefaultConfig.PriceBump) // There should still be no drops as no state has been modified. - drops = list.FilterTxConditional(state) + drops = list.FilterTxConditional(state, header) count = len(drops) require.Equal(t, 0, count, "got %d filtered by TxOptions when there should not be any", count) @@ -131,7 +135,129 @@ func TestFilterTxConditional(t *testing.T) { state.SetState(common.Address{19: 1}, common.Hash{}, common.Hash{31: 1}) // tx2 should be the single transaction filtered out - drops = list.FilterTxConditional(state) + drops = list.FilterTxConditional(state, header) + + count = len(drops) + require.Equal(t, 1, count, "got %d filtered by TxOptions when there should be a single one", count) + + require.Equal(t, tx2, drops[0], "Got %x, expected %x", drops[0].Hash(), tx2.Hash()) +} + +func TestFilterTxConditionalBlockNumber(t *testing.T) { + t.Parallel() + + // Create an in memory state db to test against. + memDb := rawdb.NewMemoryDatabase() + db := state.NewDatabase(memDb) + state, _ := state.New(common.Hash{}, db, nil) + + header := &types.Header{ + Number: big.NewInt(100), + } + + // Create a private key to sign transactions. + key, _ := crypto.GenerateKey() + + // Create a list. + list := newList(true) + + // Create a transaction with no defined tx options + // and add to the list. + tx := transaction(0, 1000, key) + list.Add(tx, DefaultConfig.PriceBump) + + // There should be no drops at this point. + // No state has been modified. + drops := list.FilterTxConditional(state, header) + + count := len(drops) + require.Equal(t, 0, count, "got %d filtered by TxOptions when there should not be any", count) + + // Create another transaction with a block number option and add to the list. + tx2 := transaction(1, 1000, key) + + var options types.OptionsPIP15 + + options.BlockNumberMin = big.NewInt(90) + options.BlockNumberMax = big.NewInt(110) + + tx2.PutOptions(&options) + list.Add(tx2, DefaultConfig.PriceBump) + + // There should still be no drops as no state has been modified. + drops = list.FilterTxConditional(state, header) + + count = len(drops) + require.Equal(t, 0, count, "got %d filtered by TxOptions when there should not be any", count) + + // Set block number that conflicts with tx2's policy + header.Number = big.NewInt(120) + + // tx2 should be the single transaction filtered out + drops = list.FilterTxConditional(state, header) + + count = len(drops) + require.Equal(t, 1, count, "got %d filtered by TxOptions when there should be a single one", count) + + require.Equal(t, tx2, drops[0], "Got %x, expected %x", drops[0].Hash(), tx2.Hash()) +} + +func TestFilterTxConditionalTimestamp(t *testing.T) { + t.Parallel() + + // Create an in memory state db to test against. + memDb := rawdb.NewMemoryDatabase() + db := state.NewDatabase(memDb) + state, _ := state.New(common.Hash{}, db, nil) + + header := &types.Header{ + Number: big.NewInt(0), + Time: 100, + } + + // Create a private key to sign transactions. + key, _ := crypto.GenerateKey() + + // Create a list. + list := newList(true) + + // Create a transaction with no defined tx options + // and add to the list. + tx := transaction(0, 1000, key) + list.Add(tx, DefaultConfig.PriceBump) + + // There should be no drops at this point. + // No state has been modified. + drops := list.FilterTxConditional(state, header) + + count := len(drops) + require.Equal(t, 0, count, "got %d filtered by TxOptions when there should not be any", count) + + // Create another transaction with a timestamp option and add to the list. + tx2 := transaction(1, 1000, key) + + var options types.OptionsPIP15 + + minTimestamp := uint64(90) + maxTimestamp := uint64(110) + + options.TimestampMin = &minTimestamp + options.TimestampMax = &maxTimestamp + + tx2.PutOptions(&options) + list.Add(tx2, DefaultConfig.PriceBump) + + // There should still be no drops as no state has been modified. + drops = list.FilterTxConditional(state, header) + + count = len(drops) + require.Equal(t, 0, count, "got %d filtered by TxOptions when there should not be any", count) + + // Set timestamp that conflicts with tx2's policy + header.Time = 120 + + // tx2 should be the single transaction filtered out + drops = list.FilterTxConditional(state, header) count = len(drops) require.Equal(t, 1, count, "got %d filtered by TxOptions when there should be a single one", count)