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 }