Skip to content

Commit

Permalink
mempool: introduce KeepInvalidTxsInCache config option (#5813)
Browse files Browse the repository at this point in the history
When set to true, an invalid transaction will be kept in the cache (this may help some applications to protect against spam).

NOTE: this is a temporary config option. The more correct solution would be to add a TTL to each transaction (i.e. CheckTx may return a TTL in ResponseCheckTx).

Closes: #5751
  • Loading branch information
melekes committed Dec 21, 2020
1 parent 9f0d71e commit 886857f
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 6 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi

### FEATURES



### IMPROVEMENTS

- [mempool] \#5813 Add `keep-invalid-txs-in-cache` config option. When set to true, mempool will keep invalid transactions in the cache (@p4u)

### BUG FIXES

- [crypto] \#5707 Fix infinite recursion in string formatting of Secp256k1 keys (@erikgrinaker)
4 changes: 4 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,10 @@ type MempoolConfig struct {
MaxTxsBytes int64 `mapstructure:"max_txs_bytes"`
// Size of the cache (used to filter transactions we saw earlier) in transactions
CacheSize int `mapstructure:"cache_size"`
// Do not remove invalid transactions from the cache (default: false)
// Set to true if it's not possible for any invalid transaction to become
// valid again in the future.
KeepInvalidTxsInCache bool `mapstructure:"keep-invalid-txs-in-cache"`
// Maximum size of a single transaction
// NOTE: the max size of a tx transmitted over the network is {max_tx_bytes}.
MaxTxBytes int `mapstructure:"max_tx_bytes"`
Expand Down
5 changes: 5 additions & 0 deletions config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,11 @@ max_txs_bytes = {{ .Mempool.MaxTxsBytes }}
# Size of the cache (used to filter transactions we saw earlier) in transactions
cache_size = {{ .Mempool.CacheSize }}
# Do not remove invalid transactions from the cache (default: false)
# Set to true if it's not possible for any invalid transaction to become valid
# again in the future.
keep-invalid-txs-in-cache = {{ .Mempool.KeepInvalidTxsInCache }}
# Maximum size of a single transaction.
# NOTE: the max size of a tx transmitted over the network is {max_tx_bytes}.
max_tx_bytes = {{ .Mempool.MaxTxBytes }}
Expand Down
5 changes: 5 additions & 0 deletions docs/tendermint-core/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@ max_txs_bytes = 1073741824
# Size of the cache (used to filter transactions we saw earlier) in transactions
cache_size = 10000

# Do not remove invalid transactions from the cache (default: false)
# Set to true if it's not possible for any invalid transaction to become valid
# again in the future.
keep-invalid-txs-in-cache = false

# Maximum size of a single transaction.
# NOTE: the max size of a tx transmitted over the network is {max_tx_bytes}.
max_tx_bytes = 1048576
Expand Down
10 changes: 6 additions & 4 deletions mempool/clist_mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,10 @@ func (mem *CListMempool) resCbFirstTime(
mem.logger.Info("Rejected bad transaction",
"tx", txID(tx), "peerID", peerP2PID, "res", r, "err", postCheckErr)
mem.metrics.FailedTxs.Add(1)
// remove from cache (it might be good later)
mem.cache.Remove(tx)
if !mem.config.KeepInvalidTxsInCache {
// remove from cache (it might be good later)
mem.cache.Remove(tx)
}
}
default:
// ignore other messages
Expand Down Expand Up @@ -472,7 +474,7 @@ func (mem *CListMempool) resCbRecheck(req *abci.Request, res *abci.Response) {
// Tx became invalidated due to newly committed block.
mem.logger.Info("Tx is no longer valid", "tx", txID(tx), "res", r, "err", postCheckErr)
// NOTE: we remove tx from the cache because it might be good later
mem.removeTx(tx, mem.recheckCursor, true)
mem.removeTx(tx, mem.recheckCursor, !mem.config.KeepInvalidTxsInCache)
}
if mem.recheckCursor == mem.recheckEnd {
mem.recheckCursor = nil
Expand Down Expand Up @@ -586,7 +588,7 @@ func (mem *CListMempool) Update(
if deliverTxResponses[i].Code == abci.CodeTypeOK {
// Add valid committed tx to the cache (if missing).
_ = mem.cache.Push(tx)
} else {
} else if !mem.config.KeepInvalidTxsInCache {
// Allow invalid transactions to be resubmitted.
mem.cache.Remove(tx)
}
Expand Down
57 changes: 57 additions & 0 deletions mempool/clist_mempool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,63 @@ func TestMempoolUpdate(t *testing.T) {
}
}

func TestMempool_KeepInvalidTxsInCache(t *testing.T) {
app := counter.NewApplication(true)
cc := proxy.NewLocalClientCreator(app)
wcfg := cfg.DefaultConfig()
wcfg.Mempool.KeepInvalidTxsInCache = true
mempool, cleanup := newMempoolWithAppAndConfig(cc, wcfg)
defer cleanup()

// 1. An invalid transaction must remain in the cache after Update
{
a := make([]byte, 8)
binary.BigEndian.PutUint64(a, 0)

b := make([]byte, 8)
binary.BigEndian.PutUint64(b, 1)

err := mempool.CheckTx(b, nil, TxInfo{})
require.NoError(t, err)

// simulate new block
_ = app.DeliverTx(abci.RequestDeliverTx{Tx: a})
_ = app.DeliverTx(abci.RequestDeliverTx{Tx: b})
err = mempool.Update(1, []types.Tx{a, b},
[]*abci.ResponseDeliverTx{{Code: abci.CodeTypeOK}, {Code: 2}}, nil, nil)
require.NoError(t, err)

// a must be added to the cache
err = mempool.CheckTx(a, nil, TxInfo{})
if assert.Error(t, err) {
assert.Equal(t, ErrTxInCache, err)
}

// b must remain in the cache
err = mempool.CheckTx(b, nil, TxInfo{})
if assert.Error(t, err) {
assert.Equal(t, ErrTxInCache, err)
}
}

// 2. An invalid transaction must remain in the cache
{
a := make([]byte, 8)
binary.BigEndian.PutUint64(a, 0)

// remove a from the cache to test (2)
mempool.cache.Remove(a)

err := mempool.CheckTx(a, nil, TxInfo{})
require.NoError(t, err)

err = mempool.CheckTx(a, nil, TxInfo{})
if assert.Error(t, err) {
assert.Equal(t, ErrTxInCache, err)
}
}
}

func TestTxsAvailable(t *testing.T) {
app := kvstore.NewApplication()
cc := proxy.NewLocalClientCreator(app)
Expand Down

0 comments on commit 886857f

Please sign in to comment.