github.com/vipernet-xyz/tm@v0.34.24/test/maverick/consensus/replay_file.go (about) 1 package consensus 2 3 import ( 4 "bufio" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "strconv" 11 "strings" 12 13 dbm "github.com/tendermint/tm-db" 14 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 tmos "github.com/vipernet-xyz/tm/libs/os" 19 "github.com/vipernet-xyz/tm/proxy" 20 sm "github.com/vipernet-xyz/tm/state" 21 "github.com/vipernet-xyz/tm/store" 22 "github.com/vipernet-xyz/tm/types" 23 ) 24 25 const ( 26 // event bus subscriber 27 subscriber = "replay-file" 28 ) 29 30 //-------------------------------------------------------- 31 // replay messages interactively or all at once 32 33 // replay the wal file 34 func RunReplayFile(config cfg.BaseConfig, csConfig *cfg.ConsensusConfig, console bool) { 35 consensusState := newConsensusStateForReplay(config, csConfig) 36 37 if err := consensusState.ReplayFile(csConfig.WalFile(), console); err != nil { 38 tmos.Exit(fmt.Sprintf("Error during consensus replay: %v", err)) 39 } 40 } 41 42 // Replay msgs in file or start the console 43 func (cs *State) ReplayFile(file string, console bool) error { 44 45 if cs.IsRunning() { 46 return errors.New("cs is already running, cannot replay") 47 } 48 if cs.wal != nil { 49 return errors.New("cs wal is open, cannot replay") 50 } 51 52 cs.startForReplay() 53 54 // ensure all new step events are regenerated as expected 55 56 ctx := context.Background() 57 newStepSub, err := cs.eventBus.Subscribe(ctx, subscriber, types.EventQueryNewRoundStep) 58 if err != nil { 59 return fmt.Errorf("failed to subscribe %s to %v", subscriber, types.EventQueryNewRoundStep) 60 } 61 defer func() { 62 if err := cs.eventBus.Unsubscribe(ctx, subscriber, types.EventQueryNewRoundStep); err != nil { 63 cs.Logger.Error("Error unsubscribing to event bus", "err", err) 64 } 65 }() 66 67 // just open the file for reading, no need to use wal 68 fp, err := os.OpenFile(file, os.O_RDONLY, 0600) 69 if err != nil { 70 return err 71 } 72 73 pb := newPlayback(file, fp, cs, cs.state.Copy()) 74 defer pb.fp.Close() 75 76 var nextN int // apply N msgs in a row 77 var msg *tmcon.TimedWALMessage 78 for { 79 if nextN == 0 && console { 80 nextN = pb.replayConsoleLoop() 81 } 82 83 msg, err = pb.dec.Decode() 84 if err == io.EOF { 85 return nil 86 } else if err != nil { 87 return err 88 } 89 90 if err := pb.cs.readReplayMessage(msg, newStepSub); err != nil { 91 return err 92 } 93 94 if nextN > 0 { 95 nextN-- 96 } 97 pb.count++ 98 } 99 } 100 101 //------------------------------------------------ 102 // playback manager 103 104 type playback struct { 105 cs *State 106 107 fp *os.File 108 dec *WALDecoder 109 count int // how many lines/msgs into the file are we 110 111 // replays can be reset to beginning 112 fileName string // so we can close/reopen the file 113 genesisState sm.State // so the replay session knows where to restart from 114 } 115 116 func newPlayback(fileName string, fp *os.File, cs *State, genState sm.State) *playback { 117 return &playback{ 118 cs: cs, 119 fp: fp, 120 fileName: fileName, 121 genesisState: genState, 122 dec: NewWALDecoder(fp), 123 } 124 } 125 126 // go back count steps by resetting the state and running (pb.count - count) steps 127 func (pb *playback) replayReset(count int, newStepSub types.Subscription) error { 128 if err := pb.cs.Stop(); err != nil { 129 return err 130 } 131 pb.cs.Wait() 132 133 newCS := NewState(pb.cs.config, pb.genesisState.Copy(), pb.cs.blockExec, 134 pb.cs.blockStore, pb.cs.txNotifier, pb.cs.evpool, map[int64]Misbehavior{}) 135 newCS.SetEventBus(pb.cs.eventBus) 136 newCS.startForReplay() 137 138 if err := pb.fp.Close(); err != nil { 139 return err 140 } 141 fp, err := os.OpenFile(pb.fileName, os.O_RDONLY, 0600) 142 if err != nil { 143 return err 144 } 145 pb.fp = fp 146 pb.dec = NewWALDecoder(fp) 147 count = pb.count - count 148 fmt.Printf("Reseting from %d to %d\n", pb.count, count) 149 pb.count = 0 150 pb.cs = newCS 151 var msg *tmcon.TimedWALMessage 152 for i := 0; i < count; i++ { 153 msg, err = pb.dec.Decode() 154 if err == io.EOF { 155 return nil 156 } else if err != nil { 157 return err 158 } 159 if err := pb.cs.readReplayMessage(msg, newStepSub); err != nil { 160 return err 161 } 162 pb.count++ 163 } 164 return nil 165 } 166 167 func (cs *State) startForReplay() { 168 cs.Logger.Error("Replay commands are disabled until someone updates them and writes tests") 169 /* TODO:! 170 // since we replay tocks we just ignore ticks 171 go func() { 172 for { 173 select { 174 case <-cs.tickChan: 175 case <-cs.Quit: 176 return 177 } 178 } 179 }()*/ 180 } 181 182 // console function for parsing input and running commands 183 func (pb *playback) replayConsoleLoop() int { 184 for { 185 fmt.Printf("> ") 186 bufReader := bufio.NewReader(os.Stdin) 187 line, more, err := bufReader.ReadLine() 188 if more { 189 tmos.Exit("input is too long") 190 } else if err != nil { 191 tmos.Exit(err.Error()) 192 } 193 194 tokens := strings.Split(string(line), " ") 195 if len(tokens) == 0 { 196 continue 197 } 198 199 switch tokens[0] { 200 case "next": 201 // "next" -> replay next message 202 // "next N" -> replay next N messages 203 204 if len(tokens) == 1 { 205 return 0 206 } 207 i, err := strconv.Atoi(tokens[1]) 208 if err != nil { 209 fmt.Println("next takes an integer argument") 210 } else { 211 return i 212 } 213 214 case "back": 215 // "back" -> go back one message 216 // "back N" -> go back N messages 217 218 // NOTE: "back" is not supported in the state machine design, 219 // so we restart and replay up to 220 221 ctx := context.Background() 222 // ensure all new step events are regenerated as expected 223 224 newStepSub, err := pb.cs.eventBus.Subscribe(ctx, subscriber, types.EventQueryNewRoundStep) 225 if err != nil { 226 tmos.Exit(fmt.Sprintf("failed to subscribe %s to %v", subscriber, types.EventQueryNewRoundStep)) 227 } 228 defer func() { 229 if err := pb.cs.eventBus.Unsubscribe(ctx, subscriber, types.EventQueryNewRoundStep); err != nil { 230 pb.cs.Logger.Error("Error unsubscribing from eventBus", "err", err) 231 } 232 }() 233 234 if len(tokens) == 1 { 235 if err := pb.replayReset(1, newStepSub); err != nil { 236 pb.cs.Logger.Error("Replay reset error", "err", err) 237 } 238 } else { 239 i, err := strconv.Atoi(tokens[1]) 240 if err != nil { 241 fmt.Println("back takes an integer argument") 242 } else if i > pb.count { 243 fmt.Printf("argument to back must not be larger than the current count (%d)\n", pb.count) 244 } else if err := pb.replayReset(i, newStepSub); err != nil { 245 pb.cs.Logger.Error("Replay reset error", "err", err) 246 } 247 } 248 249 case "rs": 250 // "rs" -> print entire round state 251 // "rs short" -> print height/round/step 252 // "rs <field>" -> print another field of the round state 253 254 rs := pb.cs.RoundState 255 if len(tokens) == 1 { 256 fmt.Println(rs) 257 } else { 258 switch tokens[1] { 259 case "short": 260 fmt.Printf("%v/%v/%v\n", rs.Height, rs.Round, rs.Step) 261 case "validators": 262 fmt.Println(rs.Validators) 263 case "proposal": 264 fmt.Println(rs.Proposal) 265 case "proposal_block": 266 fmt.Printf("%v %v\n", rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort()) 267 case "locked_round": 268 fmt.Println(rs.LockedRound) 269 case "locked_block": 270 fmt.Printf("%v %v\n", rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort()) 271 case "votes": 272 fmt.Println(rs.Votes.StringIndented(" ")) 273 274 default: 275 fmt.Println("Unknown option", tokens[1]) 276 } 277 } 278 case "n": 279 fmt.Println(pb.count) 280 } 281 } 282 } 283 284 //-------------------------------------------------------------------------------- 285 286 // convenience for replay mode 287 func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusConfig) *State { 288 dbType := dbm.BackendType(config.DBBackend) 289 // Get BlockStore 290 blockStoreDB, err := dbm.NewDB("blockstore", dbType, config.DBDir()) 291 if err != nil { 292 tmos.Exit(err.Error()) 293 } 294 blockStore := store.NewBlockStore(blockStoreDB) 295 296 // Get State 297 stateDB, err := dbm.NewDB("state", dbType, config.DBDir()) 298 if err != nil { 299 tmos.Exit(err.Error()) 300 } 301 stateStore := sm.NewStore(stateDB, sm.StoreOptions{ 302 DiscardABCIResponses: false, 303 }) 304 gdoc, err := sm.MakeGenesisDocFromFile(config.GenesisFile()) 305 if err != nil { 306 tmos.Exit(err.Error()) 307 } 308 state, err := sm.MakeGenesisState(gdoc) 309 if err != nil { 310 tmos.Exit(err.Error()) 311 } 312 313 // Create proxyAppConn connection (consensus, mempool, query) 314 clientCreator := proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()) 315 proxyApp := proxy.NewAppConns(clientCreator) 316 err = proxyApp.Start() 317 if err != nil { 318 tmos.Exit(fmt.Sprintf("Error starting proxy app conns: %v", err)) 319 } 320 321 eventBus := types.NewEventBus() 322 if err := eventBus.Start(); err != nil { 323 tmos.Exit(fmt.Sprintf("Failed to start event bus: %v", err)) 324 } 325 326 handshaker := NewHandshaker(stateStore, state, blockStore, gdoc) 327 handshaker.SetEventBus(eventBus) 328 err = handshaker.Handshake(proxyApp) 329 if err != nil { 330 tmos.Exit(fmt.Sprintf("Error on handshake: %v", err)) 331 } 332 333 mempool, evpool := emptyMempool{}, sm.EmptyEvidencePool{} 334 blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) 335 336 consensusState := NewState(csConfig, state.Copy(), blockExec, 337 blockStore, mempool, evpool, map[int64]Misbehavior{}) 338 339 consensusState.SetEventBus(eventBus) 340 return consensusState 341 }