github.com/vipernet-xyz/tm@v0.34.24/test/maverick/consensus/wal_generator.go (about) 1 package consensus 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "path/filepath" 9 "testing" 10 "time" 11 12 db "github.com/tendermint/tm-db" 13 14 "github.com/vipernet-xyz/tm/abci/example/kvstore" 15 cfg "github.com/vipernet-xyz/tm/config" 16 tmcon "github.com/vipernet-xyz/tm/consensus" 17 "github.com/vipernet-xyz/tm/libs/log" 18 tmrand "github.com/vipernet-xyz/tm/libs/rand" 19 "github.com/vipernet-xyz/tm/privval" 20 "github.com/vipernet-xyz/tm/proxy" 21 sm "github.com/vipernet-xyz/tm/state" 22 "github.com/vipernet-xyz/tm/store" 23 "github.com/vipernet-xyz/tm/types" 24 ) 25 26 // WALGenerateNBlocks generates a consensus WAL. It does this by spinning up a 27 // stripped down version of node (proxy app, event bus, consensus state) with a 28 // persistent kvstore application and special consensus wal instance 29 // (byteBufferWAL) and waits until numBlocks are created. 30 // If the node fails to produce given numBlocks, it returns an error. 31 func WALGenerateNBlocks(t *testing.T, wr io.Writer, numBlocks int) (err error) { 32 config := getConfig(t) 33 34 app := kvstore.NewPersistentKVStoreApplication(filepath.Join(config.DBDir(), "wal_generator")) 35 36 logger := log.TestingLogger().With("wal_generator", "wal_generator") 37 logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks) 38 39 // /////////////////////////////////////////////////////////////////////////// 40 // COPY PASTE FROM node.go WITH A FEW MODIFICATIONS 41 // NOTE: we can't import node package because of circular dependency. 42 // NOTE: we don't do handshake so need to set state.Version.Consensus.App directly. 43 privValidatorKeyFile := config.PrivValidatorKeyFile() 44 privValidatorStateFile := config.PrivValidatorStateFile() 45 privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile) 46 genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) 47 if err != nil { 48 return fmt.Errorf("failed to read genesis file: %w", err) 49 } 50 blockStoreDB := db.NewMemDB() 51 stateDB := blockStoreDB 52 stateStore := sm.NewStore(stateDB, sm.StoreOptions{ 53 DiscardABCIResponses: false, 54 }) 55 state, err := sm.MakeGenesisState(genDoc) 56 if err != nil { 57 return fmt.Errorf("failed to make genesis state: %w", err) 58 } 59 state.Version.Consensus.App = kvstore.ProtocolVersion 60 if err = stateStore.Save(state); err != nil { 61 t.Error(err) 62 } 63 64 blockStore := store.NewBlockStore(blockStoreDB) 65 66 proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app)) 67 proxyApp.SetLogger(logger.With("module", "proxy")) 68 if err := proxyApp.Start(); err != nil { 69 return fmt.Errorf("failed to start proxy app connections: %w", err) 70 } 71 t.Cleanup(func() { 72 if err := proxyApp.Stop(); err != nil { 73 t.Error(err) 74 } 75 }) 76 77 eventBus := types.NewEventBus() 78 eventBus.SetLogger(logger.With("module", "events")) 79 if err := eventBus.Start(); err != nil { 80 return fmt.Errorf("failed to start event bus: %w", err) 81 } 82 t.Cleanup(func() { 83 if err := eventBus.Stop(); err != nil { 84 t.Error(err) 85 } 86 }) 87 mempool := emptyMempool{} 88 evpool := sm.EmptyEvidencePool{} 89 blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) 90 consensusState := NewState(config.Consensus, state.Copy(), 91 blockExec, blockStore, mempool, evpool, map[int64]Misbehavior{}) 92 consensusState.SetLogger(logger) 93 consensusState.SetEventBus(eventBus) 94 if privValidator != nil { 95 consensusState.SetPrivValidator(privValidator) 96 } 97 // END OF COPY PASTE 98 // /////////////////////////////////////////////////////////////////////////// 99 100 // set consensus wal to buffered WAL, which will write all incoming msgs to buffer 101 numBlocksWritten := make(chan struct{}) 102 wal := newByteBufferWAL(logger, NewWALEncoder(wr), int64(numBlocks), numBlocksWritten) 103 // see wal.go#103 104 if err := wal.Write(tmcon.EndHeightMessage{Height: 0}); err != nil { 105 t.Error(err) 106 } 107 108 consensusState.wal = wal 109 110 if err := consensusState.Start(); err != nil { 111 return fmt.Errorf("failed to start consensus state: %w", err) 112 } 113 114 select { 115 case <-numBlocksWritten: 116 if err := consensusState.Stop(); err != nil { 117 t.Error(err) 118 } 119 return nil 120 case <-time.After(1 * time.Minute): 121 if err := consensusState.Stop(); err != nil { 122 t.Error(err) 123 } 124 return fmt.Errorf("waited too long for tendermint to produce %d blocks (grep logs for `wal_generator`)", numBlocks) 125 } 126 } 127 128 // WALWithNBlocks returns a WAL content with numBlocks. 129 func WALWithNBlocks(t *testing.T, numBlocks int) (data []byte, err error) { 130 var b bytes.Buffer 131 wr := bufio.NewWriter(&b) 132 133 if err := WALGenerateNBlocks(t, wr, numBlocks); err != nil { 134 return []byte{}, err 135 } 136 137 wr.Flush() 138 return b.Bytes(), nil 139 } 140 141 func randPort() int { 142 // returns between base and base + spread 143 base, spread := 20000, 20000 144 return base + tmrand.Intn(spread) 145 } 146 147 func makeAddrs() (string, string, string) { 148 start := randPort() 149 return fmt.Sprintf("tcp://127.0.0.1:%d", start), 150 fmt.Sprintf("tcp://127.0.0.1:%d", start+1), 151 fmt.Sprintf("tcp://127.0.0.1:%d", start+2) 152 } 153 154 // getConfig returns a config for test cases 155 func getConfig(t *testing.T) *cfg.Config { 156 c := cfg.ResetTestRoot(t.Name()) 157 158 // and we use random ports to run in parallel 159 tm, rpc, grpc := makeAddrs() 160 c.P2P.ListenAddress = tm 161 c.RPC.ListenAddress = rpc 162 c.RPC.GRPCListenAddress = grpc 163 return c 164 } 165 166 // byteBufferWAL is a WAL which writes all msgs to a byte buffer. Writing stops 167 // when the heightToStop is reached. Client will be notified via 168 // signalWhenStopsTo channel. 169 type byteBufferWAL struct { 170 enc *WALEncoder 171 stopped bool 172 heightToStop int64 173 signalWhenStopsTo chan<- struct{} 174 175 logger log.Logger 176 } 177 178 // needed for determinism 179 var fixedTime, _ = time.Parse(time.RFC3339, "2017-01-02T15:04:05Z") 180 181 func newByteBufferWAL(logger log.Logger, enc *WALEncoder, nBlocks int64, signalStop chan<- struct{}) *byteBufferWAL { 182 return &byteBufferWAL{ 183 enc: enc, 184 heightToStop: nBlocks, 185 signalWhenStopsTo: signalStop, 186 logger: logger, 187 } 188 } 189 190 // Save writes message to the internal buffer except when heightToStop is 191 // reached, in which case it will signal the caller via signalWhenStopsTo and 192 // skip writing. 193 func (w *byteBufferWAL) Write(m tmcon.WALMessage) error { 194 if w.stopped { 195 w.logger.Debug("WAL already stopped. Not writing message", "msg", m) 196 return nil 197 } 198 199 if endMsg, ok := m.(tmcon.EndHeightMessage); ok { 200 w.logger.Debug("WAL write end height message", "height", endMsg.Height, "stopHeight", w.heightToStop) 201 if endMsg.Height == w.heightToStop { 202 w.logger.Debug("Stopping WAL at height", "height", endMsg.Height) 203 w.signalWhenStopsTo <- struct{}{} 204 w.stopped = true 205 return nil 206 } 207 } 208 209 w.logger.Debug("WAL Write Message", "msg", m) 210 err := w.enc.Encode(&tmcon.TimedWALMessage{Time: fixedTime, Msg: m}) 211 if err != nil { 212 panic(fmt.Sprintf("failed to encode the msg %v", m)) 213 } 214 215 return nil 216 } 217 218 func (w *byteBufferWAL) WriteSync(m tmcon.WALMessage) error { 219 return w.Write(m) 220 } 221 222 func (w *byteBufferWAL) FlushAndSync() error { return nil } 223 224 func (w *byteBufferWAL) SearchForEndHeight( 225 height int64, 226 options *tmcon.WALSearchOptions) (rd io.ReadCloser, found bool, err error) { 227 return nil, false, nil 228 } 229 230 func (w *byteBufferWAL) Start() error { return nil } 231 func (w *byteBufferWAL) Stop() error { return nil } 232 func (w *byteBufferWAL) Wait() {}