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