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  }