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