|
1 | 1 | package cmd |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "context" |
5 | | - "errors" |
6 | 4 | "fmt" |
7 | 5 | "os" |
8 | 6 | "path/filepath" |
9 | | - "time" |
10 | 7 |
|
11 | | - goheader "github.com/celestiaorg/go-header" |
12 | | - goheaderstore "github.com/celestiaorg/go-header/store" |
13 | | - ds "github.com/ipfs/go-datastore" |
14 | | - kt "github.com/ipfs/go-datastore/keytransform" |
15 | 8 | "github.com/spf13/cobra" |
16 | 9 |
|
17 | | - "github.com/evstack/ev-node/node" |
18 | 10 | "github.com/evstack/ev-node/pkg/config" |
19 | | - "github.com/evstack/ev-node/pkg/store" |
20 | | - "github.com/evstack/ev-node/types" |
21 | 11 | ) |
22 | 12 |
|
23 | 13 | // UnsafeCleanDataDir removes all contents of the specified data directory. |
@@ -66,175 +56,6 @@ This operation is unsafe and cannot be undone. Use with caution!`, |
66 | 56 | }, |
67 | 57 | } |
68 | 58 |
|
69 | | -// StoreP2PInspectCmd reports head/tail information for the go-header stores used by P2P sync. |
70 | | -var StoreP2PInspectCmd = &cobra.Command{ |
71 | | - Use: "store-info", |
72 | | - Short: "Inspect the go-header (P2P) stores and display their tail/head entries", |
73 | | - Long: `Opens the datastore used by the node's go-header services and reports |
74 | | -the current height, head, and tail information for both the header and data stores.`, |
75 | | - RunE: func(cmd *cobra.Command, args []string) error { |
76 | | - nodeConfig, err := ParseConfig(cmd) |
77 | | - if err != nil { |
78 | | - return fmt.Errorf("error parsing config: %w", err) |
79 | | - } |
80 | | - |
81 | | - ctx := cmd.Context() |
82 | | - if ctx == nil { |
83 | | - ctx = context.Background() |
84 | | - } |
85 | | - |
86 | | - dbName := resolveDBName(cmd) |
87 | | - |
88 | | - rawStore, err := store.NewDefaultKVStore(nodeConfig.RootDir, nodeConfig.DBPath, dbName) |
89 | | - if err != nil { |
90 | | - return fmt.Errorf("failed to open datastore: %w", err) |
91 | | - } |
92 | | - defer func() { |
93 | | - if closeErr := rawStore.Close(); closeErr != nil { |
94 | | - cmd.PrintErrf("warning: failed to close datastore: %v\n", closeErr) |
95 | | - } |
96 | | - }() |
97 | | - |
98 | | - mainStore := kt.Wrap(rawStore, &kt.PrefixTransform{ |
99 | | - Prefix: ds.NewKey(node.EvPrefix), |
100 | | - }) |
101 | | - |
102 | | - headerSnapshot, err := inspectP2PStore[*types.SignedHeader](ctx, mainStore, headerStorePrefix, "Header Store") |
103 | | - if err != nil { |
104 | | - return fmt.Errorf("failed to inspect header store: %w", err) |
105 | | - } |
106 | | - |
107 | | - dataSnapshot, err := inspectP2PStore[*types.Data](ctx, mainStore, dataStorePrefix, "Data Store") |
108 | | - if err != nil { |
109 | | - return fmt.Errorf("failed to inspect data store: %w", err) |
110 | | - } |
111 | | - |
112 | | - storePath := resolveStorePath(nodeConfig.RootDir, nodeConfig.DBPath, dbName) |
113 | | - |
114 | | - out := cmd.OutOrStdout() |
115 | | - fmt.Fprintf(out, "Inspecting go-header stores at %s\n", storePath) |
116 | | - printP2PStoreSnapshot(cmd, headerSnapshot) |
117 | | - printP2PStoreSnapshot(cmd, dataSnapshot) |
118 | | - |
119 | | - return nil |
120 | | - }, |
121 | | -} |
122 | | - |
123 | | -const ( |
124 | | - headerStorePrefix = "headerSync" |
125 | | - dataStorePrefix = "dataSync" |
126 | | -) |
127 | | - |
128 | | -type p2pStoreSnapshot struct { |
129 | | - Label string |
130 | | - Prefix string |
131 | | - Height uint64 |
132 | | - HeadHeight uint64 |
133 | | - HeadHash string |
134 | | - HeadTime time.Time |
135 | | - TailHeight uint64 |
136 | | - TailHash string |
137 | | - TailTime time.Time |
138 | | - HeadPresent bool |
139 | | - TailPresent bool |
140 | | - Empty bool |
141 | | -} |
142 | | - |
143 | | -func inspectP2PStore[H goheader.Header[H]]( |
144 | | - ctx context.Context, |
145 | | - datastore ds.Batching, |
146 | | - prefix string, |
147 | | - label string, |
148 | | -) (p2pStoreSnapshot, error) { |
149 | | - storeImpl, err := goheaderstore.NewStore[H]( |
150 | | - datastore, |
151 | | - goheaderstore.WithStorePrefix(prefix), |
152 | | - goheaderstore.WithMetrics(), |
153 | | - ) |
154 | | - if err != nil { |
155 | | - return p2pStoreSnapshot{}, fmt.Errorf("failed to open %s: %w", label, err) |
156 | | - } |
157 | | - |
158 | | - if err := storeImpl.Start(ctx); err != nil { |
159 | | - return p2pStoreSnapshot{}, fmt.Errorf("failed to start %s: %w", label, err) |
160 | | - } |
161 | | - defer func() { |
162 | | - _ = storeImpl.Stop(context.Background()) |
163 | | - }() |
164 | | - |
165 | | - snapshot := p2pStoreSnapshot{ |
166 | | - Label: label, |
167 | | - Prefix: prefix, |
168 | | - Height: storeImpl.Height(), |
169 | | - } |
170 | | - |
171 | | - if err := populateSnapshot(ctx, storeImpl, &snapshot); err != nil { |
172 | | - return p2pStoreSnapshot{}, err |
173 | | - } |
174 | | - |
175 | | - return snapshot, nil |
176 | | -} |
177 | | - |
178 | | -func populateSnapshot[H goheader.Header[H]]( |
179 | | - ctx context.Context, |
180 | | - storeImpl *goheaderstore.Store[H], |
181 | | - snapshot *p2pStoreSnapshot, |
182 | | -) error { |
183 | | - head, err := storeImpl.Head(ctx) |
184 | | - switch { |
185 | | - case err == nil: |
186 | | - snapshot.HeadPresent = true |
187 | | - snapshot.HeadHeight = head.Height() |
188 | | - snapshot.HeadHash = head.Hash().String() |
189 | | - snapshot.HeadTime = head.Time() |
190 | | - case errors.Is(err, goheader.ErrEmptyStore), errors.Is(err, goheader.ErrNotFound): |
191 | | - // store not initialized yet |
192 | | - default: |
193 | | - return fmt.Errorf("failed to read %s head: %w", snapshot.Label, err) |
194 | | - } |
195 | | - |
196 | | - tail, err := storeImpl.Tail(ctx) |
197 | | - switch { |
198 | | - case err == nil: |
199 | | - snapshot.TailPresent = true |
200 | | - snapshot.TailHeight = tail.Height() |
201 | | - snapshot.TailHash = tail.Hash().String() |
202 | | - snapshot.TailTime = tail.Time() |
203 | | - case errors.Is(err, goheader.ErrEmptyStore), errors.Is(err, goheader.ErrNotFound): |
204 | | - default: |
205 | | - return fmt.Errorf("failed to read %s tail: %w", snapshot.Label, err) |
206 | | - } |
207 | | - |
208 | | - snapshot.Empty = !snapshot.HeadPresent && !snapshot.TailPresent |
209 | | - |
210 | | - return nil |
211 | | -} |
212 | | - |
213 | | -func printP2PStoreSnapshot(cmd *cobra.Command, snapshot p2pStoreSnapshot) { |
214 | | - out := cmd.OutOrStdout() |
215 | | - fmt.Fprintf(out, "\n[%s]\n", snapshot.Label) |
216 | | - fmt.Fprintf(out, "prefix: %s\n", snapshot.Prefix) |
217 | | - fmt.Fprintf(out, "height: %d\n", snapshot.Height) |
218 | | - if snapshot.Empty { |
219 | | - fmt.Fprintln(out, "status: empty (no entries found)") |
220 | | - return |
221 | | - } |
222 | | - |
223 | | - if snapshot.TailPresent { |
224 | | - fmt.Fprintf(out, "tail: height=%d hash=%s%s\n", snapshot.TailHeight, snapshot.TailHash, formatTime(snapshot.TailTime)) |
225 | | - } |
226 | | - if snapshot.HeadPresent { |
227 | | - fmt.Fprintf(out, "head: height=%d hash=%s%s\n", snapshot.HeadHeight, snapshot.HeadHash, formatTime(snapshot.HeadTime)) |
228 | | - } |
229 | | -} |
230 | | - |
231 | | -func formatTime(t time.Time) string { |
232 | | - if t.IsZero() { |
233 | | - return "" |
234 | | - } |
235 | | - return fmt.Sprintf(" time=%s", t.UTC().Format(time.RFC3339)) |
236 | | -} |
237 | | - |
238 | 59 | func resolveDBName(cmd *cobra.Command) string { |
239 | 60 | if cmd == nil { |
240 | 61 | return config.ConfigFileName |
|
0 commit comments