github.com/vipernet-xyz/tm@v0.34.24/consensus/mempool_test.go (about)

     1  package consensus
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	dbm "github.com/tendermint/tm-db"
    14  
    15  	"github.com/vipernet-xyz/tm/abci/example/code"
    16  	abci "github.com/vipernet-xyz/tm/abci/types"
    17  	mempl "github.com/vipernet-xyz/tm/mempool"
    18  	sm "github.com/vipernet-xyz/tm/state"
    19  	"github.com/vipernet-xyz/tm/types"
    20  )
    21  
    22  // for testing
    23  func assertMempool(txn txNotifier) mempl.Mempool {
    24  	return txn.(mempl.Mempool)
    25  }
    26  
    27  func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) {
    28  	config := ResetConfig("consensus_mempool_txs_available_test")
    29  	defer os.RemoveAll(config.RootDir)
    30  	config.Consensus.CreateEmptyBlocks = false
    31  	state, privVals := randGenesisState(1, false, 10)
    32  	cs := newStateWithConfig(config, state, privVals[0], NewCounterApplication())
    33  	assertMempool(cs.txNotifier).EnableTxsAvailable()
    34  	height, round := cs.Height, cs.Round
    35  	newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)
    36  	startTestRound(cs, height, round)
    37  
    38  	ensureNewEventOnChannel(newBlockCh) // first block gets committed
    39  	ensureNoNewEventOnChannel(newBlockCh)
    40  	deliverTxsRange(cs, 0, 1)
    41  	ensureNewEventOnChannel(newBlockCh) // commit txs
    42  	ensureNewEventOnChannel(newBlockCh) // commit updated app hash
    43  	ensureNoNewEventOnChannel(newBlockCh)
    44  }
    45  
    46  func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) {
    47  	config := ResetConfig("consensus_mempool_txs_available_test")
    48  	defer os.RemoveAll(config.RootDir)
    49  
    50  	config.Consensus.CreateEmptyBlocksInterval = ensureTimeout
    51  	state, privVals := randGenesisState(1, false, 10)
    52  	cs := newStateWithConfig(config, state, privVals[0], NewCounterApplication())
    53  
    54  	assertMempool(cs.txNotifier).EnableTxsAvailable()
    55  
    56  	newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)
    57  	startTestRound(cs, cs.Height, cs.Round)
    58  
    59  	ensureNewEventOnChannel(newBlockCh)   // first block gets committed
    60  	ensureNoNewEventOnChannel(newBlockCh) // then we dont make a block ...
    61  	ensureNewEventOnChannel(newBlockCh)   // until the CreateEmptyBlocksInterval has passed
    62  }
    63  
    64  func TestMempoolProgressInHigherRound(t *testing.T) {
    65  	config := ResetConfig("consensus_mempool_txs_available_test")
    66  	defer os.RemoveAll(config.RootDir)
    67  	config.Consensus.CreateEmptyBlocks = false
    68  	state, privVals := randGenesisState(1, false, 10)
    69  	cs := newStateWithConfig(config, state, privVals[0], NewCounterApplication())
    70  	assertMempool(cs.txNotifier).EnableTxsAvailable()
    71  	height, round := cs.Height, cs.Round
    72  	newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)
    73  	newRoundCh := subscribe(cs.eventBus, types.EventQueryNewRound)
    74  	timeoutCh := subscribe(cs.eventBus, types.EventQueryTimeoutPropose)
    75  	cs.setProposal = func(proposal *types.Proposal) error {
    76  		if cs.Height == 2 && cs.Round == 0 {
    77  			// dont set the proposal in round 0 so we timeout and
    78  			// go to next round
    79  			cs.Logger.Info("Ignoring set proposal at height 2, round 0")
    80  			return nil
    81  		}
    82  		return cs.defaultSetProposal(proposal)
    83  	}
    84  	startTestRound(cs, height, round)
    85  
    86  	ensureNewRound(newRoundCh, height, round) // first round at first height
    87  	ensureNewEventOnChannel(newBlockCh)       // first block gets committed
    88  
    89  	height++ // moving to the next height
    90  	round = 0
    91  
    92  	ensureNewRound(newRoundCh, height, round) // first round at next height
    93  	deliverTxsRange(cs, 0, 1)                 // we deliver txs, but dont set a proposal so we get the next round
    94  	ensureNewTimeout(timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds())
    95  
    96  	round++                                   // moving to the next round
    97  	ensureNewRound(newRoundCh, height, round) // wait for the next round
    98  	ensureNewEventOnChannel(newBlockCh)       // now we can commit the block
    99  }
   100  
   101  func deliverTxsRange(cs *State, start, end int) {
   102  	// Deliver some txs.
   103  	for i := start; i < end; i++ {
   104  		txBytes := make([]byte, 8)
   105  		binary.BigEndian.PutUint64(txBytes, uint64(i))
   106  		err := assertMempool(cs.txNotifier).CheckTx(txBytes, nil, mempl.TxInfo{})
   107  		if err != nil {
   108  			panic(fmt.Sprintf("Error after CheckTx: %v", err))
   109  		}
   110  	}
   111  }
   112  
   113  func TestMempoolTxConcurrentWithCommit(t *testing.T) {
   114  	state, privVals := randGenesisState(1, false, 10)
   115  	blockDB := dbm.NewMemDB()
   116  	stateStore := sm.NewStore(blockDB, sm.StoreOptions{DiscardABCIResponses: false})
   117  	cs := newStateWithConfigAndBlockStore(config, state, privVals[0], NewCounterApplication(), blockDB)
   118  	err := stateStore.Save(state)
   119  	require.NoError(t, err)
   120  	newBlockHeaderCh := subscribe(cs.eventBus, types.EventQueryNewBlockHeader)
   121  
   122  	const numTxs int64 = 3000
   123  	go deliverTxsRange(cs, 0, int(numTxs))
   124  
   125  	startTestRound(cs, cs.Height, cs.Round)
   126  	for n := int64(0); n < numTxs; {
   127  		select {
   128  		case msg := <-newBlockHeaderCh:
   129  			headerEvent := msg.Data().(types.EventDataNewBlockHeader)
   130  			n += headerEvent.NumTxs
   131  		case <-time.After(30 * time.Second):
   132  			t.Fatal("Timed out waiting 30s to commit blocks with transactions")
   133  		}
   134  	}
   135  }
   136  
   137  func TestMempoolRmBadTx(t *testing.T) {
   138  	state, privVals := randGenesisState(1, false, 10)
   139  	app := NewCounterApplication()
   140  	blockDB := dbm.NewMemDB()
   141  	stateStore := sm.NewStore(blockDB, sm.StoreOptions{DiscardABCIResponses: false})
   142  	cs := newStateWithConfigAndBlockStore(config, state, privVals[0], app, blockDB)
   143  	err := stateStore.Save(state)
   144  	require.NoError(t, err)
   145  
   146  	// increment the counter by 1
   147  	txBytes := make([]byte, 8)
   148  	binary.BigEndian.PutUint64(txBytes, uint64(0))
   149  
   150  	resDeliver := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
   151  	assert.False(t, resDeliver.IsErr(), fmt.Sprintf("expected no error. got %v", resDeliver))
   152  
   153  	resCommit := app.Commit()
   154  	assert.True(t, len(resCommit.Data) > 0)
   155  
   156  	emptyMempoolCh := make(chan struct{})
   157  	checkTxRespCh := make(chan struct{})
   158  	go func() {
   159  		// Try to send the tx through the mempool.
   160  		// CheckTx should not err, but the app should return a bad abci code
   161  		// and the tx should get removed from the pool
   162  		err := assertMempool(cs.txNotifier).CheckTx(txBytes, func(r *abci.Response) {
   163  			if r.GetCheckTx().Code != code.CodeTypeBadNonce {
   164  				t.Errorf("expected checktx to return bad nonce, got %v", r)
   165  				return
   166  			}
   167  			checkTxRespCh <- struct{}{}
   168  		}, mempl.TxInfo{})
   169  		if err != nil {
   170  			t.Errorf("error after CheckTx: %v", err)
   171  			return
   172  		}
   173  
   174  		// check for the tx
   175  		for {
   176  			txs := assertMempool(cs.txNotifier).ReapMaxBytesMaxGas(int64(len(txBytes)), -1)
   177  			if len(txs) == 0 {
   178  				emptyMempoolCh <- struct{}{}
   179  				return
   180  			}
   181  			time.Sleep(10 * time.Millisecond)
   182  		}
   183  	}()
   184  
   185  	// Wait until the tx returns
   186  	ticker := time.After(time.Second * 5)
   187  	select {
   188  	case <-checkTxRespCh:
   189  		// success
   190  	case <-ticker:
   191  		t.Errorf("timed out waiting for tx to return")
   192  		return
   193  	}
   194  
   195  	// Wait until the tx is removed
   196  	ticker = time.After(time.Second * 5)
   197  	select {
   198  	case <-emptyMempoolCh:
   199  		// success
   200  	case <-ticker:
   201  		t.Errorf("timed out waiting for tx to be removed")
   202  		return
   203  	}
   204  }
   205  
   206  // CounterApplication that maintains a mempool state and resets it upon commit
   207  type CounterApplication struct {
   208  	abci.BaseApplication
   209  
   210  	txCount        int
   211  	mempoolTxCount int
   212  }
   213  
   214  func NewCounterApplication() *CounterApplication {
   215  	return &CounterApplication{}
   216  }
   217  
   218  func (app *CounterApplication) Info(req abci.RequestInfo) abci.ResponseInfo {
   219  	return abci.ResponseInfo{Data: fmt.Sprintf("txs:%v", app.txCount)}
   220  }
   221  
   222  func (app *CounterApplication) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
   223  	txValue := txAsUint64(req.Tx)
   224  	if txValue != uint64(app.txCount) {
   225  		return abci.ResponseDeliverTx{
   226  			Code: code.CodeTypeBadNonce,
   227  			Log:  fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.txCount, txValue)}
   228  	}
   229  	app.txCount++
   230  	return abci.ResponseDeliverTx{Code: code.CodeTypeOK}
   231  }
   232  
   233  func (app *CounterApplication) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
   234  	txValue := txAsUint64(req.Tx)
   235  	if txValue != uint64(app.mempoolTxCount) {
   236  		return abci.ResponseCheckTx{
   237  			Code: code.CodeTypeBadNonce,
   238  			Log:  fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.mempoolTxCount, txValue)}
   239  	}
   240  	app.mempoolTxCount++
   241  	return abci.ResponseCheckTx{Code: code.CodeTypeOK}
   242  }
   243  
   244  func txAsUint64(tx []byte) uint64 {
   245  	tx8 := make([]byte, 8)
   246  	copy(tx8[len(tx8)-len(tx):], tx)
   247  	return binary.BigEndian.Uint64(tx8)
   248  }
   249  
   250  func (app *CounterApplication) Commit() abci.ResponseCommit {
   251  	app.mempoolTxCount = app.txCount
   252  	if app.txCount == 0 {
   253  		return abci.ResponseCommit{}
   254  	}
   255  	hash := make([]byte, 8)
   256  	binary.BigEndian.PutUint64(hash, uint64(app.txCount))
   257  	return abci.ResponseCommit{Data: hash}
   258  }