bitbucket.org/number571/tendermint@v0.8.14/internal/consensus/wal_generator.go (about) 1 package consensus 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 mrand "math/rand" 9 "path/filepath" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/require" 14 db "github.com/tendermint/tm-db" 15 16 "bitbucket.org/number571/tendermint/abci/example/kvstore" 17 cfg "bitbucket.org/number571/tendermint/config" 18 "bitbucket.org/number571/tendermint/libs/log" 19 "bitbucket.org/number571/tendermint/privval" 20 "bitbucket.org/number571/tendermint/proxy" 21 sm "bitbucket.org/number571/tendermint/state" 22 "bitbucket.org/number571/tendermint/store" 23 "bitbucket.org/number571/tendermint/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 t.Cleanup(func() { require.NoError(t, app.Close()) }) 36 37 logger := log.TestingLogger().With("wal_generator", "wal_generator") 38 logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks) 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.PrivValidator.KeyFile() 44 privValidatorStateFile := config.PrivValidator.StateFile() 45 privValidator, err := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile) 46 if err != nil { 47 return err 48 } 49 genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) 50 if err != nil { 51 return fmt.Errorf("failed to read genesis file: %w", err) 52 } 53 blockStoreDB := db.NewMemDB() 54 stateDB := blockStoreDB 55 stateStore := sm.NewStore(stateDB) 56 state, err := sm.MakeGenesisState(genDoc) 57 if err != nil { 58 return fmt.Errorf("failed to make genesis state: %w", err) 59 } 60 state.Version.Consensus.App = kvstore.ProtocolVersion 61 if err = stateStore.Save(state); err != nil { 62 t.Error(err) 63 } 64 65 blockStore := store.NewBlockStore(blockStoreDB) 66 67 proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app)) 68 proxyApp.SetLogger(logger.With("module", "proxy")) 69 if err := proxyApp.Start(); err != nil { 70 return fmt.Errorf("failed to start proxy app connections: %w", err) 71 } 72 t.Cleanup(func() { 73 if err := proxyApp.Stop(); err != nil { 74 t.Error(err) 75 } 76 }) 77 78 eventBus := types.NewEventBus() 79 eventBus.SetLogger(logger.With("module", "events")) 80 if err := eventBus.Start(); err != nil { 81 return fmt.Errorf("failed to start event bus: %w", err) 82 } 83 t.Cleanup(func() { 84 if err := eventBus.Stop(); err != nil { 85 t.Error(err) 86 } 87 }) 88 mempool := emptyMempool{} 89 evpool := sm.EmptyEvidencePool{} 90 blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool, blockStore) 91 consensusState := NewState(config.Consensus, state.Copy(), blockExec, blockStore, mempool, evpool) 92 consensusState.SetLogger(logger) 93 consensusState.SetEventBus(eventBus) 94 if privValidator != nil && privValidator != (*privval.FilePV)(nil) { 95 consensusState.SetPrivValidator(privValidator) 96 } 97 // END OF COPY PASTE 98 99 // set consensus wal to buffered WAL, which will write all incoming msgs to buffer 100 numBlocksWritten := make(chan struct{}) 101 wal := newByteBufferWAL(logger, NewWALEncoder(wr), int64(numBlocks), numBlocksWritten) 102 // see wal.go#103 103 if err := wal.Write(EndHeightMessage{0}); err != nil { 104 t.Error(err) 105 } 106 107 consensusState.wal = wal 108 109 if err := consensusState.Start(); err != nil { 110 return fmt.Errorf("failed to start consensus state: %w", err) 111 } 112 113 select { 114 case <-numBlocksWritten: 115 if err := consensusState.Stop(); err != nil { 116 t.Error(err) 117 } 118 return nil 119 case <-time.After(1 * time.Minute): 120 if err := consensusState.Stop(); err != nil { 121 t.Error(err) 122 } 123 return fmt.Errorf("waited too long for tendermint to produce %d blocks (grep logs for `wal_generator`)", numBlocks) 124 } 125 } 126 127 // WALWithNBlocks returns a WAL content with numBlocks. 128 func WALWithNBlocks(t *testing.T, numBlocks int) (data []byte, err error) { 129 var b bytes.Buffer 130 wr := bufio.NewWriter(&b) 131 132 if err := WALGenerateNBlocks(t, wr, numBlocks); err != nil { 133 return []byte{}, err 134 } 135 136 wr.Flush() 137 return b.Bytes(), nil 138 } 139 140 func randPort() int { 141 // returns between base and base + spread 142 base, spread := 20000, 20000 143 // nolint:gosec // G404: Use of weak random number generator 144 return base + mrand.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 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.(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(&TimedWALMessage{fixedTime, 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 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 *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() {}