Skip to content

File tree

10 files changed

+718
-0
lines changed

10 files changed

+718
-0
lines changed
 

‎CHANGELOG_PENDING.md

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi
2020

2121
### FEATURES
2222

23+
- [#9083] backport cli command to reindex missed events (@cmwaters)
24+
2325
### IMPROVEMENTS
2426

2527
### BUG FIXES
+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
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

Comments
 (0)
Please sign in to comment.