github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/consensus/replay_file.go (about) 1 package consensus 2 3 import ( 4 "bufio" 5 goerrors "errors" 6 "fmt" 7 "io" 8 "os" 9 "strconv" 10 "strings" 11 12 cstypes "github.com/gnolang/gno/tm2/pkg/bft/consensus/types" 13 sm "github.com/gnolang/gno/tm2/pkg/bft/state" 14 walm "github.com/gnolang/gno/tm2/pkg/bft/wal" 15 "github.com/gnolang/gno/tm2/pkg/errors" 16 "github.com/gnolang/gno/tm2/pkg/events" 17 osm "github.com/gnolang/gno/tm2/pkg/os" 18 ) 19 20 const ( 21 // event bus subscriber 22 subscriber = "replay-file" 23 ) 24 25 // -------------------------------------------------------- 26 // replay messages interactively or all at once 27 28 // Replay msgs in file or start the console 29 func (cs *ConsensusState) ReplayFile(file string, console bool) error { 30 if cs.IsRunning() { 31 return errors.New("cs is already running, cannot replay") 32 } 33 if cs.wal != nil { 34 return errors.New("cs wal is open, cannot replay") 35 } 36 37 cs.startForReplay() 38 39 // ensure all new step events are regenerated as expected 40 41 newStepSub := events.SubscribeToEvent(cs.evsw, subscriber, cstypes.EventNewRoundStep{}) 42 defer cs.evsw.RemoveListener(subscriber) 43 44 // just open the file for reading, no need to use wal 45 fp, err := os.OpenFile(file, os.O_RDONLY, 0o600) 46 if err != nil { 47 return err 48 } 49 50 pb := newPlayback(file, fp, cs, cs.state.Copy()) 51 defer pb.fp.Close() //nolint: errcheck 52 53 var nextN int // apply N msgs in a row 54 var msg *walm.TimedWALMessage 55 var meta *walm.MetaMessage 56 for { 57 if nextN == 0 && console { 58 nextN = pb.replayConsoleLoop() 59 } 60 61 msg, meta, err = pb.dec.ReadMessage() 62 if goerrors.Is(err, io.EOF) { 63 return nil 64 } else if err != nil { 65 return err 66 } 67 68 if err := pb.cs.readReplayMessage(msg, meta, newStepSub); err != nil { 69 return err 70 } 71 72 if nextN > 0 { 73 nextN-- 74 } 75 pb.count++ 76 } 77 } 78 79 // ------------------------------------------------ 80 // playback manager 81 82 type playback struct { 83 cs *ConsensusState 84 85 fp *os.File 86 dec *walm.WALReader 87 count int // how many lines/msgs into the file are we 88 89 // replays can be reset to beginning 90 fileName string // so we can close/reopen the file 91 genesisState sm.State // so the replay session knows where to restart from 92 } 93 94 func newPlayback(fileName string, fp *os.File, cs *ConsensusState, genState sm.State) *playback { 95 return &playback{ 96 cs: cs, 97 fp: fp, 98 fileName: fileName, 99 genesisState: genState, 100 dec: walm.NewWALReader(fp, maxMsgSize), 101 } 102 } 103 104 // go back count steps by resetting the state and running (pb.count - count) steps 105 func (pb *playback) replayReset(count int, newStepSub <-chan events.Event) error { 106 pb.cs.Stop() 107 pb.cs.Wait() 108 109 newCS := NewConsensusState(pb.cs.config, pb.genesisState.Copy(), pb.cs.blockExec, 110 pb.cs.blockStore, pb.cs.txNotifier) 111 newCS.SetEventSwitch(pb.cs.evsw) 112 newCS.startForReplay() 113 114 if err := pb.fp.Close(); err != nil { 115 return err 116 } 117 fp, err := os.OpenFile(pb.fileName, os.O_RDONLY, 0o600) 118 if err != nil { 119 return err 120 } 121 pb.fp = fp 122 pb.dec = walm.NewWALReader(fp, maxMsgSize) 123 count = pb.count - count 124 fmt.Printf("Reseting from %d to %d\n", pb.count, count) 125 pb.count = 0 126 pb.cs = newCS 127 var msg *walm.TimedWALMessage 128 var meta *walm.MetaMessage 129 for i := 0; i < count; i++ { 130 msg, meta, err = pb.dec.ReadMessage() 131 if goerrors.Is(err, io.EOF) { 132 return nil 133 } else if err != nil { 134 return err 135 } 136 if err := pb.cs.readReplayMessage(msg, meta, newStepSub); err != nil { 137 return err 138 } 139 pb.count++ 140 } 141 return nil 142 } 143 144 func (cs *ConsensusState) startForReplay() { 145 cs.Logger.Error("Replay commands are disabled until someone updates them and writes tests") 146 /* TODO:! 147 // since we replay tocks we just ignore ticks 148 go func() { 149 for { 150 select { 151 case <-cs.tickChan: 152 case <-cs.Quit: 153 return 154 } 155 } 156 }()*/ 157 } 158 159 // console function for parsing input and running commands 160 func (pb *playback) replayConsoleLoop() int { 161 for { 162 fmt.Printf("> ") 163 bufReader := bufio.NewReader(os.Stdin) 164 line, more, err := bufReader.ReadLine() 165 if more { 166 osm.Exit("input is too long") 167 } else if err != nil { 168 osm.Exit(err.Error()) 169 } 170 171 tokens := strings.Split(string(line), " ") 172 if len(tokens) == 0 { 173 continue 174 } 175 176 switch tokens[0] { 177 case "next": 178 // "next" -> replay next message 179 // "next N" -> replay next N messages 180 181 if len(tokens) == 1 { 182 return 0 183 } 184 i, err := strconv.Atoi(tokens[1]) 185 if err != nil { 186 fmt.Println("next takes an integer argument") 187 } else { 188 return i 189 } 190 191 case "back": 192 // "back" -> go back one message 193 // "back N" -> go back N messages 194 195 // NOTE: "back" is not supported in the state machine design, 196 // so we restart and replay up to 197 198 // ensure all new step events are regenerated as expected 199 200 newStepSub := events.SubscribeToEvent(pb.cs.evsw, subscriber, cstypes.EventNewRoundStep{}) 201 defer pb.cs.evsw.RemoveListener(subscriber) 202 203 if len(tokens) == 1 { 204 if err := pb.replayReset(1, newStepSub); err != nil { 205 pb.cs.Logger.Error("Replay reset error", "err", err) 206 } 207 } else { 208 i, err := strconv.Atoi(tokens[1]) 209 if err != nil { 210 fmt.Println("back takes an integer argument") 211 } else if i > pb.count { 212 fmt.Printf("argument to back must not be larger than the current count (%d)\n", pb.count) 213 } else if err := pb.replayReset(i, newStepSub); err != nil { 214 pb.cs.Logger.Error("Replay reset error", "err", err) 215 } 216 } 217 218 case "rs": 219 // "rs" -> print entire round state 220 // "rs short" -> print height/round/step 221 // "rs <field>" -> print another field of the round state 222 223 rs := pb.cs.RoundState 224 if len(tokens) == 1 { 225 fmt.Println(rs) 226 } else { 227 switch tokens[1] { 228 case "short": 229 fmt.Printf("%v/%v/%v\n", rs.Height, rs.Round, rs.Step) 230 case "validators": 231 fmt.Println(rs.Validators) 232 case "proposal": 233 fmt.Println(rs.Proposal) 234 case "proposal_block": 235 fmt.Printf("%v %v\n", rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort()) 236 case "locked_round": 237 fmt.Println(rs.LockedRound) 238 case "locked_block": 239 fmt.Printf("%v %v\n", rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort()) 240 case "votes": 241 fmt.Println(rs.Votes.StringIndented(" ")) 242 243 default: 244 fmt.Println("Unknown option", tokens[1]) 245 } 246 } 247 case "n": 248 fmt.Println(pb.count) 249 } 250 } 251 }