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 }