@@ -11,6 +11,9 @@ import (
11
11
queue "github.com/madz-lab/insertion-queue"
12
12
"go.uber.org/zap"
13
13
14
+ "github.com/gnolang/gno/gno.land/pkg/gnoland"
15
+ "github.com/gnolang/gno/tm2/pkg/amino"
16
+ bft_types "github.com/gnolang/gno/tm2/pkg/bft/types"
14
17
"github.com/gnolang/tx-indexer/storage"
15
18
storageErrors "github.com/gnolang/tx-indexer/storage/errors"
16
19
"github.com/gnolang/tx-indexer/types"
@@ -21,6 +24,8 @@ const (
21
24
DefaultMaxChunkSize = 100
22
25
)
23
26
27
+ var errInvalidGenesisState = errors .New ("invalid genesis state" )
28
+
24
29
// Fetcher is an instance of the block indexer
25
30
// fetcher
26
31
type Fetcher struct {
@@ -67,9 +72,71 @@ func New(
67
72
return f
68
73
}
69
74
75
+ func (f * Fetcher ) fetchGenesisData () error {
76
+ _ , err := f .storage .GetLatestHeight ()
77
+ // Possible cases:
78
+ // - err is ErrNotFound: the storage is empty, we execute the rest of the routine and fetch+write genesis data
79
+ // - err is nil: the storage has a latest height, this means at least the genesis data has been written,
80
+ // or some blocks past it, we do nothing and return nil
81
+ // - err is something else: there has been a storage error, we do nothing and return this error
82
+ if ! errors .Is (err , storageErrors .ErrNotFound ) {
83
+ return err
84
+ }
85
+
86
+ f .logger .Info ("Fetching genesis" )
87
+
88
+ block , err := getGenesisBlock (f .client )
89
+ if err != nil {
90
+ return fmt .Errorf ("failed to fetch genesis block: %w" , err )
91
+ }
92
+
93
+ results , err := f .client .GetBlockResults (0 )
94
+ if err != nil {
95
+ return fmt .Errorf ("failed to fetch genesis results: %w" , err )
96
+ }
97
+
98
+ if results .Results == nil {
99
+ return errors .New ("nil results" )
100
+ }
101
+
102
+ txResults := make ([]* bft_types.TxResult , len (block .Txs ))
103
+
104
+ for txIndex , tx := range block .Txs {
105
+ result := & bft_types.TxResult {
106
+ Height : 0 ,
107
+ Index : uint32 (txIndex ),
108
+ Tx : tx ,
109
+ Response : results .Results .DeliverTxs [txIndex ],
110
+ }
111
+
112
+ txResults [txIndex ] = result
113
+ }
114
+
115
+ s := & slot {
116
+ chunk : & chunk {
117
+ blocks : []* bft_types.Block {block },
118
+ results : [][]* bft_types.TxResult {txResults },
119
+ },
120
+ chunkRange : chunkRange {
121
+ from : 0 ,
122
+ to : 0 ,
123
+ },
124
+ }
125
+
126
+ return f .writeSlot (s )
127
+ }
128
+
70
129
// FetchChainData starts the fetching process that indexes
71
130
// blockchain data
72
131
func (f * Fetcher ) FetchChainData (ctx context.Context ) error {
132
+ // Attempt to fetch the genesis data
133
+ if err := f .fetchGenesisData (); err != nil {
134
+ // We treat this error as soft, to ease migration, since
135
+ // some versions of gno networks don't support this.
136
+ // In the future, we should hard fail if genesis is not fetch-able
137
+ f .logger .Error ("unable to fetch genesis data" , zap .Error (err ))
138
+ }
139
+
73
140
collectorCh := make (chan * workerResponse , DefaultMaxSlots )
74
141
75
142
// attemptRangeFetch compares local and remote state
@@ -178,68 +245,114 @@ func (f *Fetcher) FetchChainData(ctx context.Context) error {
178
245
// Pop the next chunk
179
246
f .chunkBuffer .PopFront ()
180
247
181
- wb := f .storage .WriteBatch ()
248
+ if err := f .writeSlot (item ); err != nil {
249
+ return err
250
+ }
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ func (f * Fetcher ) writeSlot (s * slot ) error {
257
+ wb := f .storage .WriteBatch ()
182
258
183
- // Save the fetched data
184
- for blockIndex , block := range item .chunk .blocks {
185
- if saveErr := wb .SetBlock (block ); saveErr != nil {
186
- // This is a design choice that really highlights the strain
187
- // of keeping legacy testnets running. Current TM2 testnets
188
- // have blocks / transactions that are no longer compatible
189
- // with latest "master" changes for Amino, so these blocks / txs are ignored,
190
- // as opposed to this error being a show-stopper for the fetcher
191
- f .logger .Error ("unable to save block" , zap .String ("err" , saveErr .Error ()))
259
+ // Save the fetched data
260
+ for blockIndex , block := range s .chunk .blocks {
261
+ if saveErr := wb .SetBlock (block ); saveErr != nil {
262
+ // This is a design choice that really highlights the strain
263
+ // of keeping legacy testnets running. Current TM2 testnets
264
+ // have blocks / transactions that are no longer compatible
265
+ // with latest "master" changes for Amino, so these blocks / txs are ignored,
266
+ // as opposed to this error being a show-stopper for the fetcher
267
+ f .logger .Error ("unable to save block" , zap .String ("err" , saveErr .Error ()))
192
268
193
- continue
194
- }
269
+ continue
270
+ }
195
271
196
- f .logger .Debug ("Added block data to batch" , zap .Int64 ("number" , block .Height ))
272
+ f .logger .Debug ("Added block data to batch" , zap .Int64 ("number" , block .Height ))
197
273
198
- // Get block results
199
- txResults := item .chunk .results [blockIndex ]
274
+ // Get block results
275
+ txResults := s .chunk .results [blockIndex ]
200
276
201
- // Save the fetched transaction results
202
- for _ , txResult := range txResults {
203
- if err := wb .SetTx (txResult ); err != nil {
204
- f .logger .Error ("unable to save tx" , zap .String ("err" , err .Error ()))
277
+ // Save the fetched transaction results
278
+ for _ , txResult := range txResults {
279
+ if err := wb .SetTx (txResult ); err != nil {
280
+ f .logger .Error ("unable to save tx" , zap .String ("err" , err .Error ()))
205
281
206
- continue
207
- }
282
+ continue
283
+ }
208
284
209
- f .logger .Debug (
210
- "Added tx to batch" ,
211
- zap .String ("hash" , base64 .StdEncoding .EncodeToString (txResult .Tx .Hash ())),
212
- )
213
- }
285
+ f .logger .Debug (
286
+ "Added tx to batch" ,
287
+ zap .String ("hash" , base64 .StdEncoding .EncodeToString (txResult .Tx .Hash ())),
288
+ )
289
+ }
214
290
215
- // Alert any listeners of a new saved block
216
- event := & types.NewBlock {
217
- Block : block ,
218
- Results : txResults ,
219
- }
291
+ // Alert any listeners of a new saved block
292
+ event := & types.NewBlock {
293
+ Block : block ,
294
+ Results : txResults ,
295
+ }
220
296
221
- f .events .SignalEvent (event )
222
- }
297
+ f .events .SignalEvent (event )
298
+ }
223
299
224
- f .logger .Info (
225
- "Added to batch block and tx data for range" ,
226
- zap .Uint64 ("from" , item .chunkRange .from ),
227
- zap .Uint64 ("to" , item .chunkRange .to ),
228
- )
300
+ f .logger .Info (
301
+ "Added to batch block and tx data for range" ,
302
+ zap .Uint64 ("from" , s .chunkRange .from ),
303
+ zap .Uint64 ("to" , s .chunkRange .to ),
304
+ )
229
305
230
- // Save the latest height data
231
- if err := wb .SetLatestHeight (item .chunkRange .to ); err != nil {
232
- if rErr := wb .Rollback (); rErr != nil {
233
- return fmt .Errorf ("unable to save latest height info, %w, %w" , err , rErr )
234
- }
306
+ // Save the latest height data
307
+ if err := wb .SetLatestHeight (s .chunkRange .to ); err != nil {
308
+ if rErr := wb .Rollback (); rErr != nil {
309
+ return fmt .Errorf ("unable to save latest height info, %w, %w" , err , rErr )
310
+ }
235
311
236
- return fmt .Errorf ("unable to save latest height info, %w" , err )
237
- }
312
+ return fmt .Errorf ("unable to save latest height info, %w" , err )
313
+ }
238
314
239
- if err := wb .Commit (); err != nil {
240
- return fmt .Errorf ("error persisting block information into storage, %w" , err )
241
- }
242
- }
315
+ if err := wb .Commit (); err != nil {
316
+ return fmt .Errorf ("error persisting block information into storage, %w" , err )
317
+ }
318
+
319
+ return nil
320
+ }
321
+
322
+ func getGenesisBlock (client Client ) (* bft_types.Block , error ) {
323
+ gblock , err := client .GetGenesis ()
324
+ if err != nil {
325
+ return nil , fmt .Errorf ("unable to get genesis block: %w" , err )
326
+ }
327
+
328
+ if gblock .Genesis == nil {
329
+ return nil , errInvalidGenesisState
330
+ }
331
+
332
+ genesisState , ok := gblock .Genesis .AppState .(gnoland.GnoGenesisState )
333
+ if ! ok {
334
+ return nil , fmt .Errorf ("unknown genesis state kind '%T'" , gblock .Genesis .AppState )
335
+ }
336
+
337
+ txs := make ([]bft_types.Tx , len (genesisState .Txs ))
338
+ for i , tx := range genesisState .Txs {
339
+ txs [i ], err = amino .MarshalJSON (tx )
340
+ if err != nil {
341
+ return nil , fmt .Errorf ("unable to marshal genesis tx: %w" , err )
243
342
}
244
343
}
344
+
345
+ block := & bft_types.Block {
346
+ Header : bft_types.Header {
347
+ NumTxs : int64 (len (txs )),
348
+ TotalTxs : int64 (len (txs )),
349
+ Time : gblock .Genesis .GenesisTime ,
350
+ ChainID : gblock .Genesis .ChainID ,
351
+ },
352
+ Data : bft_types.Data {
353
+ Txs : txs ,
354
+ },
355
+ }
356
+
357
+ return block , nil
245
358
}
0 commit comments