github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/cmd/tendermint/commands/reindex_event.go (about)

     1  package commands
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/spf13/cobra"
    10  	dbm "github.com/tendermint/tm-db"
    11  
    12  	abcitypes "github.com/ari-anchor/sei-tendermint/abci/types"
    13  	tmcfg "github.com/ari-anchor/sei-tendermint/config"
    14  	"github.com/ari-anchor/sei-tendermint/internal/libs/progressbar"
    15  	"github.com/ari-anchor/sei-tendermint/internal/state"
    16  	"github.com/ari-anchor/sei-tendermint/internal/state/indexer"
    17  	"github.com/ari-anchor/sei-tendermint/internal/state/indexer/sink/kv"
    18  	"github.com/ari-anchor/sei-tendermint/internal/state/indexer/sink/psql"
    19  	"github.com/ari-anchor/sei-tendermint/internal/store"
    20  	"github.com/ari-anchor/sei-tendermint/libs/log"
    21  	"github.com/ari-anchor/sei-tendermint/libs/os"
    22  	"github.com/ari-anchor/sei-tendermint/rpc/coretypes"
    23  	"github.com/ari-anchor/sei-tendermint/types"
    24  )
    25  
    26  const (
    27  	reindexFailed = "event re-index failed: "
    28  )
    29  
    30  // MakeReindexEventCommand constructs a command to re-index events in a block height interval.
    31  func MakeReindexEventCommand(conf *tmcfg.Config, logger log.Logger) *cobra.Command {
    32  	var (
    33  		startHeight int64
    34  		endHeight   int64
    35  	)
    36  
    37  	cmd := &cobra.Command{
    38  		Use:   "reindex-event",
    39  		Short: "reindex events to the event store backends",
    40  		Long: `
    41  reindex-event is an offline tooling to re-index block and tx events to the eventsinks,
    42  you can run this command when the event store backend dropped/disconnected or you want to
    43  replace the backend. The default start-height is 0, meaning the tooling will start
    44  reindex from the base block height(inclusive); and the default end-height is 0, meaning
    45  the tooling will reindex until the latest block height(inclusive). User can omit
    46  either or both arguments.
    47  	`,
    48  		Example: `
    49  	tendermint reindex-event
    50  	tendermint reindex-event --start-height 2
    51  	tendermint reindex-event --end-height 10
    52  	tendermint reindex-event --start-height 2 --end-height 10
    53  	`,
    54  		RunE: func(cmd *cobra.Command, args []string) error {
    55  			bs, ss, err := loadStateAndBlockStore(conf)
    56  			if err != nil {
    57  				return fmt.Errorf("%s: %w", reindexFailed, err)
    58  			}
    59  
    60  			cvhArgs := checkValidHeightArgs{
    61  				startHeight: startHeight,
    62  				endHeight:   endHeight,
    63  			}
    64  			if err := checkValidHeight(bs, cvhArgs); err != nil {
    65  				return fmt.Errorf("%s: %w", reindexFailed, err)
    66  			}
    67  
    68  			es, err := loadEventSinks(conf)
    69  			if err != nil {
    70  				return fmt.Errorf("%s: %w", reindexFailed, err)
    71  			}
    72  
    73  			riArgs := eventReIndexArgs{
    74  				startHeight: startHeight,
    75  				endHeight:   endHeight,
    76  				sinks:       es,
    77  				blockStore:  bs,
    78  				stateStore:  ss,
    79  			}
    80  			if err := eventReIndex(cmd, riArgs); err != nil {
    81  				return fmt.Errorf("%s: %w", reindexFailed, err)
    82  			}
    83  
    84  			logger.Info("event re-index finished")
    85  			return nil
    86  		},
    87  	}
    88  
    89  	cmd.Flags().Int64Var(&startHeight, "start-height", 0, "the block height would like to start for re-index")
    90  	cmd.Flags().Int64Var(&endHeight, "end-height", 0, "the block height would like to finish for re-index")
    91  	return cmd
    92  }
    93  
    94  func loadEventSinks(cfg *tmcfg.Config) ([]indexer.EventSink, error) {
    95  	// Check duplicated sinks.
    96  	sinks := map[string]bool{}
    97  	for _, s := range cfg.TxIndex.Indexer {
    98  		sl := strings.ToLower(s)
    99  		if sinks[sl] {
   100  			return nil, errors.New("found duplicated sinks, please check the tx-index section in the config.toml")
   101  		}
   102  		sinks[sl] = true
   103  	}
   104  
   105  	eventSinks := []indexer.EventSink{}
   106  
   107  	for k := range sinks {
   108  		switch k {
   109  		case string(indexer.NULL):
   110  			return nil, errors.New("found null event sink, please check the tx-index section in the config.toml")
   111  		case string(indexer.KV):
   112  			store, err := tmcfg.DefaultDBProvider(&tmcfg.DBContext{ID: "tx_index", Config: cfg})
   113  			if err != nil {
   114  				return nil, err
   115  			}
   116  			eventSinks = append(eventSinks, kv.NewEventSink(store))
   117  		case string(indexer.PSQL):
   118  			conn := cfg.TxIndex.PsqlConn
   119  			if conn == "" {
   120  				return nil, errors.New("the psql connection settings cannot be empty")
   121  			}
   122  			es, err := psql.NewEventSink(conn, cfg.ChainID())
   123  			if err != nil {
   124  				return nil, err
   125  			}
   126  			eventSinks = append(eventSinks, es)
   127  		default:
   128  			return nil, errors.New("unsupported event sink type")
   129  		}
   130  	}
   131  
   132  	if len(eventSinks) == 0 {
   133  		return nil, errors.New("no proper event sink can do event re-indexing," +
   134  			" please check the tx-index section in the config.toml")
   135  	}
   136  
   137  	if !indexer.IndexingEnabled(eventSinks) {
   138  		return nil, fmt.Errorf("no event sink has been enabled")
   139  	}
   140  
   141  	return eventSinks, nil
   142  }
   143  
   144  func loadStateAndBlockStore(cfg *tmcfg.Config) (*store.BlockStore, state.Store, error) {
   145  	dbType := dbm.BackendType(cfg.DBBackend)
   146  
   147  	if !os.FileExists(filepath.Join(cfg.DBDir(), "blockstore.db")) {
   148  		return nil, nil, fmt.Errorf("no blockstore found in %v", cfg.DBDir())
   149  	}
   150  
   151  	// Get BlockStore
   152  	blockStoreDB, err := dbm.NewDB("blockstore", dbType, cfg.DBDir())
   153  	if err != nil {
   154  		return nil, nil, err
   155  	}
   156  	blockStore := store.NewBlockStore(blockStoreDB)
   157  
   158  	if !os.FileExists(filepath.Join(cfg.DBDir(), "state.db")) {
   159  		return nil, nil, fmt.Errorf("no blockstore found in %v", cfg.DBDir())
   160  	}
   161  
   162  	// Get StateStore
   163  	stateDB, err := dbm.NewDB("state", dbType, cfg.DBDir())
   164  	if err != nil {
   165  		return nil, nil, err
   166  	}
   167  	stateStore := state.NewStore(stateDB)
   168  
   169  	return blockStore, stateStore, nil
   170  }
   171  
   172  type eventReIndexArgs struct {
   173  	startHeight int64
   174  	endHeight   int64
   175  	sinks       []indexer.EventSink
   176  	blockStore  state.BlockStore
   177  	stateStore  state.Store
   178  }
   179  
   180  func eventReIndex(cmd *cobra.Command, args eventReIndexArgs) error {
   181  	var bar progressbar.Bar
   182  	bar.NewOption(args.startHeight-1, args.endHeight)
   183  
   184  	fmt.Println("start re-indexing events:")
   185  	defer bar.Finish()
   186  	for i := args.startHeight; i <= args.endHeight; i++ {
   187  		select {
   188  		case <-cmd.Context().Done():
   189  			return fmt.Errorf("event re-index terminated at height %d: %w", i, cmd.Context().Err())
   190  		default:
   191  			b := args.blockStore.LoadBlock(i)
   192  			if b == nil {
   193  				return fmt.Errorf("not able to load block at height %d from the blockstore", i)
   194  			}
   195  
   196  			r, err := args.stateStore.LoadFinalizeBlockResponses(i)
   197  			if err != nil {
   198  				return fmt.Errorf("not able to load ABCI Response at height %d from the statestore", i)
   199  			}
   200  
   201  			e := types.EventDataNewBlockHeader{
   202  				Header:              b.Header,
   203  				NumTxs:              int64(len(b.Txs)),
   204  				ResultFinalizeBlock: *r,
   205  			}
   206  
   207  			var batch *indexer.Batch
   208  			if e.NumTxs > 0 {
   209  				batch = indexer.NewBatch(e.NumTxs)
   210  
   211  				for i := range b.Data.Txs {
   212  					tr := abcitypes.TxResult{
   213  						Height: b.Height,
   214  						Index:  uint32(i),
   215  						Tx:     b.Data.Txs[i],
   216  						Result: *(r.TxResults[i]),
   217  					}
   218  
   219  					_ = batch.Add(&tr)
   220  				}
   221  			}
   222  
   223  			for _, sink := range args.sinks {
   224  				if err := sink.IndexBlockEvents(e); err != nil {
   225  					return fmt.Errorf("block event re-index at height %d failed: %w", i, err)
   226  				}
   227  
   228  				if batch != nil {
   229  					if err := sink.IndexTxEvents(batch.Ops); err != nil {
   230  						return fmt.Errorf("tx event re-index at height %d failed: %w", i, err)
   231  					}
   232  				}
   233  			}
   234  		}
   235  
   236  		bar.Play(i)
   237  	}
   238  
   239  	return nil
   240  }
   241  
   242  type checkValidHeightArgs struct {
   243  	startHeight int64
   244  	endHeight   int64
   245  }
   246  
   247  func checkValidHeight(bs state.BlockStore, args checkValidHeightArgs) error {
   248  	base := bs.Base()
   249  
   250  	if args.startHeight == 0 {
   251  		args.startHeight = base
   252  		fmt.Printf("set the start block height to the base height of the blockstore %d \n", base)
   253  	}
   254  
   255  	if args.startHeight < base {
   256  		return fmt.Errorf("%s (requested start height: %d, base height: %d)",
   257  			coretypes.ErrHeightNotAvailable, args.startHeight, base)
   258  	}
   259  
   260  	height := bs.Height()
   261  
   262  	if args.startHeight > height {
   263  		return fmt.Errorf(
   264  			"%s (requested start height: %d, store height: %d)", coretypes.ErrHeightNotAvailable, args.startHeight, height)
   265  	}
   266  
   267  	if args.endHeight == 0 || args.endHeight > height {
   268  		args.endHeight = height
   269  		fmt.Printf("set the end block height to the latest height of the blockstore %d \n", height)
   270  	}
   271  
   272  	if args.endHeight < base {
   273  		return fmt.Errorf(
   274  			"%s (requested end height: %d, base height: %d)", coretypes.ErrHeightNotAvailable, args.endHeight, base)
   275  	}
   276  
   277  	if args.endHeight < args.startHeight {
   278  		return fmt.Errorf(
   279  			"%s (requested the end height: %d is less than the start height: %d)",
   280  			coretypes.ErrInvalidRequest, args.startHeight, args.endHeight)
   281  	}
   282  
   283  	return nil
   284  }