synapsecns/sanguine

View on GitHub
services/explorer/node/explorer.go

Summary

Maintainability
C
1 day
Test Coverage
package node

import (
    "context"
    "fmt"
    "net/http"
    "time"

    "github.com/synapsecns/sanguine/core/metrics"

    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/common"
    "github.com/synapsecns/sanguine/services/explorer/backfill"
    indexerConfig "github.com/synapsecns/sanguine/services/explorer/config/indexer"
    gqlClient "github.com/synapsecns/sanguine/services/explorer/consumer/client"
    fetcherpkg "github.com/synapsecns/sanguine/services/explorer/consumer/fetcher"
    "github.com/synapsecns/sanguine/services/explorer/consumer/fetcher/tokenprice"
    "github.com/synapsecns/sanguine/services/explorer/consumer/parser"
    "github.com/synapsecns/sanguine/services/explorer/consumer/parser/tokendata"
    "github.com/synapsecns/sanguine/services/explorer/contracts/bridgeconfig"
    "github.com/synapsecns/sanguine/services/explorer/db"
    "github.com/synapsecns/sanguine/services/explorer/static"
    "golang.org/x/sync/errgroup"
)

// ExplorerBackfiller is a backfiller that aggregates all backfilling from ChainBackfillers.
type ExplorerBackfiller struct {

    // consumerDB is the database to store consumer data in.
    consumerDB db.ConsumerDB
    // clients is a mapping of chain IDs -> clients.
    clients map[uint32]bind.ContractBackend
    // ChainBackfillers is a mapping of chain IDs -> chain backfillers.
    ChainBackfillers map[uint32]*backfill.ChainBackfiller
    // config is the config for the backfiller.
    config indexerConfig.Config
}

// NewExplorerBackfiller creates a new backfiller for the explorer.
//
// nolint:gocognit
func NewExplorerBackfiller(consumerDB db.ConsumerDB, config indexerConfig.Config, clients map[uint32]bind.ContractBackend, handler metrics.Handler) (*ExplorerBackfiller, error) {
    chainBackfillers := make(map[uint32]*backfill.ChainBackfiller)
    httpClient := http.Client{
        Timeout: 10 * time.Second,
        Transport: &http.Transport{
            ResponseHeaderTimeout: 10 * time.Second,
        },
    }
    fetcher := fetcherpkg.NewFetcher(gqlClient.NewClient(&httpClient, config.ScribeURL), handler)
    bridgeConfigRef, err := bridgeconfig.NewBridgeConfigRef(common.HexToAddress(config.BridgeConfigAddress), clients[config.BridgeConfigChainID])
    if err != nil || bridgeConfigRef == nil {
        return nil, fmt.Errorf("could not create bridge config ScribeFetcher: %w", err)
    }
    priceDataService, err := tokenprice.NewPriceDataService()
    if err != nil {
        return nil, fmt.Errorf("could not create price data service: %w", err)
    }
    newConfigFetcher, err := fetcherpkg.NewBridgeConfigFetcher(common.HexToAddress(config.BridgeConfigAddress), bridgeConfigRef)
    if err != nil || newConfigFetcher == nil {
        return nil, fmt.Errorf("could not get bridge abi: %w", err)
    }
    tokenSymbolToIDs, err := parser.ParseYaml(static.GetTokenSymbolToTokenIDConfig())
    if err != nil {
        return nil, fmt.Errorf("could not open yaml file: %w", err)
    }
    tokenDataService, err := tokendata.NewTokenDataService(newConfigFetcher, tokenSymbolToIDs)
    if err != nil {
        return nil, fmt.Errorf("could not create token data service: %w", err)
    }

    // Initialize each chain backfiller.
    for _, chainConfig := range config.Chains {
        chainBackfiller, err := getChainBackfiller(consumerDB, chainConfig, fetcher, clients[chainConfig.ChainID], tokenDataService, priceDataService)
        if err != nil {
            return nil, fmt.Errorf("could not get chain backfiller: %w", err)
        }

        chainBackfillers[chainConfig.ChainID] = chainBackfiller
    }

    return &ExplorerBackfiller{
        consumerDB:       consumerDB,
        clients:          clients,
        ChainBackfillers: chainBackfillers,
        config:           config,
    }, nil
}

// Backfill iterates over each chain backfiller and calls Backfill concurrently on each one.
//
// nolint:cyclop
func (e ExplorerBackfiller) Backfill(ctx context.Context, livefill bool) error {
    refreshRate := e.config.DefaultRefreshRate

    if refreshRate == 0 {
        refreshRate = 1
    }

    g, groupCtx := errgroup.WithContext(ctx)

    for i := range e.config.Chains {
        chainConfig := e.config.Chains[i]
        chainBackfiller := e.ChainBackfillers[chainConfig.ChainID]
        g.Go(func() error {
            err := chainBackfiller.Backfill(groupCtx, livefill, refreshRate)
            if err != nil {
                return fmt.Errorf("could not backfill chain %d: %w", chainConfig.ChainID, err)
            }
            return nil
        })
    }
    if err := g.Wait(); err != nil {
        logger.Errorf("backfill completed: %v", err)

        return fmt.Errorf("could not livefill explorer: %w", err)
    }
    logger.Errorf("backfill completed with no errors")

    return nil
}

// nolint gocognit,cyclop
func getChainBackfiller(consumerDB db.ConsumerDB, chainConfig indexerConfig.ChainConfig, fetcher fetcherpkg.ScribeFetcher, client bind.ContractBackend, tokenDataService tokendata.Service, priceDataService tokenprice.Service) (*backfill.ChainBackfiller, error) {
    var err error
    var bridgeParser *parser.BridgeParser
    var messageBusParser *parser.MessageBusParser
    var cctpParser *parser.CCTPParser
    var swapService fetcherpkg.SwapService
    var cctpService fetcherpkg.CCTPService
    var rfqParser *parser.RFQParser
    var rfqService fetcherpkg.RFQService

    swapParsers := make(map[common.Address]*parser.SwapParser)

    for i := range chainConfig.Contracts {
        switch chainConfig.Contracts[i].ContractType {
        case "bridge":
            bridgeParser, err = parser.NewBridgeParser(consumerDB, common.HexToAddress(chainConfig.Contracts[i].Address), tokenDataService, fetcher, priceDataService, false)
            if err != nil || bridgeParser == nil {
                return nil, fmt.Errorf("could not create bridge parser: %w", err)
            }
        case "swap":
            swapService, err = fetcherpkg.NewSwapFetcher(common.HexToAddress(chainConfig.Contracts[i].Address), client, false)
            if err != nil || swapService == nil {
                return nil, fmt.Errorf("could not create swapService: %w", err)
            }
            swapParser, err := parser.NewSwapParser(consumerDB, common.HexToAddress(chainConfig.Contracts[i].Address), false, fetcher, swapService, tokenDataService, priceDataService)
            if err != nil || swapParser == nil {
                return nil, fmt.Errorf("could not create swap parser: %w", err)
            }

            swapParsers[common.HexToAddress(chainConfig.Contracts[i].Address)] = swapParser
        case "metaswap":
            if swapService == nil {
                swapService, err := fetcherpkg.NewSwapFetcher(common.HexToAddress(chainConfig.Contracts[i].Address), client, true)
                if err != nil || swapService == nil {
                    return nil, fmt.Errorf("could not create swapService: %w", err)
                }
            }
            swapParser, err := parser.NewSwapParser(consumerDB, common.HexToAddress(chainConfig.Contracts[i].Address), true, fetcher, swapService, tokenDataService, priceDataService)
            if err != nil || swapParser == nil {
                return nil, fmt.Errorf("could not create swap parser: %w", err)
            }

            swapParsers[common.HexToAddress(chainConfig.Contracts[i].Address)] = swapParser
        case "messagebus":
            messageBusParser, err = parser.NewMessageBusParser(consumerDB, common.HexToAddress(chainConfig.Contracts[i].Address), fetcher, priceDataService)
            if err != nil || messageBusParser == nil {
                return nil, fmt.Errorf("could not create message bus parser: %w", err)
            }
        case "cctp":
            cctpService, err = fetcherpkg.NewCCTPFetcher(common.HexToAddress(chainConfig.Contracts[i].Address), client)
            if err != nil || cctpService == nil {
                return nil, fmt.Errorf("could not create cctpService: %w", err)
            }
            cctpParser, err = parser.NewCCTPParser(consumerDB, common.HexToAddress(chainConfig.Contracts[i].Address), fetcher, cctpService, tokenDataService, priceDataService, false)
            if err != nil || cctpParser == nil {
                return nil, fmt.Errorf("could not create message bus parser: %w", err)
            }
        case "rfq":
            rfqService, err = fetcherpkg.NewRFQFetcher(common.HexToAddress(chainConfig.Contracts[i].Address), client)
            if err != nil || rfqService == nil {
                return nil, fmt.Errorf("could not create rfqService: %w", err)
            }
            rfqParser, err = parser.NewRFQParser(consumerDB, common.HexToAddress(chainConfig.Contracts[i].Address), fetcher, rfqService, tokenDataService, priceDataService, false)
            if err != nil || rfqParser == nil {
                return nil, fmt.Errorf("could not create message bus parser: %w", err)
            }
        }
    }

    chainBackfiller := backfill.NewChainBackfiller(consumerDB, bridgeParser, swapParsers, messageBusParser, cctpParser, rfqParser, fetcher, chainConfig)

    return chainBackfiller, nil
}