Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: index genesis transactions #34

Merged
merged 40 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2622efe
fix: missing first elem in tx iterator
n0izn0iz Apr 14, 2024
984b8a9
feat: index genesis
n0izn0iz Apr 14, 2024
35cd374
feat: get genesis tx results and allow consumers to easily replay txs
n0izn0iz Apr 17, 2024
0a55c90
tmp
n0izn0iz Apr 26, 2024
716ae4e
tmp
n0izn0iz Jun 11, 2024
3d44897
chore: Merge remote-tracking branch 'origin/main' into handle-genesis
n0izn0iz Jun 11, 2024
f8202e0
chore: revert merge artifacts
n0izn0iz Jun 11, 2024
b930f5d
chore: cleanup
n0izn0iz Jun 11, 2024
0bd5d12
chore: remove artifact
n0izn0iz Jun 11, 2024
08830b9
chore: rework genesis indexing
n0izn0iz Jun 11, 2024
f020dcc
fix: tests
n0izn0iz Jun 11, 2024
1996592
chore: prettify
n0izn0iz Jun 11, 2024
721d17e
chore: lint
n0izn0iz Jun 11, 2024
9f736f7
fix: mock
n0izn0iz Jun 11, 2024
3485968
chore: Merge remote-tracking branch 'origin/main' into handle-genesis
n0izn0iz Jul 23, 2024
96b4c0e
chore: simplify
n0izn0iz Jul 23, 2024
1984437
chore: simplify more
n0izn0iz Jul 23, 2024
b1b5070
chore: reset go mod changesg
n0izn0iz Jul 23, 2024
1aa7046
chore: put outside of FetchChainData routine to preserve tests
n0izn0iz Jul 23, 2024
da69db6
chore: lint
n0izn0iz Jul 23, 2024
40cb711
fix: don't swallow storage error
n0izn0iz Jul 24, 2024
b9b7e16
chore: redundant case
n0izn0iz Jul 24, 2024
24e0169
chore: print unknown type
n0izn0iz Jul 24, 2024
05af58a
chore: lint
n0izn0iz Jul 24, 2024
763c68a
chore: add genesis fetch tests
n0izn0iz Jul 24, 2024
6f1bde7
chore: lint
n0izn0iz Jul 24, 2024
ff48377
Merge branch 'main' into handle-genesis
n0izn0iz Jul 24, 2024
ed51299
Merge branch 'main' into handle-genesis
n0izn0iz Jul 28, 2024
0a31d61
fix: improve tests and catch nil derefs
n0izn0iz Jul 31, 2024
0632c43
chore: default noop GetGenesis in mock
n0izn0iz Aug 7, 2024
4e17d19
chore: inline var
n0izn0iz Aug 7, 2024
7fd4a67
fix: don't skip block 1
n0izn0iz Aug 15, 2024
ccf72e1
chore: write slots
n0izn0iz Aug 15, 2024
e320a03
Merge branch 'main' into handle-genesis
n0izn0iz Aug 15, 2024
583a722
chore: put FetchGenesisData in FetchChainData
n0izn0iz Aug 15, 2024
42b9dc8
chore: add comments around confusing condition
n0izn0iz Aug 15, 2024
4feb0f0
chore: please linter
n0izn0iz Aug 15, 2024
f1d4b50
Merge branch 'main' into fork/TERITORI/handle-genesis
zivkovicmilos Sep 13, 2024
e1bc104
Fix tests
zivkovicmilos Sep 13, 2024
ca459d3
Soft fail on genesis fetch errors
zivkovicmilos Sep 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions client/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ func (c *Client) GetBlock(blockNum uint64) (*core_types.ResultBlock, error) {
return block, nil
}

func (c *Client) GetGenesis() (*core_types.ResultGenesis, error) {
genesis, err := c.client.Genesis()
if err != nil {
return nil, fmt.Errorf("unable to get genesis block, %w", err)
}

return genesis, nil
}

func (c *Client) GetBlockResults(blockNum uint64) (*core_types.ResultBlockResults, error) {
bn := int64(blockNum)

Expand Down
211 changes: 162 additions & 49 deletions fetch/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
queue "github.com/madz-lab/insertion-queue"
"go.uber.org/zap"

"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/tm2/pkg/amino"
bft_types "github.com/gnolang/gno/tm2/pkg/bft/types"
"github.com/gnolang/tx-indexer/storage"
storageErrors "github.com/gnolang/tx-indexer/storage/errors"
"github.com/gnolang/tx-indexer/types"
Expand All @@ -21,6 +24,8 @@ const (
DefaultMaxChunkSize = 100
)

var errInvalidGenesisState = errors.New("invalid genesis state")

// Fetcher is an instance of the block indexer
// fetcher
type Fetcher struct {
Expand Down Expand Up @@ -67,9 +72,71 @@ func New(
return f
}

func (f *Fetcher) fetchGenesisData() error {
_, err := f.storage.GetLatestHeight()
zivkovicmilos marked this conversation as resolved.
Show resolved Hide resolved
// Possible cases:
// - err is ErrNotFound: the storage is empty, we execute the rest of the routine and fetch+write genesis data
// - err is nil: the storage has a latest height, this means at least the genesis data has been written,
// or some blocks past it, we do nothing and return nil
// - err is something else: there has been a storage error, we do nothing and return this error
if !errors.Is(err, storageErrors.ErrNotFound) {
return err
}

f.logger.Info("Fetching genesis")

block, err := getGenesisBlock(f.client)
if err != nil {
return fmt.Errorf("failed to fetch genesis block: %w", err)
}

results, err := f.client.GetBlockResults(0)
if err != nil {
return fmt.Errorf("failed to fetch genesis results: %w", err)
}

if results.Results == nil {
return errors.New("nil results")
}

txResults := make([]*bft_types.TxResult, len(block.Txs))

for txIndex, tx := range block.Txs {
result := &bft_types.TxResult{
Height: 0,
Index: uint32(txIndex),
Tx: tx,
Response: results.Results.DeliverTxs[txIndex],
}

txResults[txIndex] = result
}

s := &slot{
chunk: &chunk{
blocks: []*bft_types.Block{block},
results: [][]*bft_types.TxResult{txResults},
},
chunkRange: chunkRange{
from: 0,
to: 0,
},
}

return f.writeSlot(s)
}

// FetchChainData starts the fetching process that indexes
// blockchain data
func (f *Fetcher) FetchChainData(ctx context.Context) error {
// Attempt to fetch the genesis data
if err := f.fetchGenesisData(); err != nil {
// We treat this error as soft, to ease migration, since
// some versions of gno networks don't support this.
// In the future, we should hard fail if genesis is not fetch-able
f.logger.Error("unable to fetch genesis data", zap.Error(err))
}

collectorCh := make(chan *workerResponse, DefaultMaxSlots)

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

wb := f.storage.WriteBatch()
if err := f.writeSlot(item); err != nil {
return err
}
}
}
}
}

func (f *Fetcher) writeSlot(s *slot) error {
wb := f.storage.WriteBatch()

// Save the fetched data
for blockIndex, block := range item.chunk.blocks {
if saveErr := wb.SetBlock(block); saveErr != nil {
// This is a design choice that really highlights the strain
// of keeping legacy testnets running. Current TM2 testnets
// have blocks / transactions that are no longer compatible
// with latest "master" changes for Amino, so these blocks / txs are ignored,
// as opposed to this error being a show-stopper for the fetcher
f.logger.Error("unable to save block", zap.String("err", saveErr.Error()))
// Save the fetched data
for blockIndex, block := range s.chunk.blocks {
if saveErr := wb.SetBlock(block); saveErr != nil {
// This is a design choice that really highlights the strain
// of keeping legacy testnets running. Current TM2 testnets
// have blocks / transactions that are no longer compatible
// with latest "master" changes for Amino, so these blocks / txs are ignored,
// as opposed to this error being a show-stopper for the fetcher
f.logger.Error("unable to save block", zap.String("err", saveErr.Error()))

continue
}
continue
}

f.logger.Debug("Added block data to batch", zap.Int64("number", block.Height))
f.logger.Debug("Added block data to batch", zap.Int64("number", block.Height))

// Get block results
txResults := item.chunk.results[blockIndex]
// Get block results
txResults := s.chunk.results[blockIndex]

// Save the fetched transaction results
for _, txResult := range txResults {
if err := wb.SetTx(txResult); err != nil {
f.logger.Error("unable to save tx", zap.String("err", err.Error()))
// Save the fetched transaction results
for _, txResult := range txResults {
if err := wb.SetTx(txResult); err != nil {
f.logger.Error("unable to save tx", zap.String("err", err.Error()))

continue
}
continue
}

f.logger.Debug(
"Added tx to batch",
zap.String("hash", base64.StdEncoding.EncodeToString(txResult.Tx.Hash())),
)
}
f.logger.Debug(
"Added tx to batch",
zap.String("hash", base64.StdEncoding.EncodeToString(txResult.Tx.Hash())),
)
}

// Alert any listeners of a new saved block
event := &types.NewBlock{
Block: block,
Results: txResults,
}
// Alert any listeners of a new saved block
event := &types.NewBlock{
Block: block,
Results: txResults,
}

f.events.SignalEvent(event)
}
f.events.SignalEvent(event)
}

f.logger.Info(
"Added to batch block and tx data for range",
zap.Uint64("from", item.chunkRange.from),
zap.Uint64("to", item.chunkRange.to),
)
f.logger.Info(
"Added to batch block and tx data for range",
zap.Uint64("from", s.chunkRange.from),
zap.Uint64("to", s.chunkRange.to),
)

// Save the latest height data
if err := wb.SetLatestHeight(item.chunkRange.to); err != nil {
if rErr := wb.Rollback(); rErr != nil {
return fmt.Errorf("unable to save latest height info, %w, %w", err, rErr)
}
// Save the latest height data
if err := wb.SetLatestHeight(s.chunkRange.to); err != nil {
if rErr := wb.Rollback(); rErr != nil {
return fmt.Errorf("unable to save latest height info, %w, %w", err, rErr)
}

return fmt.Errorf("unable to save latest height info, %w", err)
}
return fmt.Errorf("unable to save latest height info, %w", err)
}

if err := wb.Commit(); err != nil {
return fmt.Errorf("error persisting block information into storage, %w", err)
}
}
if err := wb.Commit(); err != nil {
return fmt.Errorf("error persisting block information into storage, %w", err)
}

return nil
}

func getGenesisBlock(client Client) (*bft_types.Block, error) {
gblock, err := client.GetGenesis()
if err != nil {
return nil, fmt.Errorf("unable to get genesis block: %w", err)
}

if gblock.Genesis == nil {
zivkovicmilos marked this conversation as resolved.
Show resolved Hide resolved
return nil, errInvalidGenesisState
}

genesisState, ok := gblock.Genesis.AppState.(gnoland.GnoGenesisState)
if !ok {
zivkovicmilos marked this conversation as resolved.
Show resolved Hide resolved
return nil, fmt.Errorf("unknown genesis state kind '%T'", gblock.Genesis.AppState)
}

txs := make([]bft_types.Tx, len(genesisState.Txs))
for i, tx := range genesisState.Txs {
txs[i], err = amino.MarshalJSON(tx)
if err != nil {
return nil, fmt.Errorf("unable to marshal genesis tx: %w", err)
}
}

block := &bft_types.Block{
Header: bft_types.Header{
NumTxs: int64(len(txs)),
TotalTxs: int64(len(txs)),
Time: gblock.Genesis.GenesisTime,
ChainID: gblock.Genesis.ChainID,
},
Data: bft_types.Data{
Txs: txs,
},
}

return block, nil
}
Loading
Loading