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 }