feat: add tx limit to mempool (#14014)
* feat: add bounding max tx to mempool * add bounded condition * sligh improvement on generator * remove unbouded option * add test * added mempool options mechanism * mising test * seting mempool * change function name * change function name * failing test * Revert "failing test" This reverts commit d527982b0d4ec826ff680afb8f43ac1d71809ccf. * fix import block * changelog entries * add ability to do unbounded mempool * remove unesesary variable * small comments * change 0 to mean unbounded * t * small test fix * add the ability to be bounded unbounded and disabled * t * set default maxtx * Update docs/docs/building-apps/02-app-mempool.md Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * example for opts * remove superflues logs entry * add mempool to configurations * fix more understandable name * remove table in favor of bulletpoints * sender nonce to unbounded * Update docs/docs/building-apps/02-app-mempool.md Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Update types/mempool/sender_nonce.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Update types/mempool/sender_nonce.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Update docs/docs/building-apps/02-app-mempool.md Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * Update server/config/config.go Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> * t * add comment for options * fix inport * fix inport Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com> Co-authored-by: Emmanuel T Odeke <emmanuel@orijtech.com>
This commit is contained in:
co-authored by
Aleksandr Bezobchuk
Emmanuel T Odeke
parent
3aff993fcb
commit
754ca3169e
@@ -36,4 +36,7 @@ type Iterator interface {
|
||||
Tx() sdk.Tx
|
||||
}
|
||||
|
||||
var ErrTxNotFound = errors.New("tx not found in mempool")
|
||||
var (
|
||||
ErrTxNotFound = errors.New("tx not found in mempool")
|
||||
ErrMempoolTxMaxCapacity = errors.New("pool reached max tx capacity")
|
||||
)
|
||||
|
||||
@@ -17,6 +17,8 @@ var (
|
||||
_ Iterator = (*senderNonceMepoolIterator)(nil)
|
||||
)
|
||||
|
||||
var DefaultMaxTx = 0
|
||||
|
||||
// senderNonceMempool is a mempool that prioritizes transactions within a sender by nonce, the lowest first,
|
||||
// but selects a random sender on each iteration. The mempool is iterated by:
|
||||
//
|
||||
@@ -26,15 +28,27 @@ var (
|
||||
//
|
||||
// Note that PrepareProposal could choose to stop iteration before reaching the end if maxBytes is reached.
|
||||
type senderNonceMempool struct {
|
||||
senders map[string]*skiplist.SkipList
|
||||
rnd *rand.Rand
|
||||
senders map[string]*skiplist.SkipList
|
||||
rnd *rand.Rand
|
||||
maxTx int
|
||||
existingTx map[txKey]bool
|
||||
}
|
||||
|
||||
type SenderNonceOptions func(mp *senderNonceMempool)
|
||||
|
||||
type txKey struct {
|
||||
address string
|
||||
nonce uint64
|
||||
}
|
||||
|
||||
// NewSenderNonceMempool creates a new mempool that prioritizes transactions by nonce, the lowest first.
|
||||
func NewSenderNonceMempool() Mempool {
|
||||
func NewSenderNonceMempool(opts ...SenderNonceOptions) Mempool {
|
||||
senderMap := make(map[string]*skiplist.SkipList)
|
||||
existingTx := make(map[txKey]bool)
|
||||
snp := &senderNonceMempool{
|
||||
senders: senderMap,
|
||||
senders: senderMap,
|
||||
maxTx: DefaultMaxTx,
|
||||
existingTx: existingTx,
|
||||
}
|
||||
|
||||
var seed int64
|
||||
@@ -44,17 +58,30 @@ func NewSenderNonceMempool() Mempool {
|
||||
}
|
||||
snp.setSeed(seed)
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(snp)
|
||||
}
|
||||
|
||||
return snp
|
||||
}
|
||||
|
||||
// NewSenderNonceMempoolWithSeed creates a new mempool that prioritizes transactions by nonce, the lowest first and sets the random seed.
|
||||
func NewSenderNonceMempoolWithSeed(seed int64) Mempool {
|
||||
senderMap := make(map[string]*skiplist.SkipList)
|
||||
snp := &senderNonceMempool{
|
||||
senders: senderMap,
|
||||
// SenderNonceSeedOpt Option To add a Seed for random type when calling the constructor NewSenderNonceMempool
|
||||
// Example:
|
||||
// > random_seed := int64(1000)
|
||||
// > NewSenderNonceMempool(SenderNonceSeedTxOpt(random_seed))
|
||||
func SenderNonceSeedOpt(seed int64) SenderNonceOptions {
|
||||
return func(snp *senderNonceMempool) {
|
||||
snp.setSeed(seed)
|
||||
}
|
||||
}
|
||||
|
||||
// SenderNonceMaxTxOpt Option To set limit of max tx when calling the constructor NewSenderNonceMempool
|
||||
// Example:
|
||||
// > NewSenderNonceMempool(SenderNonceMaxTxOpt(100))
|
||||
func SenderNonceMaxTxOpt(maxTx int) SenderNonceOptions {
|
||||
return func(snp *senderNonceMempool) {
|
||||
snp.maxTx = maxTx
|
||||
}
|
||||
snp.setSeed(seed)
|
||||
return snp
|
||||
}
|
||||
|
||||
func (snm *senderNonceMempool) setSeed(seed int64) {
|
||||
@@ -65,6 +92,12 @@ func (snm *senderNonceMempool) setSeed(seed int64) {
|
||||
// Insert adds a tx to the mempool. It returns an error if the tx does not have at least one signer.
|
||||
// priority is ignored.
|
||||
func (snm *senderNonceMempool) Insert(_ sdk.Context, tx sdk.Tx) error {
|
||||
if snm.maxTx > 0 && snm.CountTx() >= snm.maxTx {
|
||||
return ErrMempoolTxMaxCapacity
|
||||
}
|
||||
if snm.maxTx < 0 {
|
||||
return nil
|
||||
}
|
||||
sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -82,7 +115,8 @@ func (snm *senderNonceMempool) Insert(_ sdk.Context, tx sdk.Tx) error {
|
||||
snm.senders[sender] = senderTxs
|
||||
}
|
||||
senderTxs.Set(nonce, tx)
|
||||
|
||||
key := txKey{nonce: nonce, address: sender}
|
||||
snm.existingTx[key] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -117,14 +151,7 @@ func (snm *senderNonceMempool) Select(_ sdk.Context, _ [][]byte) Iterator {
|
||||
|
||||
// CountTx returns the total count of txs in the mempool.
|
||||
func (snm *senderNonceMempool) CountTx() int {
|
||||
count := 0
|
||||
|
||||
// Disable gosec here since we need neither strong randomness nor deterministic iteration.
|
||||
// #nosec
|
||||
for _, value := range snm.senders {
|
||||
count += value.Len()
|
||||
}
|
||||
return count
|
||||
return len(snm.existingTx)
|
||||
}
|
||||
|
||||
// Remove removes a tx from the mempool. It returns an error if the tx does not have at least one signer or the tx
|
||||
@@ -154,6 +181,10 @@ func (snm *senderNonceMempool) Remove(tx sdk.Tx) error {
|
||||
if senderTxs.Len() == 0 {
|
||||
delete(snm.senders, sender)
|
||||
}
|
||||
|
||||
key := txKey{nonce: nonce, address: sender}
|
||||
delete(snm.existingTx, key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mempool_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
|
||||
"pgregory.net/rapid"
|
||||
@@ -9,7 +8,6 @@ import (
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
mempool "github.com/cosmos/cosmos-sdk/types/mempool"
|
||||
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@@ -27,18 +25,20 @@ var (
|
||||
// same elements input on the mempool should be in the output except for sender nonce duplicates, which are overwritten by the later duplicate entries.
|
||||
// for every sender transaction tx_n, tx_0.nonce < tx_1.nonce ... < tx_n.nonce
|
||||
|
||||
var genAddress = rapid.Custom(func(t *rapid.T) simtypes.Account {
|
||||
accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(rapid.Int64().Draw(t, "seed for account"))), 1)
|
||||
return accounts[0]
|
||||
})
|
||||
func AddressGenerator(t *rapid.T) *rapid.Generator[sdk.AccAddress] {
|
||||
return rapid.Custom(func(t *rapid.T) sdk.AccAddress {
|
||||
pkBz := rapid.SliceOfN(rapid.Byte(), 20, 20).Draw(t, "hex")
|
||||
return sdk.AccAddress(pkBz)
|
||||
})
|
||||
}
|
||||
|
||||
func testMempoolProperties(t *rapid.T) {
|
||||
|
||||
ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger())
|
||||
mp := mempool.NewSenderNonceMempool()
|
||||
|
||||
genMultipleAddress := rapid.SliceOfNDistinct(genAddress, 1, 10, func(acc simtypes.Account) string {
|
||||
return acc.Address.String()
|
||||
genMultipleAddress := rapid.SliceOfNDistinct(AddressGenerator(t), 1, 10, func(acc sdk.AccAddress) string {
|
||||
return acc.String()
|
||||
})
|
||||
|
||||
accounts := genMultipleAddress.Draw(t, "address")
|
||||
@@ -46,10 +46,10 @@ func testMempoolProperties(t *rapid.T) {
|
||||
return testTx{
|
||||
priority: rapid.Int64Range(0, 1000).Draw(t, "priority"),
|
||||
nonce: rapid.Uint64().Draw(t, "nonce"),
|
||||
address: rapid.SampledFrom(accounts).Draw(t, "acc").Address,
|
||||
address: rapid.SampledFrom(accounts).Draw(t, "acc"),
|
||||
}
|
||||
})
|
||||
genMultipleTX := rapid.SliceOfN(genTx, 1, 500)
|
||||
genMultipleTX := rapid.SliceOfN(genTx, 1, 5000)
|
||||
|
||||
txs := genMultipleTX.Draw(t, "txs")
|
||||
senderTxRaw := getSenderTxMap(txs)
|
||||
@@ -61,6 +61,7 @@ func testMempoolProperties(t *rapid.T) {
|
||||
|
||||
iter := mp.Select(ctx, nil)
|
||||
orderTx := fetchAllTxs(iter)
|
||||
require.Equal(t, len(orderTx), mp.CountTx())
|
||||
senderTxOrdered := getSenderTxMap(orderTx)
|
||||
for key := range senderTxOrdered {
|
||||
ordered, found := senderTxOrdered[key]
|
||||
|
||||
@@ -114,7 +114,7 @@ func (s *MempoolTestSuite) TestTxOrder() {
|
||||
}
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||
pool := mempool.NewSenderNonceMempoolWithSeed(tt.seed)
|
||||
pool := mempool.NewSenderNonceMempool(mempool.SenderNonceSeedOpt(tt.seed))
|
||||
// create test txs and insert into mempool
|
||||
for i, ts := range tt.txs {
|
||||
tx := testTx{id: i, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a}
|
||||
@@ -137,3 +137,60 @@ func (s *MempoolTestSuite) TestTxOrder() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MempoolTestSuite) TestMaxTx() {
|
||||
t := s.T()
|
||||
ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger())
|
||||
accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 1)
|
||||
mp := mempool.NewSenderNonceMempool(mempool.SenderNonceMaxTxOpt(1))
|
||||
|
||||
tx := testTx{
|
||||
nonce: 0,
|
||||
address: accounts[0].Address,
|
||||
priority: rand.Int63(),
|
||||
}
|
||||
tx2 := testTx{
|
||||
nonce: 1,
|
||||
address: accounts[0].Address,
|
||||
priority: rand.Int63(),
|
||||
}
|
||||
|
||||
// empty mempool behavior
|
||||
require.Equal(t, 0, s.mempool.CountTx())
|
||||
itr := mp.Select(ctx, nil)
|
||||
require.Nil(t, itr)
|
||||
|
||||
ctx = ctx.WithPriority(tx.priority)
|
||||
err := mp.Insert(ctx, tx)
|
||||
require.NoError(t, err)
|
||||
ctx = ctx.WithPriority(tx.priority)
|
||||
err = mp.Insert(ctx, tx2)
|
||||
require.Equal(t, mempool.ErrMempoolTxMaxCapacity, err)
|
||||
|
||||
}
|
||||
|
||||
func (s *MempoolTestSuite) TestTxNotFoundOnSender() {
|
||||
t := s.T()
|
||||
ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger())
|
||||
accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 1)
|
||||
mp := mempool.NewSenderNonceMempool()
|
||||
|
||||
txSender := testTx{
|
||||
nonce: 0,
|
||||
address: accounts[0].Address,
|
||||
priority: rand.Int63(),
|
||||
}
|
||||
|
||||
tx := testTx{
|
||||
nonce: 1,
|
||||
address: accounts[0].Address,
|
||||
priority: rand.Int63(),
|
||||
}
|
||||
|
||||
ctx = ctx.WithPriority(tx.priority)
|
||||
err := mp.Insert(ctx, txSender)
|
||||
require.NoError(t, err)
|
||||
err = mp.Remove(tx)
|
||||
require.Equal(t, mempool.ErrTxNotFound, err)
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user