services/explorer/config/server/config.go
// Package serverconfig is the config loader for the server
package serverconfig
import (
"fmt"
"os"
"path/filepath"
"github.com/richardwilkes/toolbox/collection"
"github.com/synapsecns/sanguine/services/explorer/config"
"github.com/jftuga/ellipsis"
"gopkg.in/yaml.v2"
)
// Config is used to configure the explorer server.
type Config struct {
// HTTPPort is the http port for the api
HTTPPort uint16 `yaml:"http_port"`
// DBAddress is the address of the database
DBAddress string `yaml:"db_address"`
// HydrateCache is a flag for enabling cache hydration.
HydrateCache bool `yaml:"hydrate_cache"`
// ScribeURL is the URL of the Scribe server.
ScribeURL string `yaml:"scribe_url"`
// RPCURL is the URL of the RPC server.
RPCURL string `yaml:"rpc_url"`
// BridgeConfigAddress is the address of BridgeConfig contract.
BridgeConfigAddress string `yaml:"bridge_config_address"`
// BridgeConfigChainID is the ChainID of BridgeConfig contract.
BridgeConfigChainID uint32 `yaml:"bridge_config_chain_id"`
// SwapTopicHash is the hash of the swap topic.
SwapTopicHash string `yaml:"swap_topic_hash"`
// Chains stores the chain configurations.
Chains map[uint32]ChainConfig `yaml:"chains"`
}
// ChainConfig is the config for each chain in the server config.
type ChainConfig struct {
// ChainID is the ID of the chain.
ChainID uint32 `yaml:"chain_id"`
/// GetLogsRange is the max number of blocks to request in a single getLogs request.
GetLogsRange uint64 `yaml:"get_logs_range"`
// GetLogsBatchAmount is the number of getLogs requests to include in a single batch request.
GetLogsBatchAmount uint64 `yaml:"get_logs_batch_amount"`
// BlockTime is the block time of the chain.
BlockTime uint64 `yaml:"avg_block_time"`
// Swaps are the addresses of the swaps on the chain for parsing token address logs.
Swaps []string `yaml:"swaps"`
// Chains stores the chain configurations.
Contracts ContractsConfig `yaml:"contracts"`
}
// ContractsConfig is config for each contract in the server config.
type ContractsConfig struct {
// CCTP is the address of the cctp contract
CCTP string `yaml:"cctp"`
// Bridge is the URL of the Scribe server.
Bridge string `yaml:"bridge"`
// RFQ is the address of the rfq contract
RFQ string `yaml:"rfq"`
}
// IsValid makes sure the config is valid.
func (c *Config) IsValid() error {
switch {
case c.ScribeURL == "":
return fmt.Errorf("scribe_url, %w", config.ErrRequiredGlobalField)
case c.RPCURL == "":
return fmt.Errorf("rpc_url, %w", config.ErrRequiredGlobalField)
case c.BridgeConfigAddress == "":
return fmt.Errorf("bridge_config_address, %w", config.ErrRequiredGlobalField)
case c.BridgeConfigChainID == 0:
return fmt.Errorf("bridge_config_chain_id, %w", config.ErrRequiredGlobalField)
case c.DBAddress == "":
return fmt.Errorf("db_address, %w", config.ErrRequiredGlobalField)
}
intSet := collection.Set[uint32]{}
for _, chain := range c.Chains {
err := chain.IsValid()
if err != nil {
return err
}
if intSet.Contains(chain.ChainID) {
return fmt.Errorf("chain id %d appears twice in the server", chain.ChainID)
}
intSet.Add(chain.ChainID)
}
return nil
}
// IsValid checks if the entered ChainConfig is valid.
func (c *ChainConfig) IsValid() error {
switch {
case c.ChainID == 0:
return fmt.Errorf("chain_id cannot be 0")
case c.GetLogsRange == 0:
return fmt.Errorf("get_logs_range, %w", config.ErrRequiredChainField)
case c.GetLogsBatchAmount == 0:
return fmt.Errorf("get_logs_range, %w", config.ErrRequiredChainField)
case c.BlockTime == 0:
return fmt.Errorf("block_time, %w", config.ErrRequiredChainField)
}
err := c.Contracts.IsValid()
if err != nil {
return err
}
return nil
}
// IsValid checks if the entered ContractsConfig is valid.
func (c ContractsConfig) IsValid() error {
if c.CCTP == "" && c.Bridge == "" && c.RFQ == "" {
return fmt.Errorf("one contract must be specified on each contract config")
}
return nil
}
// DecodeServerConfig parses in a config from a file.
func DecodeServerConfig(filePath string) (cfg Config, err error) {
input, err := os.ReadFile(filepath.Clean(filePath))
if err != nil {
return Config{}, fmt.Errorf("failed to read file: %w", err)
}
err = yaml.Unmarshal(input, &cfg)
if err != nil {
return Config{}, fmt.Errorf("could not unmarshall config %s: %w", ellipsis.Shorten(string(input), 30), err)
}
err = cfg.IsValid()
if err != nil {
return cfg, err
}
return cfg, nil
}