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