github.com/516108736/tendermint@v0.36.0/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/tendermint/tendermint/abci/example/code" 16 abci "github.com/tendermint/tendermint/abci/types" 17 mempl "github.com/tendermint/tendermint/mempool" 18 sm "github.com/tendermint/tendermint/state" 19 "github.com/tendermint/tendermint/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) 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) 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 }