github.com/pokt-network/tendermint@v0.32.11-0.20230426215212-59310158d3e9/consensus/replay_file.go (about)

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