Skip to content

Commit

Permalink
improve txpool tests (#696)
Browse files Browse the repository at this point in the history
* improve txpool tests

* small tweak
  • Loading branch information
libotony authored Apr 23, 2024
1 parent 351f86c commit 33c1ff2
Showing 1 changed file with 194 additions and 23 deletions.
217 changes: 194 additions & 23 deletions txpool/tx_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"math/big"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"

"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/inconshreveable/log15"
"github.com/stretchr/testify/assert"
Expand All @@ -31,6 +33,8 @@ import (
const LIMIT = 10
const LIMIT_PER_ACCOUNT = 2

var devAccounts = genesis.DevAccounts()

func init() {
log15.Root().SetHandler(log15.DiscardHandler())
}
Expand All @@ -52,7 +56,7 @@ func newPoolWithParams(limit int, limitPerAccount int, BlocklistCacheFilePath st
Timestamp(timestamp).
State(func(state *state.State) error {
bal, _ := new(big.Int).SetString("1000000000000000000000000000", 10)
for _, acc := range genesis.DevAccounts() {
for _, acc := range devAccounts {
state.SetBalance(acc.Address, bal)
state.SetEnergy(acc.Address, bal, timestamp)
}
Expand All @@ -71,7 +75,7 @@ func newPoolWithParams(limit int, limitPerAccount int, BlocklistCacheFilePath st

func newHttpServer() *httptest.Server {
// Example data to be served by the mock server
data := "0x25Df024637d4e56c1aE9563987Bf3e92C9f534c0\n0x25Df024637d4e56c1aE9563987Bf3e92C9f534c1"
data := "0x25Df024637d4e56c1aE9563987Bf3e92C9f534c0\n0x25Df024637d4e56c1aE9563987Bf3e92C9f534c1\n0x865306084235bf804c8bba8a8d56890940ca8f0b"

// Create a mock server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -95,33 +99,29 @@ func TestNewCloseWithServer(t *testing.T) {
// Create a slice of transactions to be added to the pool.
txs := make(Tx.Transactions, 0, 15)
for i := 0; i < 15; i++ {
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[i%len(genesis.DevAccounts())])
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)])
txs = append(txs, tx)
}

// Call the Fill method
pool.Fill(txs)

// Add a delay of 2 seconds
time.Sleep(2 * time.Second)
time.Sleep(1 * time.Second)
}

func FillPoolWithTxs(pool *TxPool, t *testing.T) {
// Create a slice of transactions to be added to the pool.
txs := make(Tx.Transactions, 0, 15)
for i := 0; i < 12; i++ {
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0])
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
txs = append(txs, tx)
}

// Call the Fill method
pool.Fill(txs)

err := pool.Add(newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(10), 100, nil, Tx.Features(0), genesis.DevAccounts()[0]))
err := pool.Add(newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(10), 100, nil, Tx.Features(0), devAccounts[0]))
assert.Equal(t, err.Error(), "tx rejected: pool is full")

// Add a delay of 2 seconds
time.Sleep(2 * time.Second)
}

func TestAddWithFullErrorUnsyncedChain(t *testing.T) {
Expand All @@ -141,9 +141,6 @@ func TestAddWithFullErrorSyncedChain(t *testing.T) {
func TestNewCloseWithError(t *testing.T) {
pool := newPoolWithParams(LIMIT, LIMIT_PER_ACCOUNT, " ", " ", uint64(time.Now().Unix())+10000)
defer pool.Close()

// Add a delay of 2 seconds
time.Sleep(2 * time.Second)
}

func TestDump(t *testing.T) {
Expand All @@ -154,7 +151,7 @@ func TestDump(t *testing.T) {
// Create and add transactions to the pool
txsToAdd := make(tx.Transactions, 0, 5)
for i := 0; i < 5; i++ {
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[i%len(genesis.DevAccounts())])
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)])
txsToAdd = append(txsToAdd, tx)
assert.Nil(t, pool.Add(tx))
}
Expand Down Expand Up @@ -183,7 +180,7 @@ func TestRemove(t *testing.T) {
defer pool.Close()

// Create and add a transaction to the pool
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0])
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
assert.Nil(t, pool.Add(tx), "Adding transaction should not produce error")

// Ensure the transaction is in the pool
Expand All @@ -202,7 +199,7 @@ func TestRemoveWithError(t *testing.T) {
defer pool.Close()

// Create and add a transaction to the pool
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0])
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
// assert.Nil(t, pool.Add(tx), "Adding transaction should not produce error")

// Ensure the transaction is in the pool
Expand Down Expand Up @@ -245,7 +242,7 @@ func TestSubscribeNewTx(t *testing.T) {

pool.SubscribeTxEvent(txCh)

tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0])
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
assert.Nil(t, pool.Add(tx))

v := true
Expand All @@ -261,7 +258,7 @@ func TestWashTxs(t *testing.T) {
assert.Zero(t, len(txs))
assert.Zero(t, len(pool.Executables()))

tx1 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[0])
tx1 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
assert.Nil(t, pool.AddLocal(tx1)) // this tx won't participate in the wash out.

txs, _, err = pool.wash(pool.repo.BestBlockSummary())
Expand All @@ -284,11 +281,11 @@ func TestWashTxs(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, Tx.Transactions{tx1}, txs)

tx2 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[1])
tx2 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[1])
txObj2, _ := resolveTx(tx2, false)
assert.Nil(t, pool.all.Add(txObj2, LIMIT_PER_ACCOUNT)) // this tx will participate in the wash out.

tx3 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[2])
tx3 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[2])
txObj3, _ := resolveTx(tx3, false)
assert.Nil(t, pool.all.Add(txObj3, LIMIT_PER_ACCOUNT)) // this tx will participate in the wash out.

Expand All @@ -305,7 +302,7 @@ func TestFillPool(t *testing.T) {
// Create a slice of transactions to be added to the pool.
txs := make(Tx.Transactions, 0, 5)
for i := 0; i < 5; i++ {
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), genesis.DevAccounts()[i%len(genesis.DevAccounts())])
tx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[i%len(devAccounts)])
txs = append(txs, tx)
}

Expand All @@ -321,6 +318,11 @@ func TestFillPool(t *testing.T) {
// Further checks can be made based on the behavior of your TxPool implementation.
// For example, checking if the pool size has increased by the expected amount.
assert.Equal(t, len(txs), pool.all.Len(), "Number of transactions in the pool should match the number added")

// Test executables after wash
executables, _, _ := pool.wash(pool.repo.BestBlockSummary())
pool.executables.Store(executables)
assert.Equal(t, len(txs), len(pool.Executables()), "Number of transactions in the pool should match the number added")
}

func TestAdd(t *testing.T) {
Expand All @@ -341,7 +343,7 @@ func TestAdd(t *testing.T) {
Build().WithSignature(sig[:])
pool.repo.AddBlock(b1, nil, 0)
pool.repo.SetBestBlockID(b1.Header().ID())
acc := genesis.DevAccounts()[0]
acc := devAccounts[0]

dupTx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc)

Expand Down Expand Up @@ -371,6 +373,9 @@ func TestAdd(t *testing.T) {
t.Error(err)
}

var data [64 * 1024]byte
rand.Read(data[:])

tests = []struct {
tx *Tx.Transaction
errStr string
Expand All @@ -379,6 +384,7 @@ func TestAdd(t *testing.T) {
{newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(100), 100, nil, Tx.Features(0), acc), "tx rejected: block ref out of schedule"},
{newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(0), acc), "tx rejected: tx is not executable"},
{newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(2), acc), "tx rejected: unsupported features"},
{newTx(pool.repo.ChainTag(), []*tx.Clause{tx.NewClause(nil).WithData(data[:])}, 21000, tx.BlockRef{}, 100, &thor.Bytes32{1}, Tx.Features(0), acc), "tx rejected: size too large"},
{badReserved, "tx rejected: unsupported features"},
}

Expand All @@ -398,7 +404,7 @@ func TestBeforeVIP191Add(t *testing.T) {

chain := newChainRepo(db)

acc := genesis.DevAccounts()[0]
acc := devAccounts[0]

pool := New(chain, state.NewStater(db), Options{
Limit: 10,
Expand All @@ -411,3 +417,168 @@ func TestBeforeVIP191Add(t *testing.T) {

assert.Equal(t, "tx rejected: unsupported features", err.Error())
}

func TestPoolLimit(t *testing.T) {
// synced
pool := newPoolWithParams(2, 1, "", "", uint64(time.Now().Unix()))
defer pool.Close()

trx1 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
trx2 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
pool.add(trx1, false, false)

err := pool.add(trx2, false, false)
assert.Equal(t, "tx rejected: account quota exceeded", err.Error())

// not synced
pool = newPool(2, 1)

trx1 = newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
trx2 = newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
pool.add(trx1, false, false)
err = pool.add(trx2, false, false)
assert.Equal(t, "tx rejected: account quota exceeded", err.Error())
}

func TestBlocked(t *testing.T) {
acc := devAccounts[len(devAccounts)-1]

file, err := os.CreateTemp("", "blocklist*")
assert.Nil(t, err)
file.WriteString(acc.Address.String())
file.Close()

pool := newPoolWithParams(LIMIT, LIMIT_PER_ACCOUNT, file.Name(), "", uint64(time.Now().Unix()))
defer pool.Close()
<-time.After(10 * time.Millisecond)

// adding blocked should return nil
trx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[len(devAccounts)-1])
err = pool.Add(trx)
assert.Nil(t, err)

// added into all, will be washed out
txObj, err := resolveTx(trx, false)
assert.Nil(t, err)
pool.all.Add(txObj, LIMIT_PER_ACCOUNT)

pool.wash(pool.repo.BestBlockSummary())
got := pool.Get(trx.ID())
assert.Nil(t, got)

os.Remove(file.Name())
}

func TestWash(t *testing.T) {
pool := newPool(LIMIT, LIMIT_PER_ACCOUNT)
defer pool.Close()

tests := []struct {
name string
testFunc func(*testing.T)
}{
{
"MaxLife", func(t *testing.T) {
trx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[len(devAccounts)-1])
pool.add(trx, false, false)

txObj := pool.all.mapByID[trx.ID()]
txObj.timeAdded = txObj.timeAdded - int64(pool.options.MaxLifetime)*2

pool.wash(pool.repo.BestBlockSummary())
got := pool.Get(trx.ID())
assert.Nil(t, got)
},
},
{
"Not enough VTHO", func(t *testing.T) {
priv, err := crypto.GenerateKey()
assert.Nil(t, err)

acc := genesis.DevAccount{
Address: thor.Address(crypto.PubkeyToAddress(priv.PublicKey)),
PrivateKey: priv,
}

trx := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), acc)

txObj, err := resolveTx(trx, false)
assert.Nil(t, err)
pool.all.Add(txObj, LIMIT_PER_ACCOUNT)

pool.wash(pool.repo.BestBlockSummary())
got := pool.Get(trx.ID())
assert.Nil(t, got)
},
},
{
"Future tx", func(t *testing.T) {
pool := newPool(1, LIMIT_PER_ACCOUNT)
defer pool.Close()

priv, err := crypto.GenerateKey()
assert.Nil(t, err)

acc := genesis.DevAccount{
Address: thor.Address(crypto.PubkeyToAddress(priv.PublicKey)),
PrivateKey: priv,
}

trx1 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
trx2 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
trx3 := newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), acc)
pool.add(trx1, false, false)

txObj, err := resolveTx(trx2, false)
assert.Nil(t, err)
pool.all.Add(txObj, LIMIT_PER_ACCOUNT)

txObj, err = resolveTx(trx3, false)
assert.Nil(t, err)
pool.all.Add(txObj, LIMIT_PER_ACCOUNT)

pool.wash(pool.repo.BestBlockSummary())
got := pool.Get(trx3.ID())
assert.Nil(t, got)
},
},
{
"Executable + Non executable beyond limit", func(t *testing.T) {
pool := newPool(1, LIMIT_PER_ACCOUNT)
defer pool.Close()

priv, err := crypto.GenerateKey()
assert.Nil(t, err)

acc := genesis.DevAccount{
Address: thor.Address(crypto.PubkeyToAddress(priv.PublicKey)),
PrivateKey: priv,
}

trx1 := newTx(pool.repo.ChainTag(), nil, 21000, tx.BlockRef{}, 100, nil, tx.Features(0), devAccounts[0])
trx2 := newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), devAccounts[0])
trx3 := newTx(pool.repo.ChainTag(), nil, 21000, tx.NewBlockRef(pool.repo.BestBlockSummary().Header.Number()+10), 100, nil, tx.Features(0), acc)
pool.add(trx1, false, false)

txObj, err := resolveTx(trx2, false)
assert.Nil(t, err)
pool.all.Add(txObj, LIMIT_PER_ACCOUNT)

txObj, err = resolveTx(trx3, false)
assert.Nil(t, err)
pool.all.Add(txObj, LIMIT_PER_ACCOUNT)

pool.wash(pool.repo.BestBlockSummary())
// all non executable should be washed out
got := pool.Get(trx2.ID())
assert.Nil(t, got)
got = pool.Get(trx3.ID())
assert.Nil(t, got)
},
},
}

for _, tt := range tests {
t.Run(tt.name, tt.testFunc)
}
}

0 comments on commit 33c1ff2

Please sign in to comment.