synapsecns/sanguine

View on GitHub
services/explorer/api/server.go

Summary

Maintainability
F
3 wks
Test Coverage
package api

import (
    "context"
    "encoding/json"
    "fmt"
    "github.com/synapsecns/sanguine/core/metrics/instrumentation/httpcapture"
    "net"
    "time"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"
    "github.com/gin-gonic/gin"
    "github.com/ipfs/go-log"
    "github.com/synapsecns/sanguine/core/metrics"
    etherClient "github.com/synapsecns/sanguine/ethergo/client"
    "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/bridge"
    "github.com/synapsecns/sanguine/services/explorer/contracts/bridgeconfig"
    "github.com/synapsecns/sanguine/services/explorer/contracts/cctp"
    "github.com/synapsecns/sanguine/services/explorer/contracts/fastbridge"
    "github.com/synapsecns/sanguine/services/explorer/contracts/swap"
    "github.com/synapsecns/sanguine/services/explorer/static"
    "github.com/synapsecns/sanguine/services/explorer/types"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/metric"

    "github.com/synapsecns/sanguine/core/ginhelper"
    "github.com/synapsecns/sanguine/services/explorer/api/cache"
    "github.com/synapsecns/sanguine/services/explorer/graphql/server/graph/model"

    "net/http"

    baseServer "github.com/synapsecns/sanguine/core/server"
    serverConfig "github.com/synapsecns/sanguine/services/explorer/config/server"
    "github.com/synapsecns/sanguine/services/explorer/consumer/client"
    fetcherpkg "github.com/synapsecns/sanguine/services/explorer/consumer/fetcher"
    "github.com/synapsecns/sanguine/services/explorer/db"
    "github.com/synapsecns/sanguine/services/explorer/db/sql"
    gqlClient "github.com/synapsecns/sanguine/services/explorer/graphql/client"
    gqlServer "github.com/synapsecns/sanguine/services/explorer/graphql/server"
    "github.com/synapsecns/sanguine/services/explorer/testutil/clickhouse"
    "golang.org/x/sync/errgroup"
)

const cacheRehydrationInterval = 1800

var logger = log.Logger("explorer-api")

// nolint:gocognit,cyclop
func createParsers(ctx context.Context, db db.ConsumerDB, fetcher fetcherpkg.ScribeFetcher, clients map[uint32]etherClient.EVM, config serverConfig.Config) (*types.ServerParsers, *types.ServerRefs, map[string]*swap.SwapFlashLoanFilterer, error) {
    ethClient, err := ethclient.DialContext(ctx, config.RPCURL+fmt.Sprintf("%d", 1))
    if err != nil {
        return nil, nil, nil, fmt.Errorf("could not create client: %w", err)
    }

    bridgeConfigRef, err := bridgeconfig.NewBridgeConfigRef(common.HexToAddress(config.BridgeConfigAddress), ethClient)
    if err != nil || bridgeConfigRef == nil {
        return nil, nil, nil, fmt.Errorf("could not create bridge config ScribeFetcher: %w", err)
    }
    priceDataService, err := tokenprice.NewPriceDataService()
    if err != nil {
        return nil, nil, 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, nil, nil, fmt.Errorf("could not get bridge abi: %w", err)
    }
    tokenSymbolToIDs, err := parser.ParseYaml(static.GetTokenSymbolToTokenIDConfig())
    if err != nil {
        return nil, nil, nil, fmt.Errorf("could not open yaml file: %w", err)
    }
    tokenDataService, err := tokendata.NewTokenDataService(newConfigFetcher, tokenSymbolToIDs)
    if err != nil {
        return nil, nil, nil, fmt.Errorf("could not create token data service: %w", err)
    }
    fastbridgeParsers := make(map[uint32]*parser.RFQParser)
    fastbridgeRefs := make(map[uint32]*fastbridge.FastBridgeRef)
    cctpParsers := make(map[uint32]*parser.CCTPParser)
    bridgeParsers := make(map[uint32]*parser.BridgeParser)
    bridgeRefs := make(map[uint32]*bridge.BridgeRef)
    cctpRefs := make(map[uint32]*cctp.CCTPRef)
    swapFilterers := make(map[string]*swap.SwapFlashLoanFilterer)

    for _, chain := range config.Chains {
        if chain.Contracts.CCTP != "" {
            cctpService, err := fetcherpkg.NewCCTPFetcher(common.HexToAddress(chain.Contracts.CCTP), clients[chain.ChainID])
            if err != nil {
                return nil, nil, nil, fmt.Errorf("could not create cctp fetcher: %w", err)
            }

            cctpRef, err := cctp.NewCCTPRef(common.HexToAddress(chain.Contracts.CCTP), clients[chain.ChainID])
            if err != nil {
                return nil, nil, nil, fmt.Errorf("could not create cctp ref: %w", err)
            }
            cctpRefs[chain.ChainID] = cctpRef
            cctpParser, err := parser.NewCCTPParser(db, common.HexToAddress(chain.Contracts.CCTP), fetcher, cctpService, tokenDataService, priceDataService, true)
            if err != nil {
                return nil, nil, nil, fmt.Errorf("could not create cctp parser: %w", err)
            }
            cctpParsers[chain.ChainID] = cctpParser
        }
        if chain.Contracts.Bridge != "" {
            bridgeRef, err := bridge.NewBridgeRef(common.HexToAddress(chain.Contracts.Bridge), clients[chain.ChainID])
            if err != nil {
                return nil, nil, nil, fmt.Errorf("could not create bridge ref: %w", err)
            }
            bridgeRefs[chain.ChainID] = bridgeRef
            bridgeParser, err := parser.NewBridgeParser(db, common.HexToAddress(chain.Contracts.Bridge), tokenDataService, fetcher, priceDataService, true)
            if err != nil {
                return nil, nil, nil, fmt.Errorf("could not create bridge parser: %w", err)
            }
            bridgeParsers[chain.ChainID] = bridgeParser
        }
        if chain.Contracts.RFQ != "" {
            rfqService, err := fetcherpkg.NewRFQFetcher(common.HexToAddress(chain.Contracts.RFQ), clients[chain.ChainID])
            if err != nil {
                return nil, nil, nil, fmt.Errorf("could not create fast bridge fetcher: %w", err)
            }

            fastBridgeRef, err := fastbridge.NewFastBridgeRef(common.HexToAddress(chain.Contracts.RFQ), clients[chain.ChainID])
            if err != nil {
                return nil, nil, nil, fmt.Errorf("could not create fast bridge ref: %w", err)
            }
            fastbridgeRefs[chain.ChainID] = fastBridgeRef
            rfqParser, err := parser.NewRFQParser(db, common.HexToAddress(chain.Contracts.RFQ), fetcher, rfqService, tokenDataService, priceDataService, true)
            if err != nil {
                return nil, nil, nil, fmt.Errorf("could not create fast bridge parser: %w", err)
            }
            fastbridgeParsers[chain.ChainID] = rfqParser
        }
        if len(chain.Swaps) > 0 {
            for _, swapAddr := range chain.Swaps {
                swapFilterer, err := swap.NewSwapFlashLoanFilterer(common.HexToAddress(swapAddr), clients[chain.ChainID])
                if err != nil {
                    return nil, nil, nil, fmt.Errorf("could not create swap filterer: %w", err)
                }
                key := fmt.Sprintf("%d_%s", chain.ChainID, swapAddr)

                swapFilterers[key] = swapFilterer
            }
        }
    }
    serverParser := types.ServerParsers{
        BridgeParsers:     bridgeParsers,
        CCTParsers:        cctpParsers,
        FastBridgeParsers: fastbridgeParsers,
    }

    serverRefs := types.ServerRefs{
        BridgeRefs:     bridgeRefs,
        CCTPRefs:       cctpRefs,
        FastBridgeRefs: fastbridgeRefs,
    }
    return &serverParser, &serverRefs, swapFilterers, nil
}

// Start starts the api server.
//
// nolint:cyclop
func Start(ctx context.Context, cfg serverConfig.Config, handler metrics.Handler) error {
    router := ginhelper.New(logger)
    router.GET(ginhelper.MetricsEndpoint, gin.WrapH(handler.Handler()))

    // initialize the database
    consumerDB, err := InitDB(ctx, cfg.DBAddress, true, handler)
    if err != nil {
        return fmt.Errorf("could not initialize database: %w", err)
    }

    // configure the http client
    httpClient := http.DefaultClient
    // TODO: add an option for full capture instead of keeping on by default
    httpClient.Transport = httpcapture.NewCaptureTransport(httpClient.Transport, handler)
    handler.ConfigureHTTPClient(httpClient)

    //  get the fetcher
    fetcher := fetcherpkg.NewFetcher(client.NewClient(httpClient, cfg.ScribeURL), handler)

    // response cache
    responseCache, err := cache.NewAPICacheService()
    if err != nil {
        return fmt.Errorf("error creating api cache service, %w", err)
    }

    clients := make(map[uint32]etherClient.EVM)
    for _, chain := range cfg.Chains {
        backendClient, err := etherClient.DialBackend(ctx, cfg.RPCURL+fmt.Sprintf("%d", chain.ChainID), handler)
        if err != nil {
            return fmt.Errorf("could not start client for %s", cfg.RPCURL)
        }
        clients[chain.ChainID] = backendClient
    }
    serverParsers, serverRefs, swapFilters, err := createParsers(ctx, consumerDB, fetcher, clients, cfg)
    if err != nil {
        return fmt.Errorf("could not create parsers: %w", err)
    }
    gqlServer.EnableGraphql(router, consumerDB, fetcher, responseCache, clients, serverParsers, serverRefs, swapFilters, cfg, handler)

    fmt.Printf("started graphiql gqlServer on port: http://localhost:%d/graphiql\n", cfg.HTTPPort)

    ticker := time.NewTicker(cacheRehydrationInterval * time.Second)
    defer ticker.Stop()
    first := make(chan bool, 1)
    first <- true
    g, ctx := errgroup.WithContext(ctx)
    url := fmt.Sprintf("http://%s/graphql", net.JoinHostPort("localhost", fmt.Sprintf("%d", cfg.HTTPPort)))
    client := gqlClient.NewClient(httpClient, url)

    err = registerObservableMetrics(handler, consumerDB)
    if err != nil {
        return fmt.Errorf("could not register observable metrics: %w", err)
    }

    if cfg.HydrateCache {
        // refill cache
        go func() {
            for {
                select {
                case <-ctx.Done():
                    ticker.Stop()
                    return
                case <-ticker.C:
                    err = RehydrateCache(ctx, client, responseCache, handler)
                    if err != nil {
                        logger.Warnf("rehydration failed: %s", err)
                    }
                case <-first:
                    // buffer to wait for everything to get initialized
                    time.Sleep(10 * time.Second)
                    err = RehydrateCache(ctx, client, responseCache, handler)
                    if err != nil {
                        logger.Errorf("initial rehydration failed: %s", err)
                    }
                }
            }
        }()
    }
    g.Go(func() error {
        connection := baseServer.Server{}
        err = connection.ListenAndServe(ctx, fmt.Sprintf(":%d", cfg.HTTPPort), router)
        if err != nil {
            return fmt.Errorf("could not start gqlServer: %w", err)
        }
        return nil
    })

    err = g.Wait()
    if err != nil {
        return fmt.Errorf("server error: %w", err)
    }

    return nil
}

const meterName = "github.com/synapsecns/sanguine/services/explorer/api"
const pendingName = "pending_bridges"

func registerObservableMetrics(handler metrics.Handler, conn db.ConsumerDBReader) error {
    meter := handler.Meter(meterName)
    pendingGauge, err := meter.Int64ObservableGauge(pendingName)

    if err != nil {
        return fmt.Errorf("could not create pending bridges gauge: %w", err)
    }

    if _, err := meter.RegisterCallback(func(parentCtx context.Context, o metric.Observer) (err error) {
        ctx, span := handler.Tracer().Start(parentCtx, "pending_bridge_stats")
        defer func() {
            metrics.EndSpanWithErr(span, err)
        }()

        pendingCount, err := conn.GetPendingByChain(ctx)
        if err != nil {
            return fmt.Errorf("could not get pending bridges: %w", err)
        }

        itr := pendingCount.Iterator()
        for !itr.Done() {
            chainID, count, _ := itr.Next()
            o.ObserveInt64(pendingGauge, int64(count), metric.WithAttributes(attribute.Int(metrics.ChainID, chainID)))
        }

        return nil
    }, pendingGauge); err != nil {
        return fmt.Errorf("could not register callback for pending bridges gauge: %w", err)
    }

    return nil
}

// InitDB initializes a database given a database type and path.
func InitDB(ctx context.Context, address string, readOnly bool, handler metrics.Handler) (db.ConsumerDB, error) {
    if address == "default" {
        cleanup, port, err := clickhouse.NewClickhouseStore("explorer")
        if cleanup == nil {
            return nil, fmt.Errorf("clickhouse spin up failure, no open port found: %w", err)
        }
        if port == nil || err != nil {
            cleanup()
            return nil, fmt.Errorf("clickhouse spin up failure, no open port found: %w", err)
        }
        address = "clickhouse://clickhouse_test:clickhouse_test@localhost:" + fmt.Sprintf("%d", *port) + "/clickhouse_test"
    }
    clickhouseDB, err := sql.OpenGormClickhouse(ctx, address, readOnly, handler)
    if err != nil {
        return nil, fmt.Errorf("could not open database: %w", err)
    }

    return clickhouseDB, nil
}

// TODO make this nicer. make a yaml of the queries needed for rehydration w/refresh rate and iterate on that.

// RehydrateCache rehydrates the cache.
//
// nolint:dupl,gocognit,cyclop,maintidx
func RehydrateCache(parentCtx context.Context, client *gqlClient.Client, service cache.Service, handler metrics.Handler) (err error) {
    traceCtx, span := handler.Tracer().Start(parentCtx, "RehydrateCache")
    defer func() {
        metrics.EndSpanWithErr(span, err)
    }()

    fmt.Println("rehydrating Cache")
    totalVolumeType := model.StatisticTypeTotalVolumeUsd
    totalFeeType := model.StatisticTypeTotalFeeUsd
    countAddressType := model.StatisticTypeCountAddresses
    countTxType := model.StatisticTypeCountTransactions

    allPlatformType := model.PlatformAll
    bridgeType := model.PlatformBridge
    swapType := model.PlatformSwap
    messagingType := model.PlatformMessageBus

    volumeType := model.DailyStatisticTypeVolume
    feeType := model.DailyStatisticTypeFee
    txType := model.DailyStatisticTypeTransactions
    addrType := model.DailyStatisticTypeAddresses

    monthType := model.DurationPastMonth
    threeMonthType := model.DurationPast3Months
    sixMonthType := model.DurationPast6Months
    allTimeType := model.DurationAllTime

    // dontUseMv := false
    useMv := true

    g, ctx := errgroup.WithContext(traceCtx)
    g.Go(func() error {
        statsVolAll, err := client.GetAmountStatistic(ctx, totalVolumeType, &allPlatformType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, TOTAL_VOLUME_USD, ALL, ALL_TIME, , , ", HandleJSONAmountStat(statsVolAll))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsFeeAll, err := client.GetAmountStatistic(ctx, totalFeeType, &allPlatformType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, TOTAL_FEE_USD, ALL, ALL_TIME, , , ", HandleJSONAmountStat(statsFeeAll))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsAddrAll, err := client.GetAmountStatistic(ctx, countAddressType, &allPlatformType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, COUNT_ADDRESSES, ALL, ALL_TIME, , , ", HandleJSONAmountStat(statsAddrAll))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsTxAll, err := client.GetAmountStatistic(ctx, countTxType, &allPlatformType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, COUNT_TRANSACTIONS, ALL, ALL_TIME, , , ", HandleJSONAmountStat(statsTxAll))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        statsVolBridge, err := client.GetAmountStatistic(ctx, totalVolumeType, &bridgeType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, TOTAL_VOLUME_USD, BRIDGE, ALL_TIME, , , ", HandleJSONAmountStat(statsVolBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsFeeBridge, err := client.GetAmountStatistic(ctx, totalFeeType, &bridgeType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, TOTAL_FEE_USD, BRIDGE, ALL_TIME, , , ", HandleJSONAmountStat(statsFeeBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsAddrBridge, err := client.GetAmountStatistic(ctx, countAddressType, &bridgeType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, COUNT_ADDRESSES, BRIDGE, ALL_TIME, , , ", HandleJSONAmountStat(statsAddrBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsTxBridge, err := client.GetAmountStatistic(ctx, countTxType, &bridgeType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, COUNT_TRANSACTIONS, BRIDGE, ALL_TIME, , , ", HandleJSONAmountStat(statsTxBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        statsVolSwap, err := client.GetAmountStatistic(ctx, totalVolumeType, &swapType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, TOTAL_VOLUME_USD, SWAP, ALL_TIME, , , ", HandleJSONAmountStat(statsVolSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsFeeSwap, err := client.GetAmountStatistic(ctx, totalFeeType, &swapType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, TOTAL_FEE_USD, SWAP, ALL_TIME, , , ", HandleJSONAmountStat(statsFeeSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsAddrSwap, err := client.GetAmountStatistic(ctx, countAddressType, &swapType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, COUNT_ADDRESSES, SWAP, ALL_TIME, , , ", HandleJSONAmountStat(statsAddrSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsTxSwap, err := client.GetAmountStatistic(ctx, countTxType, &swapType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, COUNT_TRANSACTIONS, SWAP, ALL_TIME, , , ", HandleJSONAmountStat(statsTxSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        statsFeeMsg, err := client.GetAmountStatistic(ctx, totalFeeType, &messagingType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, TOTAL_FEE_USD, MESSAGE_BUS, ALL_TIME, , , ", HandleJSONAmountStat(statsFeeMsg))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsAddrMsg, err := client.GetAmountStatistic(ctx, countAddressType, &messagingType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, COUNT_ADDRESSES, MESSAGE_BUS, ALL_TIME, , , ", HandleJSONAmountStat(statsAddrMsg))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        statsTxMsg, err := client.GetAmountStatistic(ctx, countTxType, &messagingType, &allTimeType, nil, nil, nil, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("amountStatistic, COUNT_TRANSACTIONS, MESSAGE_BUS, ALL_TIME, , , ", HandleJSONAmountStat(statsTxMsg))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        dailyVolMonth, err := client.GetDailyStatisticsByChain(ctx, nil, &volumeType, &monthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , VOLUME, PAST_MONTH, ALL", HandleJSONDailyStat(dailyVolMonth))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyFeeMonth, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &monthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_MONTH, ALL", HandleJSONDailyStat(dailyFeeMonth))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxMonth, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &monthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_MONTH, ALL", HandleJSONDailyStat(dailyTxMonth))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrMonth, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &monthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_MONTH, ALL", HandleJSONDailyStat(dailyAddrMonth))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        dailyVolYear, err := client.GetDailyStatisticsByChain(ctx, nil, &volumeType, &threeMonthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , VOLUME, PAST_3_MONTHS, ALL", HandleJSONDailyStat(dailyVolYear))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyFeeYear, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &threeMonthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_3_MONTHS, ALL", HandleJSONDailyStat(dailyFeeYear))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxYear, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &threeMonthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_3_MONTHS, ALL", HandleJSONDailyStat(dailyTxYear))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrYear, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &threeMonthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_3_MONTHS, ALL", HandleJSONDailyStat(dailyAddrYear))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        dailyVolAllTime, err := client.GetDailyStatisticsByChain(ctx, nil, &volumeType, &sixMonthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , VOLUME, PAST_6_MONTHS, ALL", HandleJSONDailyStat(dailyVolAllTime))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyFeeAllTime, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &sixMonthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_6_MONTHS, ALL", HandleJSONDailyStat(dailyFeeAllTime))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxAllTime, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &sixMonthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_6_MONTHS, ALL", HandleJSONDailyStat(dailyTxAllTime))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrAllTime, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &sixMonthType, &allPlatformType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_6_MONTHS, ALL", HandleJSONDailyStat(dailyAddrAllTime))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })

    g.Go(func() error {
        dailyVolMonthBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &volumeType, &monthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , VOLUME, PAST_MONTH, BRIDGE", HandleJSONDailyStat(dailyVolMonthBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyFeeMonthBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &monthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_MONTH, BRIDGE", HandleJSONDailyStat(dailyFeeMonthBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxMonthBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &monthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_MONTH, BRIDGE", HandleJSONDailyStat(dailyTxMonthBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrMonthBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &monthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_MONTH, BRIDGE", HandleJSONDailyStat(dailyAddrMonthBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        dailyVolYearBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &volumeType, &threeMonthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , VOLUME, PAST_3_MONTHS, BRIDGE", HandleJSONDailyStat(dailyVolYearBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyFeeYearBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &threeMonthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_3_MONTHS, BRIDGE", HandleJSONDailyStat(dailyFeeYearBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxYearBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &threeMonthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_3_MONTHS, BRIDGE", HandleJSONDailyStat(dailyTxYearBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrYearBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &threeMonthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_3_MONTHS, BRIDGE", HandleJSONDailyStat(dailyAddrYearBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        dailyVolAllTimeBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &volumeType, &sixMonthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , VOLUME, PAST_6_MONTHS, BRIDGE", HandleJSONDailyStat(dailyVolAllTimeBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyFeeAllTimeBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &sixMonthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_6_MONTHS, BRIDGE", HandleJSONDailyStat(dailyFeeAllTimeBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxAllTimeBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &sixMonthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_6_MONTHS, BRIDGE", HandleJSONDailyStat(dailyTxAllTimeBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrAllTimeBridge, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &sixMonthType, &bridgeType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_6_MONTHS, BRIDGE", HandleJSONDailyStat(dailyAddrAllTimeBridge))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })

    g.Go(func() error {
        dailyVolMonthSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &volumeType, &monthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , VOLUME, PAST_MONTH, SWAP", HandleJSONDailyStat(dailyVolMonthSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyFeeMonthSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &monthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_MONTH, SWAP", HandleJSONDailyStat(dailyFeeMonthSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxMonthSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &monthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_MONTH, SWAP", HandleJSONDailyStat(dailyTxMonthSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrMonthSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &monthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }

        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_MONTH, SWAP", HandleJSONDailyStat(dailyAddrMonthSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        dailyVolYearSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &volumeType, &threeMonthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , VOLUME, PAST_3_MONTHS, SWAP", HandleJSONDailyStat(dailyVolYearSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyFeeYearSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &threeMonthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_3_MONTHS, SWAP", HandleJSONDailyStat(dailyFeeYearSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxYearSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &threeMonthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_3_MONTHS, SWAP", HandleJSONDailyStat(dailyTxYearSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrYearSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &threeMonthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_3_MONTHS, SWAP", HandleJSONDailyStat(dailyAddrYearSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        dailyVolAllTimeSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &volumeType, &sixMonthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , VOLUME, PAST_6_MONTHS, SWAP", HandleJSONDailyStat(dailyVolAllTimeSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyFeeAllTimeSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &sixMonthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_6_MONTHS, SWAP", HandleJSONDailyStat(dailyFeeAllTimeSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxAllTimeSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &sixMonthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_6_MONTHS, SWAP", HandleJSONDailyStat(dailyTxAllTimeSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrAllTimeSwap, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &sixMonthType, &swapType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_6_MONTHS, SWAP", HandleJSONDailyStat(dailyAddrAllTimeSwap))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })

    g.Go(func() error {
        dailyFeeMonthMessageBus, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &monthType, &messagingType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_MONTH, MESSAGE_BUS", HandleJSONDailyStat(dailyFeeMonthMessageBus))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxMonthMessageBus, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &monthType, &messagingType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_MONTH, MESSAGE_BUS", HandleJSONDailyStat(dailyTxMonthMessageBus))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrMonthMessageBus, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &monthType, &messagingType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_MONTH, MESSAGE_BUS", HandleJSONDailyStat(dailyAddrMonthMessageBus))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        dailyFeeYearMessageBus, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &threeMonthType, &messagingType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_3_MONTHS, MESSAGE_BUS", HandleJSONDailyStat(dailyFeeYearMessageBus))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxYearMessageBus, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &threeMonthType, &messagingType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_3_MONTHS, MESSAGE_BUS", HandleJSONDailyStat(dailyTxYearMessageBus))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrYearMessageBus, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &threeMonthType, &messagingType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_3_MONTHS, MESSAGE_BUS", HandleJSONDailyStat(dailyAddrYearMessageBus))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })
    g.Go(func() error {
        dailyFeeAllTimeMessageBus, err := client.GetDailyStatisticsByChain(ctx, nil, &feeType, &sixMonthType, &messagingType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , FEE, PAST_6_MONTHS, MESSAGE_BUS", HandleJSONDailyStat(dailyFeeAllTimeMessageBus))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyTxAllTimeMessageBus, err := client.GetDailyStatisticsByChain(ctx, nil, &txType, &sixMonthType, &messagingType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , TRANSACTIONS, PAST_6_MONTHS, MESSAGE_BUS", HandleJSONDailyStat(dailyTxAllTimeMessageBus))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        dailyAddrAllTimeMessageBus, err := client.GetDailyStatisticsByChain(ctx, nil, &addrType, &sixMonthType, &messagingType, &useMv)
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        err = service.CacheResponse("dailyStatisticsByChain, , ADDRESSES, PAST_6_MONTHS, MESSAGE_BUS", HandleJSONDailyStat(dailyAddrAllTimeMessageBus))
        if err != nil {
            return fmt.Errorf("error rehydrating cache: %w", err)
        }
        return nil
    })

    err = g.Wait()
    if err != nil {
        return fmt.Errorf("error rehyrdrating cache, %w", err)
    }
    return nil
}

// HandleJSONAmountStat converts the gqlClient.GetAmountStatistic to model.ValueResul.
func HandleJSONAmountStat(r *gqlClient.GetAmountStatistic) *model.ValueResult {
    var res *model.ValueResult
    jsonRes, err := json.Marshal(r.Response)
    if err != nil {
        return nil
    }
    err = json.Unmarshal(jsonRes, &res)
    if err != nil {
        return nil
    }
    return res
}

// HandleJSONDailyStat converts the gqlClient.GetDailyStatisticsByChain to the []*model.DateResultByChain type.
func HandleJSONDailyStat(r *gqlClient.GetDailyStatisticsByChain) []*model.DateResultByChain {
    var res []*model.DateResultByChain
    jsonRes, err := json.Marshal(r.Response)
    if err != nil {
        return nil
    }
    err = json.Unmarshal(jsonRes, &res)
    if err != nil {
        return nil
    }
    return res
}