github.com/Finschia/ostracon@v1.1.5/consensus/mempool_test.go (about)

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