|
| 1 | +package commands |
| 2 | + |
| 3 | +import ( |
| 4 | + "errors" |
| 5 | + "fmt" |
| 6 | + "strings" |
| 7 | + |
| 8 | + "github.com/spf13/cobra" |
| 9 | + dbm "github.com/tendermint/tm-db" |
| 10 | + |
| 11 | + abcitypes "github.com/tendermint/tendermint/abci/types" |
| 12 | + tmcfg "github.com/tendermint/tendermint/config" |
| 13 | + "github.com/tendermint/tendermint/libs/progressbar" |
| 14 | + "github.com/tendermint/tendermint/state" |
| 15 | + "github.com/tendermint/tendermint/state/indexer" |
| 16 | + blockidxkv "github.com/tendermint/tendermint/state/indexer/block/kv" |
| 17 | + "github.com/tendermint/tendermint/state/indexer/sink/psql" |
| 18 | + "github.com/tendermint/tendermint/state/txindex" |
| 19 | + "github.com/tendermint/tendermint/state/txindex/kv" |
| 20 | + "github.com/tendermint/tendermint/types" |
| 21 | +) |
| 22 | + |
| 23 | +const ( |
| 24 | + reindexFailed = "event re-index failed: " |
| 25 | +) |
| 26 | + |
| 27 | +var ( |
| 28 | + ErrHeightNotAvailable = errors.New("height is not available") |
| 29 | + ErrInvalidRequest = errors.New("invalid request") |
| 30 | +) |
| 31 | + |
| 32 | +// ReIndexEventCmd constructs a command to re-index events in a block height interval. |
| 33 | +var ReIndexEventCmd = &cobra.Command{ |
| 34 | + Use: "reindex-event", |
| 35 | + Short: "reindex events to the event store backends", |
| 36 | + Long: ` |
| 37 | +reindex-event is an offline tooling to re-index block and tx events to the eventsinks, |
| 38 | +you can run this command when the event store backend dropped/disconnected or you want to |
| 39 | +replace the backend. The default start-height is 0, meaning the tooling will start |
| 40 | +reindex from the base block height(inclusive); and the default end-height is 0, meaning |
| 41 | +the tooling will reindex until the latest block height(inclusive). User can omit |
| 42 | +either or both arguments. |
| 43 | + `, |
| 44 | + Example: ` |
| 45 | + tendermint reindex-event |
| 46 | + tendermint reindex-event --start-height 2 |
| 47 | + tendermint reindex-event --end-height 10 |
| 48 | + tendermint reindex-event --start-height 2 --end-height 10 |
| 49 | + `, |
| 50 | + Run: func(cmd *cobra.Command, args []string) { |
| 51 | + bs, ss, err := loadStateAndBlockStore(config) |
| 52 | + if err != nil { |
| 53 | + fmt.Println(reindexFailed, err) |
| 54 | + return |
| 55 | + } |
| 56 | + |
| 57 | + if err := checkValidHeight(bs); err != nil { |
| 58 | + fmt.Println(reindexFailed, err) |
| 59 | + return |
| 60 | + } |
| 61 | + |
| 62 | + bi, ti, err := loadEventSinks(config) |
| 63 | + if err != nil { |
| 64 | + fmt.Println(reindexFailed, err) |
| 65 | + return |
| 66 | + } |
| 67 | + |
| 68 | + riArgs := eventReIndexArgs{ |
| 69 | + startHeight: startHeight, |
| 70 | + endHeight: endHeight, |
| 71 | + blockIndexer: bi, |
| 72 | + txIndexer: ti, |
| 73 | + blockStore: bs, |
| 74 | + stateStore: ss, |
| 75 | + } |
| 76 | + if err := eventReIndex(cmd, riArgs); err != nil { |
| 77 | + panic(fmt.Errorf("%s: %w", reindexFailed, err)) |
| 78 | + } |
| 79 | + |
| 80 | + fmt.Println("event re-index finished") |
| 81 | + }, |
| 82 | +} |
| 83 | + |
| 84 | +var ( |
| 85 | + startHeight int64 |
| 86 | + endHeight int64 |
| 87 | +) |
| 88 | + |
| 89 | +func init() { |
| 90 | + ReIndexEventCmd.Flags().Int64Var(&startHeight, "start-height", 0, "the block height would like to start for re-index") |
| 91 | + ReIndexEventCmd.Flags().Int64Var(&endHeight, "end-height", 0, "the block height would like to finish for re-index") |
| 92 | +} |
| 93 | + |
| 94 | +func loadEventSinks(cfg *tmcfg.Config) (indexer.BlockIndexer, txindex.TxIndexer, error) { |
| 95 | + switch strings.ToLower(cfg.TxIndex.Indexer) { |
| 96 | + case "null": |
| 97 | + return nil, nil, errors.New("found null event sink, please check the tx-index section in the config.toml") |
| 98 | + case "psql": |
| 99 | + conn := cfg.TxIndex.PsqlConn |
| 100 | + if conn == "" { |
| 101 | + return nil, nil, errors.New("the psql connection settings cannot be empty") |
| 102 | + } |
| 103 | + es, err := psql.NewEventSink(conn, cfg.ChainID()) |
| 104 | + if err != nil { |
| 105 | + return nil, nil, err |
| 106 | + } |
| 107 | + return es.BlockIndexer(), es.TxIndexer(), nil |
| 108 | + case "kv": |
| 109 | + store, err := dbm.NewDB("tx_index", dbm.BackendType(cfg.DBBackend), cfg.DBDir()) |
| 110 | + if err != nil { |
| 111 | + return nil, nil, err |
| 112 | + } |
| 113 | + |
| 114 | + txIndexer := kv.NewTxIndex(store) |
| 115 | + blockIndexer := blockidxkv.New(dbm.NewPrefixDB(store, []byte("block_events"))) |
| 116 | + return blockIndexer, txIndexer, nil |
| 117 | + default: |
| 118 | + return nil, nil, fmt.Errorf("unsupported event sink type: %s", cfg.TxIndex.Indexer) |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +type eventReIndexArgs struct { |
| 123 | + startHeight int64 |
| 124 | + endHeight int64 |
| 125 | + blockIndexer indexer.BlockIndexer |
| 126 | + txIndexer txindex.TxIndexer |
| 127 | + blockStore state.BlockStore |
| 128 | + stateStore state.Store |
| 129 | +} |
| 130 | + |
| 131 | +func eventReIndex(cmd *cobra.Command, args eventReIndexArgs) error { |
| 132 | + var bar progressbar.Bar |
| 133 | + bar.NewOption(args.startHeight-1, args.endHeight) |
| 134 | + |
| 135 | + fmt.Println("start re-indexing events:") |
| 136 | + defer bar.Finish() |
| 137 | + for i := args.startHeight; i <= args.endHeight; i++ { |
| 138 | + select { |
| 139 | + case <-cmd.Context().Done(): |
| 140 | + return fmt.Errorf("event re-index terminated at height %d: %w", i, cmd.Context().Err()) |
| 141 | + default: |
| 142 | + b := args.blockStore.LoadBlock(i) |
| 143 | + if b == nil { |
| 144 | + return fmt.Errorf("not able to load block at height %d from the blockstore", i) |
| 145 | + } |
| 146 | + |
| 147 | + r, err := args.stateStore.LoadABCIResponses(i) |
| 148 | + if err != nil { |
| 149 | + return fmt.Errorf("not able to load ABCI Response at height %d from the statestore", i) |
| 150 | + } |
| 151 | + |
| 152 | + e := types.EventDataNewBlockHeader{ |
| 153 | + Header: b.Header, |
| 154 | + NumTxs: int64(len(b.Txs)), |
| 155 | + ResultBeginBlock: *r.BeginBlock, |
| 156 | + ResultEndBlock: *r.EndBlock, |
| 157 | + } |
| 158 | + |
| 159 | + var batch *txindex.Batch |
| 160 | + if e.NumTxs > 0 { |
| 161 | + batch = txindex.NewBatch(e.NumTxs) |
| 162 | + |
| 163 | + for i := range b.Data.Txs { |
| 164 | + tr := abcitypes.TxResult{ |
| 165 | + Height: b.Height, |
| 166 | + Index: uint32(i), |
| 167 | + Tx: b.Data.Txs[i], |
| 168 | + Result: *(r.DeliverTxs[i]), |
| 169 | + } |
| 170 | + |
| 171 | + if err = batch.Add(&tr); err != nil { |
| 172 | + return fmt.Errorf("adding tx to batch: %w", err) |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + if err := args.txIndexer.AddBatch(batch); err != nil { |
| 177 | + return fmt.Errorf("tx event re-index at height %d failed: %w", i, err) |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + if err := args.blockIndexer.Index(e); err != nil { |
| 182 | + return fmt.Errorf("block event re-index at height %d failed: %w", i, err) |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + bar.Play(i) |
| 187 | + } |
| 188 | + |
| 189 | + return nil |
| 190 | +} |
| 191 | + |
| 192 | +func checkValidHeight(bs state.BlockStore) error { |
| 193 | + base := bs.Base() |
| 194 | + |
| 195 | + if startHeight == 0 { |
| 196 | + startHeight = base |
| 197 | + fmt.Printf("set the start block height to the base height of the blockstore %d \n", base) |
| 198 | + } |
| 199 | + |
| 200 | + if startHeight < base { |
| 201 | + return fmt.Errorf("%s (requested start height: %d, base height: %d)", |
| 202 | + ErrHeightNotAvailable, startHeight, base) |
| 203 | + } |
| 204 | + |
| 205 | + height := bs.Height() |
| 206 | + |
| 207 | + if startHeight > height { |
| 208 | + return fmt.Errorf( |
| 209 | + "%s (requested start height: %d, store height: %d)", ErrHeightNotAvailable, startHeight, height) |
| 210 | + } |
| 211 | + |
| 212 | + if endHeight == 0 || endHeight > height { |
| 213 | + endHeight = height |
| 214 | + fmt.Printf("set the end block height to the latest height of the blockstore %d \n", height) |
| 215 | + } |
| 216 | + |
| 217 | + if endHeight < base { |
| 218 | + return fmt.Errorf( |
| 219 | + "%s (requested end height: %d, base height: %d)", ErrHeightNotAvailable, endHeight, base) |
| 220 | + } |
| 221 | + |
| 222 | + if endHeight < startHeight { |
| 223 | + return fmt.Errorf( |
| 224 | + "%s (requested the end height: %d is less than the start height: %d)", |
| 225 | + ErrInvalidRequest, startHeight, endHeight) |
| 226 | + } |
| 227 | + |
| 228 | + return nil |
| 229 | +} |
0 commit comments