Skip to content

Commit 3e524a7

Browse files
feat: index genesis transactions (#34)
Co-authored-by: Miloš Živković <[email protected]>
1 parent 9921720 commit 3e524a7

File tree

7 files changed

+683
-77
lines changed

7 files changed

+683
-77
lines changed

client/http.go

+9
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ func (c *Client) GetBlock(blockNum uint64) (*core_types.ResultBlock, error) {
5353
return block, nil
5454
}
5555

56+
func (c *Client) GetGenesis() (*core_types.ResultGenesis, error) {
57+
genesis, err := c.client.Genesis()
58+
if err != nil {
59+
return nil, fmt.Errorf("unable to get genesis block, %w", err)
60+
}
61+
62+
return genesis, nil
63+
}
64+
5665
func (c *Client) GetBlockResults(blockNum uint64) (*core_types.ResultBlockResults, error) {
5766
bn := int64(blockNum)
5867

fetch/fetch.go

+162-49
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
queue "github.com/madz-lab/insertion-queue"
1212
"go.uber.org/zap"
1313

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"
1417
"github.com/gnolang/tx-indexer/storage"
1518
storageErrors "github.com/gnolang/tx-indexer/storage/errors"
1619
"github.com/gnolang/tx-indexer/types"
@@ -21,6 +24,8 @@ const (
2124
DefaultMaxChunkSize = 100
2225
)
2326

27+
var errInvalidGenesisState = errors.New("invalid genesis state")
28+
2429
// Fetcher is an instance of the block indexer
2530
// fetcher
2631
type Fetcher struct {
@@ -67,9 +72,71 @@ func New(
6772
return f
6873
}
6974

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+
70129
// FetchChainData starts the fetching process that indexes
71130
// blockchain data
72131
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+
73140
collectorCh := make(chan *workerResponse, DefaultMaxSlots)
74141

75142
// attemptRangeFetch compares local and remote state
@@ -178,68 +245,114 @@ func (f *Fetcher) FetchChainData(ctx context.Context) error {
178245
// Pop the next chunk
179246
f.chunkBuffer.PopFront()
180247

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()
182258

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()))
192268

193-
continue
194-
}
269+
continue
270+
}
195271

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))
197273

198-
// Get block results
199-
txResults := item.chunk.results[blockIndex]
274+
// Get block results
275+
txResults := s.chunk.results[blockIndex]
200276

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()))
205281

206-
continue
207-
}
282+
continue
283+
}
208284

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+
}
214290

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+
}
220296

221-
f.events.SignalEvent(event)
222-
}
297+
f.events.SignalEvent(event)
298+
}
223299

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+
)
229305

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+
}
235311

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+
}
238314

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)
243342
}
244343
}
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
245358
}

0 commit comments

Comments
 (0)