github.com/okex/exchain@v1.8.0/libs/tendermint/consensus/mempool_test.go (about)

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