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 }