github.com/evdatsion/aphelion-dpos-bft@v0.32.1/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  
    12  	"github.com/evdatsion/aphelion-dpos-bft/abci/example/code"
    13  	abci "github.com/evdatsion/aphelion-dpos-bft/abci/types"
    14  	dbm "github.com/evdatsion/aphelion-dpos-bft/libs/db"
    15  	mempl "github.com/evdatsion/aphelion-dpos-bft/mempool"
    16  	sm "github.com/evdatsion/aphelion-dpos-bft/state"
    17  	"github.com/evdatsion/aphelion-dpos-bft/types"
    18  )
    19  
    20  // for testing
    21  func assertMempool(txn txNotifier) mempl.Mempool {
    22  	return txn.(mempl.Mempool)
    23  }
    24  
    25  func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) {
    26  	config := ResetConfig("consensus_mempool_txs_available_test")
    27  	defer os.RemoveAll(config.RootDir)
    28  	config.Consensus.CreateEmptyBlocks = false
    29  	state, privVals := randGenesisState(1, false, 10)
    30  	cs := newConsensusStateWithConfig(config, state, privVals[0], NewCounterApplication())
    31  	assertMempool(cs.txNotifier).EnableTxsAvailable()
    32  	height, round := cs.Height, cs.Round
    33  	newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)
    34  	startTestRound(cs, height, round)
    35  
    36  	ensureNewEventOnChannel(newBlockCh) // first block gets committed
    37  	ensureNoNewEventOnChannel(newBlockCh)
    38  	deliverTxsRange(cs, 0, 1)
    39  	ensureNewEventOnChannel(newBlockCh) // commit txs
    40  	ensureNewEventOnChannel(newBlockCh) // commit updated app hash
    41  	ensureNoNewEventOnChannel(newBlockCh)
    42  }
    43  
    44  func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) {
    45  	config := ResetConfig("consensus_mempool_txs_available_test")
    46  	defer os.RemoveAll(config.RootDir)
    47  	config.Consensus.CreateEmptyBlocksInterval = ensureTimeout
    48  	state, privVals := randGenesisState(1, false, 10)
    49  	cs := newConsensusStateWithConfig(config, state, privVals[0], NewCounterApplication())
    50  	assertMempool(cs.txNotifier).EnableTxsAvailable()
    51  	height, round := cs.Height, cs.Round
    52  	newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)
    53  	startTestRound(cs, height, round)
    54  
    55  	ensureNewEventOnChannel(newBlockCh)   // first block gets committed
    56  	ensureNoNewEventOnChannel(newBlockCh) // then we dont make a block ...
    57  	ensureNewEventOnChannel(newBlockCh)   // until the CreateEmptyBlocksInterval has passed
    58  }
    59  
    60  func TestMempoolProgressInHigherRound(t *testing.T) {
    61  	config := ResetConfig("consensus_mempool_txs_available_test")
    62  	defer os.RemoveAll(config.RootDir)
    63  	config.Consensus.CreateEmptyBlocks = false
    64  	state, privVals := randGenesisState(1, false, 10)
    65  	cs := newConsensusStateWithConfig(config, state, privVals[0], NewCounterApplication())
    66  	assertMempool(cs.txNotifier).EnableTxsAvailable()
    67  	height, round := cs.Height, cs.Round
    68  	newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)
    69  	newRoundCh := subscribe(cs.eventBus, types.EventQueryNewRound)
    70  	timeoutCh := subscribe(cs.eventBus, types.EventQueryTimeoutPropose)
    71  	cs.setProposal = func(proposal *types.Proposal) error {
    72  		if cs.Height == 2 && cs.Round == 0 {
    73  			// dont set the proposal in round 0 so we timeout and
    74  			// go to next round
    75  			cs.Logger.Info("Ignoring set proposal at height 2, round 0")
    76  			return nil
    77  		}
    78  		return cs.defaultSetProposal(proposal)
    79  	}
    80  	startTestRound(cs, height, round)
    81  
    82  	ensureNewRound(newRoundCh, height, round) // first round at first height
    83  	ensureNewEventOnChannel(newBlockCh)       // first block gets committed
    84  
    85  	height = height + 1 // moving to the next height
    86  	round = 0
    87  
    88  	ensureNewRound(newRoundCh, height, round) // first round at next height
    89  	deliverTxsRange(cs, 0, 1)                 // we deliver txs, but dont set a proposal so we get the next round
    90  	ensureNewTimeout(timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds())
    91  
    92  	round = round + 1                         // moving to the next round
    93  	ensureNewRound(newRoundCh, height, round) // wait for the next round
    94  	ensureNewEventOnChannel(newBlockCh)       // now we can commit the block
    95  }
    96  
    97  func deliverTxsRange(cs *ConsensusState, start, end int) {
    98  	// Deliver some txs.
    99  	for i := start; i < end; i++ {
   100  		txBytes := make([]byte, 8)
   101  		binary.BigEndian.PutUint64(txBytes, uint64(i))
   102  		err := assertMempool(cs.txNotifier).CheckTx(txBytes, nil)
   103  		if err != nil {
   104  			panic(fmt.Sprintf("Error after CheckTx: %v", err))
   105  		}
   106  	}
   107  }
   108  
   109  func TestMempoolTxConcurrentWithCommit(t *testing.T) {
   110  	state, privVals := randGenesisState(1, false, 10)
   111  	blockDB := dbm.NewMemDB()
   112  	cs := newConsensusStateWithConfigAndBlockStore(config, state, privVals[0], NewCounterApplication(), blockDB)
   113  	sm.SaveState(blockDB, state)
   114  	height, round := cs.Height, cs.Round
   115  	newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock)
   116  
   117  	NTxs := 3000
   118  	go deliverTxsRange(cs, 0, NTxs)
   119  
   120  	startTestRound(cs, height, round)
   121  	for nTxs := 0; nTxs < NTxs; {
   122  		ticker := time.NewTicker(time.Second * 30)
   123  		select {
   124  		case msg := <-newBlockCh:
   125  			blockEvent := msg.Data().(types.EventDataNewBlock)
   126  			nTxs += int(blockEvent.Block.Header.NumTxs)
   127  		case <-ticker.C:
   128  			panic("Timed out waiting 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 := newConsensusStateWithConfigAndBlockStore(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()
   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.Fatalf("expected checktx to return bad nonce, got %v", r)
   159  			}
   160  			checkTxRespCh <- struct{}{}
   161  		})
   162  		if err != nil {
   163  			t.Fatalf("Error after CheckTx: %v", err)
   164  		}
   165  
   166  		// check for the tx
   167  		for {
   168  			txs := assertMempool(cs.txNotifier).ReapMaxBytesMaxGas(int64(len(txBytes)), -1)
   169  			if len(txs) == 0 {
   170  				emptyMempoolCh <- struct{}{}
   171  				return
   172  			}
   173  			time.Sleep(10 * time.Millisecond)
   174  		}
   175  	}()
   176  
   177  	// Wait until the tx returns
   178  	ticker := time.After(time.Second * 5)
   179  	select {
   180  	case <-checkTxRespCh:
   181  		// success
   182  	case <-ticker:
   183  		t.Fatalf("Timed out waiting for tx to return")
   184  	}
   185  
   186  	// Wait until the tx is removed
   187  	ticker = time.After(time.Second * 5)
   188  	select {
   189  	case <-emptyMempoolCh:
   190  		// success
   191  	case <-ticker:
   192  		t.Fatalf("Timed out waiting for tx to be removed")
   193  	}
   194  }
   195  
   196  // CounterApplication that maintains a mempool state and resets it upon commit
   197  type CounterApplication struct {
   198  	abci.BaseApplication
   199  
   200  	txCount        int
   201  	mempoolTxCount int
   202  }
   203  
   204  func NewCounterApplication() *CounterApplication {
   205  	return &CounterApplication{}
   206  }
   207  
   208  func (app *CounterApplication) Info(req abci.RequestInfo) abci.ResponseInfo {
   209  	return abci.ResponseInfo{Data: fmt.Sprintf("txs:%v", app.txCount)}
   210  }
   211  
   212  func (app *CounterApplication) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
   213  	txValue := txAsUint64(req.Tx)
   214  	if txValue != uint64(app.txCount) {
   215  		return abci.ResponseDeliverTx{
   216  			Code: code.CodeTypeBadNonce,
   217  			Log:  fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.txCount, txValue)}
   218  	}
   219  	app.txCount++
   220  	return abci.ResponseDeliverTx{Code: code.CodeTypeOK}
   221  }
   222  
   223  func (app *CounterApplication) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
   224  	txValue := txAsUint64(req.Tx)
   225  	if txValue != uint64(app.mempoolTxCount) {
   226  		return abci.ResponseCheckTx{
   227  			Code: code.CodeTypeBadNonce,
   228  			Log:  fmt.Sprintf("Invalid nonce. Expected %v, got %v", app.mempoolTxCount, txValue)}
   229  	}
   230  	app.mempoolTxCount++
   231  	return abci.ResponseCheckTx{Code: code.CodeTypeOK}
   232  }
   233  
   234  func txAsUint64(tx []byte) uint64 {
   235  	tx8 := make([]byte, 8)
   236  	copy(tx8[len(tx8)-len(tx):], tx)
   237  	return binary.BigEndian.Uint64(tx8)
   238  }
   239  
   240  func (app *CounterApplication) Commit() abci.ResponseCommit {
   241  	app.mempoolTxCount = app.txCount
   242  	if app.txCount == 0 {
   243  		return abci.ResponseCommit{}
   244  	}
   245  	hash := make([]byte, 8)
   246  	binary.BigEndian.PutUint64(hash, uint64(app.txCount))
   247  	return abci.ResponseCommit{Data: hash}
   248  }