From 6777dcd23a00fa4f8f8f76d8f29d82064edcedf8 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Mon, 29 Sep 2025 21:10:39 -0400 Subject: [PATCH 1/9] Refactor: Move disabled hydrator config to setupHydrator --- cmd/hydrator.go | 27 ++++++++++++++++++++++++++- cmd/qos.go | 10 ---------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/cmd/hydrator.go b/cmd/hydrator.go index f959b22e7..2264f57a3 100644 --- a/cmd/hydrator.go +++ b/cmd/hydrator.go @@ -4,6 +4,7 @@ package main import ( "errors" + "fmt" "time" "github.com/pokt-network/poktroll/pkg/polylog" @@ -45,10 +46,34 @@ func setupEndpointHydrator( return nil, errors.New("endpoint hydrator enabled but no protocol provided. this should never happen") } + // Get configured service IDs from the protocol instance. + gatewayServiceIDs := protocolInstance.ConfiguredServiceIDs() + + // Filter out any service IDs that are manually disabled by the user. + activeQoSServices := make(map[protocol.ServiceID]gateway.QoSService) + for serviceID, qosService := range qosServices { + activeQoSServices[serviceID] = qosService + } + + for _, disabledQoSServiceIDForGateway := range hydratorConfig.QoSDisabledServiceIDs { + // Throw error if any manually disabled service IDs are not found in the protocol's configured service IDs. + if _, found := gatewayServiceIDs[disabledQoSServiceIDForGateway]; !found { + return nil, fmt.Errorf("[INVALID CONFIGURATION] QoS manually disabled for service ID: %s BUT NOT not found in protocol's configured service IDs", disabledQoSServiceIDForGateway) + } + logger.Info().Msgf("Gateway manually disabled QoS for service ID: %s", disabledQoSServiceIDForGateway) + delete(activeQoSServices, disabledQoSServiceIDForGateway) + } + + // Check if all QoS services were disabled after filtering + if len(activeQoSServices) == 0 { + logger.Warn().Msg("endpoint hydrator is fully disabled: all QoS services were manually disabled") + return nil, nil + } + endpointHydrator := gateway.EndpointHydrator{ Logger: cmdLogger, Protocol: protocolInstance, - ActiveQoSServices: qosServices, + ActiveQoSServices: activeQoSServices, RunInterval: hydratorConfig.RunInterval, MaxEndpointCheckWorkers: hydratorConfig.MaxEndpointCheckWorkers, MetricsReporter: metricsReporter, diff --git a/cmd/qos.go b/cmd/qos.go index 2978e8f6a..d23dec5dc 100644 --- a/cmd/qos.go +++ b/cmd/qos.go @@ -42,16 +42,6 @@ func getServiceQoSInstances( gatewayServiceIDs := protocolInstance.ConfiguredServiceIDs() logGatewayServiceIDs(hydratedLogger, gatewayServiceIDs) - // Remove any service IDs that are manually disabled by the user. - for _, disabledQoSServiceIDForGateway := range gatewayConfig.HydratorConfig.QoSDisabledServiceIDs { - // Throw error if any manually disabled service IDs are not found in the protocol's configured service IDs. - if _, found := gatewayServiceIDs[disabledQoSServiceIDForGateway]; !found { - return nil, fmt.Errorf("[INVALID CONFIGURATION] QoS manually disabled for service ID: %s BUT NOT not found in protocol's configured service IDs", disabledQoSServiceIDForGateway) - } - hydratedLogger.Info().Msgf("Gateway manually disabled QoS for service ID: %s", disabledQoSServiceIDForGateway) - delete(gatewayServiceIDs, disabledQoSServiceIDForGateway) - } - // Get the service configs for the current protocol qosServiceConfigs := config.QoSServiceConfigs.GetServiceConfigs(gatewayConfig) logQoSServiceConfigs(hydratedLogger, qosServiceConfigs) From 0567c9e5f2b28dc62953507c915b0ed38b41ce92 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 30 Sep 2025 10:32:00 -0400 Subject: [PATCH 2/9] Update QoS EVM config struct --- qos/evm/check_archival.go | 7 +- qos/evm/config.go | 77 +++++++++++++++++++ qos/evm/endpoint_selection.go | 12 +-- qos/evm/qos.go | 22 +++--- qos/evm/service_qos_config.go | 137 ---------------------------------- qos/evm/service_state.go | 25 +++++-- qos/evm/state_archival.go | 20 ++--- 7 files changed, 120 insertions(+), 180 deletions(-) create mode 100644 qos/evm/config.go delete mode 100644 qos/evm/service_qos_config.go diff --git a/qos/evm/check_archival.go b/qos/evm/check_archival.go index 88d5a51e4..672054132 100644 --- a/qos/evm/check_archival.go +++ b/qos/evm/check_archival.go @@ -42,18 +42,21 @@ func (e *endpointCheckArchival) getRequestID() jsonrpc.ID { return jsonrpc.IDFromInt(idArchivalCheck) } +// TODO_TECHDEBT(@adshmh): Refactor to improve encapsulation: +// - We should not need to pass the archival state to the archival check. +// // getRequest returns a JSONRPC request to check the balance of: // - the contract specified in `a.archivalCheckConfig.ContractAddress` // - at the block number specified in `a.blockNumberHex` // // For example: // '{"jsonrpc":"2.0","id":1,"method":"eth_getBalance","params":["0x28C6c06298d514Db089934071355E5743bf21d60", "0xe71e1d"]}' -func (e *endpointCheckArchival) getServicePayload(archivalState archivalState) protocol.Payload { +func (e *endpointCheckArchival) getServicePayload(archivalState *archivalState) protocol.Payload { // Pass params in this order: [, ] // eg. "params":["0x28C6c06298d514Db089934071355E5743bf21d60", "0xe71e1d"] // Reference: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getbalance params, err := jsonrpc.BuildParamsFromStringArray([2]string{ - archivalState.archivalCheckConfig.contractAddress, + archivalState.archivalCheckConfig.ContractAddress, archivalState.blockNumberHex, }) if err != nil { diff --git a/qos/evm/config.go b/qos/evm/config.go new file mode 100644 index 000000000..b5f34d39f --- /dev/null +++ b/qos/evm/config.go @@ -0,0 +1,77 @@ +package evm + +import ( + "fmt" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/protocol" +) + +// Config represents EVM-specific service configuration +type Config struct { + // Chain ID in hex format (e.g., "0x1" for Ethereum mainnet) + ChainID string `yaml:"chain_id"` + + // Sync allowance (required, must be greater than 0) + SyncAllowance uint64 `yaml:"sync_allowance"` + + // Optional: archival check configuration + ArchivalCheck *ArchivalCheckConfig `yaml:"archival_check,omitempty"` + + // Supported RPC types for this service + SupportedAPIs map[string]struct{} `yaml:"supported_apis"` +} + +// Validate validates the Config struct +func (c *Config) Validate(logger polylog.Logger, serviceID protocol.ServiceID) error { + if c.ChainID == "" { + return fmt.Errorf("service %s: chain_id is required", serviceID) + } + + if c.SyncAllowance == 0 { + return fmt.Errorf("service %s: sync_allowance is required and must be greater than 0", serviceID) + } + + if len(c.SupportedAPIs) == 0 { + return fmt.Errorf("service %s: supported_apis must contain at least one API", serviceID) + } + + // Validate ArchivalCheck if present + if c.ArchivalCheck != nil { + if err := c.ArchivalCheck.Validate(logger, serviceID); err != nil { + return err + } + } + + return nil +} + +// ArchivalCheckConfig represents the archival check configuration +type ArchivalCheckConfig struct { + // Contract address to check for archival balance (in hex format) + ContractAddress string `yaml:"contract_address"` + + // The block number when the contract first had a balance + ContractStartBlock uint64 `yaml:"contract_start_block"` + + // Threshold + Threshold uint64 `yaml:"threshold,omitempty"` +} + +// Validate validates the ArchivalCheckConfig struct +func (a *ArchivalCheckConfig) Validate(logger polylog.Logger, serviceID protocol.ServiceID) error { + if a.ContractAddress == "" { + return fmt.Errorf("service %s: archival_check.contract_address is required when archival_check is set", serviceID) + } + + if a.ContractStartBlock == 0 { + return fmt.Errorf("service %s: archival_check.contract_start_block is required when archival_check is set", serviceID) + } + + if a.Threshold == 0 { + return fmt.Errorf("service %s: archival_check.threshold is required when archival_check is set", serviceID) + } + + return nil +} diff --git a/qos/evm/endpoint_selection.go b/qos/evm/endpoint_selection.go index b55a6a6c9..adff3fc1c 100644 --- a/qos/evm/endpoint_selection.go +++ b/qos/evm/endpoint_selection.go @@ -44,8 +44,8 @@ type EndpointSelectionMetadata struct { // If numEndpoints is 0, it defaults to 1. If numEndpoints is greater than available endpoints, it returns all valid endpoints. func (ss *serviceState) SelectMultiple(availableEndpoints protocol.EndpointAddrList, numEndpoints uint) (protocol.EndpointAddrList, error) { logger := ss.logger.With("method", "SelectMultiple"). - With("chain_id", ss.serviceQoSConfig.getEVMChainID()). - With("service_id", ss.serviceQoSConfig.GetServiceID()). + With("chain_id", ss.serviceQoSConfig.ChainID). + With("service_id", ss.serviceID). With("num_endpoints", numEndpoints) logger.Info().Msgf("filtering %d available endpoints to select up to %d.", len(availableEndpoints), numEndpoints) @@ -72,8 +72,8 @@ func (ss *serviceState) SelectMultiple(availableEndpoints protocol.EndpointAddrL // Selects random endpoint if all fail validation. func (ss *serviceState) SelectWithMetadata(availableEndpoints protocol.EndpointAddrList) (EndpointSelectionResult, error) { logger := ss.logger.With("method", "SelectWithMetadata"). - With("chain_id", ss.serviceQoSConfig.getEVMChainID()). - With("service_id", ss.serviceQoSConfig.GetServiceID()) + With("chain_id", ss.serviceQoSConfig.ChainID). + With("service_id", ss.serviceID) availableCount := len(availableEndpoints) logger.Info().Msgf("filtering %d available endpoints.", availableCount) @@ -279,7 +279,7 @@ func (ss *serviceState) isBlockNumberValid(check endpointCheckBlockNumber) error // If the endpoint's block height is less than the perceived block height minus the sync allowance, // then the endpoint is behind the chain and should be filtered out. - syncAllowance := ss.serviceQoSConfig.getSyncAllowance() + syncAllowance := ss.serviceQoSConfig.SyncAllowance minAllowedBlockNumber := ss.perceivedBlockNumber - syncAllowance if parsedBlockNumber < minAllowedBlockNumber { return fmt.Errorf("%w: block number %d is outside the sync allowance relative to min allowed block number %d and sync allowance %d", @@ -300,7 +300,7 @@ func (ss *serviceState) isChainIDValid(check endpointCheckChainID) error { // Dereference pointer to show actual chain ID instead of memory address in error logs chainID := *check.chainID - expectedChainID := ss.serviceQoSConfig.getEVMChainID() + expectedChainID := ss.serviceQoSConfig.ChainID if chainID != expectedChainID { return fmt.Errorf("%w: chain ID %s does not match expected chain ID %s", errInvalidChainIDObs, chainID, expectedChainID) diff --git a/qos/evm/qos.go b/qos/evm/qos.go index 14d37d5c2..4046a7129 100644 --- a/qos/evm/qos.go +++ b/qos/evm/qos.go @@ -32,14 +32,11 @@ type QoS struct { } // NewQoSInstance builds and returns an instance of the EVM QoS service. -func NewQoSInstance(logger polylog.Logger, config EVMServiceQoSConfig) *QoS { - evmChainID := config.getEVMChainID() - serviceId := config.GetServiceID() - +func NewQoSInstance(logger polylog.Logger, serviceID protocol.ServiceID, serviceConfig Config) *QoS { logger = logger.With( "qos_instance", "evm", - "service_id", serviceId, - "evm_chain_id", evmChainID, + "service_id", serviceID, + "evm_chain_id", serviceConfig.ChainID, ) store := &endpointStore{ @@ -50,16 +47,17 @@ func NewQoSInstance(logger polylog.Logger, config EVMServiceQoSConfig) *QoS { serviceState := &serviceState{ logger: logger, - serviceQoSConfig: config, + serviceID: serviceID, + serviceQoSConfig: serviceConfig, endpointStore: store, } // TODO_CONSIDERATION(@olshansk): Archival checks are currently optional to enable iteration // and optionality. In the future, evaluate whether it should be mandatory for all EVM services. - if config.archivalCheckEnabled() { - serviceState.archivalState = archivalState{ + if serviceConfig.ArchivalCheck != nil { + serviceState.archivalState = &archivalState{ logger: logger.With("state", "archival"), - archivalCheckConfig: config.getEVMArchivalCheckConfig(), + archivalCheckConfig: serviceConfig.ArchivalCheck, // Initialize the balance consensus map. // It keeps track and maps a balance (at the configured address and contract) // to the number of occurrences seen across all endpoints. @@ -69,8 +67,8 @@ func NewQoSInstance(logger polylog.Logger, config EVMServiceQoSConfig) *QoS { evmRequestValidator := &evmRequestValidator{ logger: logger, - serviceID: serviceId, - chainID: evmChainID, + serviceID: serviceID, + chainID: serviceConfig.ChainID, serviceState: serviceState, } diff --git a/qos/evm/service_qos_config.go b/qos/evm/service_qos_config.go deleted file mode 100644 index 69bd297dc..000000000 --- a/qos/evm/service_qos_config.go +++ /dev/null @@ -1,137 +0,0 @@ -package evm - -import ( - sharedtypes "github.com/pokt-network/poktroll/x/shared/types" - - "github.com/buildwithgrove/path/protocol" -) - -// QoSType is the QoS type for the EVM blockchain. -const QoSType = "evm" - -// 128 is the default archival threshold for EVM-based chains. -// This is an opinionated value that aligns with industry standard -// practices for defining what constitutes an archival block. -const DefaultEVMArchivalThreshold = 128 - -// defaultEVMBlockNumberSyncAllowance is the default sync allowance for EVM-based chains. -// This number indicates how many blocks behind the perceived -// block number the endpoint may be and still be considered valid. -const defaultEVMBlockNumberSyncAllowance = 5 - -// ServiceQoSConfig defines the base interface for service QoS configurations. -// This avoids circular dependency with the config package. -type ServiceQoSConfig interface { - GetServiceID() protocol.ServiceID - GetServiceQoSType() string -} - -// EVMServiceQoSConfig is the configuration for the EVM service QoS. -type EVMServiceQoSConfig interface { - ServiceQoSConfig // Using locally defined interface to avoid circular dependency - getEVMChainID() string - getSyncAllowance() uint64 - getEVMArchivalCheckConfig() evmArchivalCheckConfig - archivalCheckEnabled() bool - getSupportedAPIs() map[sharedtypes.RPCType]struct{} -} - -// evmArchivalCheckConfig is the configuration for the archival check. -// -// The basic methodology is: -// 1. Select a `ContractAddress` for the chain with a frequent transaction volume and large balance. -// 2. Determine its starting block height (`ContractStartBlock`). -// 3. Set a `Threshold` for how many blocks below the current block number are considered "archival" data. -// -// With all of this data, the QoS implementation can select a random block number to check using `eth_getBalance`. -type evmArchivalCheckConfig struct { - threshold uint64 // The number of blocks below the current block number to be considered "archival" data - contractAddress string // The address of the contract to check for the archival balance. - contractStartBlock uint64 // The start block of the contract address (ie. when it first had a balance) -} - -func (c evmArchivalCheckConfig) IsEmpty() bool { - return c.contractAddress == "" || c.contractStartBlock == 0 || c.threshold == 0 -} - -// NewEVMServiceQoSConfig creates a new EVM service configuration with the specified archival check settings. -func NewEVMServiceQoSConfig( - serviceID protocol.ServiceID, - evmChainID string, - archivalCheckConfig *evmArchivalCheckConfig, - supportedAPIs map[sharedtypes.RPCType]struct{}, -) EVMServiceQoSConfig { - return evmServiceQoSConfig{ - serviceID: serviceID, - evmChainID: evmChainID, - archivalCheckConfig: archivalCheckConfig, - supportedAPIs: supportedAPIs, - } -} - -func NewEVMArchivalCheckConfig( - contractAddress string, - contractStartBlock uint64, -) *evmArchivalCheckConfig { - return &evmArchivalCheckConfig{ - threshold: DefaultEVMArchivalThreshold, - contractAddress: contractAddress, - contractStartBlock: contractStartBlock, - } -} - -// Ensure implementation satisfies interface -var _ EVMServiceQoSConfig = (*evmServiceQoSConfig)(nil) - -type evmServiceQoSConfig struct { - serviceID protocol.ServiceID - evmChainID string - syncAllowance uint64 - archivalCheckConfig *evmArchivalCheckConfig - supportedAPIs map[sharedtypes.RPCType]struct{} -} - -// GetServiceID returns the ID of the service. -// Implements the ServiceQoSConfig interface. -func (c evmServiceQoSConfig) GetServiceID() protocol.ServiceID { - return c.serviceID -} - -// GetServiceQoSType returns the QoS type of the service. -// Implements the ServiceQoSConfig interface. -func (evmServiceQoSConfig) GetServiceQoSType() string { - return QoSType -} - -// getEVMChainID returns the chain ID. -// Implements the EVMServiceQoSConfig interface. -func (c evmServiceQoSConfig) getEVMChainID() string { - return c.evmChainID -} - -// getSyncAllowance returns the amount of blocks behind the perceived -// block number the endpoint may be and still be considered valid. -func (c evmServiceQoSConfig) getSyncAllowance() uint64 { - if c.syncAllowance == 0 { - c.syncAllowance = defaultEVMBlockNumberSyncAllowance - } - return c.syncAllowance -} - -// archivalCheckEnabled returns true if the archival check is enabled. -// If the archival check is not enabled for the service, this will always return false. -func (c evmServiceQoSConfig) archivalCheckEnabled() bool { - return c.archivalCheckConfig != nil -} - -// getEVMArchivalCheckConfig returns the archival check configuration. -// Implements the EVMServiceQoSConfig interface. -func (c evmServiceQoSConfig) getEVMArchivalCheckConfig() evmArchivalCheckConfig { - return *c.archivalCheckConfig -} - -// getSupportedAPIs returns the RPC types supported by the service. -// Implements the EVMServiceQoSConfig interface. -func (c evmServiceQoSConfig) getSupportedAPIs() map[sharedtypes.RPCType]struct{} { - return c.supportedAPIs -} diff --git a/qos/evm/service_state.go b/qos/evm/service_state.go index 14e488890..540a12d27 100644 --- a/qos/evm/service_state.go +++ b/qos/evm/service_state.go @@ -31,11 +31,14 @@ var ( type serviceState struct { logger polylog.Logger + // Tracks the ID of the service + serviceID protocol.ServiceID + // serviceStateLock is a read-write mutex used to synchronize access to this struct serviceStateLock sync.RWMutex // serviceQoSConfig maintains the QoS configs for this service - serviceQoSConfig EVMServiceQoSConfig + serviceQoSConfig Config // endpointStore maintains the set of available endpoints and their quality data endpointStore *endpointStore @@ -50,7 +53,7 @@ type serviceState struct { perceivedBlockNumber uint64 // archivalState contains the current state of the EVM archival check for the service. - archivalState archivalState + archivalState *archivalState } /* -------------------- QoS Endpoint Check Generator -------------------- */ @@ -62,7 +65,7 @@ var _ gateway.QoSEndpointCheckGenerator = &serviceState{} // CheckWebsocketConnection returns true if the endpoint supports Websocket connections. func (ss *serviceState) CheckWebsocketConnection() bool { - _, supportsWebsockets := ss.serviceQoSConfig.getSupportedAPIs()[sharedtypes.RPCType_WEBSOCKET] + _, supportsWebsockets := ss.serviceQoSConfig.SupportedAPIs[sharedtypes.RPCType_WEBSOCKET.String()] return supportsWebsockets } @@ -86,7 +89,7 @@ func (ss *serviceState) GetRequiredQualityChecks(endpointAddr protocol.EndpointA // Archival check runs infrequently as the result of a request for an archival block is not expected to change regularly. // Additionally, this check will only run if the service is configured to perform archival checks. - if ss.archivalState.shouldArchivalCheckRun(endpoint.checkArchival) { + if ss.archivalState != nil && ss.archivalState.shouldArchivalCheckRun(endpoint.checkArchival) { checks = append( checks, ss.getEndpointCheck(endpoint.checkArchival.getRequestID(), endpoint.checkArchival.getServicePayload(ss.archivalState)), @@ -109,8 +112,8 @@ func (ss *serviceState) getEndpointCheck(jsonrpcReqID jsonrpc.ID, servicePayload jsonrpcReqID: servicePayload, }, // Set the chain and Service ID: this is required to generate observations with the correct chain ID. - chainID: ss.serviceQoSConfig.getEVMChainID(), - serviceID: ss.serviceQoSConfig.GetServiceID(), + chainID: ss.serviceQoSConfig.ChainID, + serviceID: ss.serviceID, // Set the origin of the request as Synthetic. // The request is generated by the QoS service to collect extra observations on endpoints. requestOrigin: qosobservations.RequestOrigin_REQUEST_ORIGIN_SYNTHETIC, @@ -135,9 +138,15 @@ func (ss *serviceState) ApplyObservations(observations *qosobservations.Observat return errNilApplyEVMObservations } + var blockNumberHex string + if ss.archivalState != nil { + blockNumberHex = ss.archivalState.blockNumberHex + } + + // TODO_TECHDEBT(@adshmh): refactor to eliminate the need for passing internal archival state fields to endpoint store. updatedEndpoints := ss.endpointStore.updateEndpointsFromObservations( evmObservations, - ss.archivalState.blockNumberHex, + blockNumberHex, ) return ss.updateFromEndpoints(updatedEndpoints) @@ -179,7 +188,7 @@ func (ss *serviceState) updateFromEndpoints(updatedEndpoints map[protocol.Endpoi } // If archival checks are enabled for the service, update the archival state. - if ss.archivalState.isEnabled() { + if ss.archivalState != nil { // Update the archival state based on the perceived block number. // When the expected balance at the archival block number is known, this becomes a no-op. ss.archivalState.updateArchivalState(ss.perceivedBlockNumber, updatedEndpoints) diff --git a/qos/evm/state_archival.go b/qos/evm/state_archival.go index 00d9cd33b..c90cef870 100644 --- a/qos/evm/state_archival.go +++ b/qos/evm/state_archival.go @@ -29,7 +29,7 @@ type archivalState struct { logger polylog.Logger // archivalCheckConfig contains all configurable values for an EVM archival check. - archivalCheckConfig evmArchivalCheckConfig + archivalCheckConfig *ArchivalCheckConfig // balanceConsensus is a map where: // - key: hex balance value for the archival block number @@ -90,17 +90,11 @@ func (as *archivalState) updateArchivalState( } } -// isEnabled returns true if archival checks are enabled for the service. -// Not all EVM services will require archival checks (for example, if a service is expected to run pruned nodes). -func (as *archivalState) isEnabled() bool { - return !as.archivalCheckConfig.IsEmpty() -} - // calculateArchivalBlockNumber determines a, archival block number based on the perceived block number. // See comment on `archivalState.blockNumberHex` in `archivalState` struct for more details on the calculation. func (as *archivalState) calculateArchivalBlockNumber(perceivedBlockNumber uint64) { - archivalThreshold := as.archivalCheckConfig.threshold - minArchivalBlock := as.archivalCheckConfig.contractStartBlock + archivalThreshold := as.archivalCheckConfig.Threshold + minArchivalBlock := as.archivalCheckConfig.ContractStartBlock var blockNumHex string // Case 1: Block number is below or equal to the archival threshold @@ -174,7 +168,7 @@ func (as *archivalState) updateExpectedBalance(updatedEndpoints map[protocol.End as.expectedBalance = balance as.logger.Info(). Str("archival_block_number", as.blockNumberHex). - Str("contract_address", as.archivalCheckConfig.contractAddress). + Str("contract_address", as.archivalCheckConfig.ContractAddress). Str("expected_balance", balance). Msg("Updated expected archival balance") as.balanceConsensus = make(map[string]int) @@ -190,10 +184,6 @@ func (as *archivalState) updateExpectedBalance(updatedEndpoints map[protocol.End // isArchivalBalanceValid returns an error if the endpoint's observed // archival balance does not match the expected archival balance. func (as *archivalState) isArchivalBalanceValid(check endpointCheckArchival) error { - if !as.isEnabled() { - return nil - } - if check.observedArchivalBalance == "" { return errNoArchivalBalanceObs } @@ -209,7 +199,7 @@ func (as *archivalState) shouldArchivalCheckRun(check endpointCheckArchival) boo // Do not perform an archival check if: // - The archival check is not enabled for the service. // - The archival block number has not yet been set in the archival state. - if !as.isEnabled() || as.blockNumberHex == "" { + if as.blockNumberHex == "" { return false } From e532d890122c65b18e72cf020f0c3e0102c51f68 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 30 Sep 2025 10:32:53 -0400 Subject: [PATCH 3/9] Update QoS Solana config structs --- qos/solana/config.go | 7 ++++ qos/solana/service_qos_config.go | 56 -------------------------------- 2 files changed, 7 insertions(+), 56 deletions(-) create mode 100644 qos/solana/config.go delete mode 100644 qos/solana/service_qos_config.go diff --git a/qos/solana/config.go b/qos/solana/config.go new file mode 100644 index 000000000..d9fe2f3a5 --- /dev/null +++ b/qos/solana/config.go @@ -0,0 +1,7 @@ +package solana + +// Config represents Solana-specific service configuration +type Config struct { + // Chain ID (e.g., "solana", "mainnet-beta") + ChainID string `yaml:"chain_id"` +} diff --git a/qos/solana/service_qos_config.go b/qos/solana/service_qos_config.go deleted file mode 100644 index 89da4cbdf..000000000 --- a/qos/solana/service_qos_config.go +++ /dev/null @@ -1,56 +0,0 @@ -package solana - -import "github.com/buildwithgrove/path/protocol" - -// QoSType is the QoS type for the Solana blockchain. -const QoSType = "solana" - -// ServiceQoSConfig defines the base interface for service QoS configurations. -// This avoids circular dependency with the config package. -type ServiceQoSConfig interface { - GetServiceID() protocol.ServiceID - GetServiceQoSType() string -} - -// SolanaServiceQoSConfig is the configuration for the Solana service QoS. -type SolanaServiceQoSConfig interface { - ServiceQoSConfig // Using locally defined interface to avoid circular dependency - getChainID() string // The Chain ID set by the preprocessor. -} - -// NewSolanaServiceQoSConfig creates a new Solana service configuration. -func NewSolanaServiceQoSConfig( - serviceID protocol.ServiceID, - chainID string, -) SolanaServiceQoSConfig { - return solanaServiceQoSConfig{ - serviceID: serviceID, - chainID: chainID, - } -} - -// Ensure implementation satisfies interface -var _ SolanaServiceQoSConfig = (*solanaServiceQoSConfig)(nil) - -type solanaServiceQoSConfig struct { - serviceID protocol.ServiceID - chainID string -} - -// GetServiceID returns the ID of the service. -// Implements the ServiceQoSConfig interface. -func (c solanaServiceQoSConfig) GetServiceID() protocol.ServiceID { - return c.serviceID -} - -// getChainID returns the chain ID configured for the Solana QoS instance. -// Implements the ServiceQoSConfig interface. -func (c solanaServiceQoSConfig) getChainID() string { - return c.chainID -} - -// GetServiceQoSType returns the QoS type of the service. -// Implements the ServiceQoSConfig interface. -func (solanaServiceQoSConfig) GetServiceQoSType() string { - return QoSType -} From 22256ef817d3f1a9a3efa101f441ee20f6c97b10 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 30 Sep 2025 10:34:44 -0400 Subject: [PATCH 4/9] QoS Solana: update the init function --- qos/solana/qos.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qos/solana/qos.go b/qos/solana/qos.go index 5d390a0fb..bfcb4bd27 100644 --- a/qos/solana/qos.go +++ b/qos/solana/qos.go @@ -2,12 +2,13 @@ package solana import ( "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/protocol" ) // NewQoSInstance builds and returns an instance of the Solana QoS service. -func NewQoSInstance(logger polylog.Logger, serviceConfig SolanaServiceQoSConfig) *QoS { - chainID := serviceConfig.getChainID() - serviceID := serviceConfig.GetServiceID() +func NewQoSInstance(logger polylog.Logger, serviceID protocol.ServiceID, serviceConfig Config) *QoS { + chainID := serviceConfig.ChainID logger = logger.With( "qos_instance", "solana", From 55ea9b3531a5dd738135d785c4ef5d080bdc1096 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 30 Sep 2025 10:53:07 -0400 Subject: [PATCH 5/9] Update QoS Cosmos config structs --- qos/cosmos/config.go | 77 +++++++++++++ qos/cosmos/qos.go | 13 +-- qos/cosmos/request_validator_checks.go | 4 +- qos/cosmos/service_qos_config.go | 105 ------------------ qos/cosmos/service_state.go | 2 +- .../service_state_endpoint_validation.go | 8 +- 6 files changed, 90 insertions(+), 119 deletions(-) create mode 100644 qos/cosmos/config.go delete mode 100644 qos/cosmos/service_qos_config.go diff --git a/qos/cosmos/config.go b/qos/cosmos/config.go new file mode 100644 index 000000000..f79b71714 --- /dev/null +++ b/qos/cosmos/config.go @@ -0,0 +1,77 @@ +package cosmos + +import ( + "fmt" + + "github.com/pokt-network/poktroll/pkg/polylog" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" + + "github.com/buildwithgrove/path/protocol" +) + +// Config represents Cosmos SDK-specific service configuration +type Config struct { + // Cosmos SDK chain ID (e.g., "cosmoshub-4") + CosmosChainID string `yaml:"chain_id"` + + // EVM chain ID in hex format for Cosmos chains with native EVM support (e.g., XRPLEVM) + EVMChainID string `yaml:"evm_chain_id"` + + // Sync allowance override + SyncAllowance uint64 `yaml:"sync_allowance"` + + // Supported RPC types for this service + SupportedAPIs map[string]struct{} `yaml:"supported_apis"` +} + +// Validate validates the Cosmos service configuration +func (c *Config) Validate(logger polylog.Logger, serviceID protocol.ServiceID) error { + if c.CosmosChainID == "" { + err := fmt.Errorf("service %q: chain_id cannot be empty", serviceID) + logger.Error().Err(err).Msg("Validation failed") + return err + } + + if c.EVMChainID == "" { + err := fmt.Errorf("service %q: evm_chain_id cannot be empty", serviceID) + logger.Error().Err(err).Msg("Validation failed") + return err + } + + if c.SyncAllowance == 0 { + err := fmt.Errorf("service %q: sync_allowance must be greater than 0", serviceID) + logger.Error().Err(err).Msg("Validation failed") + return err + } + + if c.SupportedAPIs == nil || len(c.SupportedAPIs) == 0 { + err := fmt.Errorf("service %q: supported_apis map cannot be empty", serviceID) + logger.Error().Err(err).Msg("Validation failed") + return err + } + + // Validate each API type against the RPCType enum + for apiType := range c.SupportedAPIs { + rpcTypeValue, exists := sharedtypes.RPCType_value[apiType] + if !exists || rpcTypeValue <= 0 { + err := fmt.Errorf("service %q: invalid RPC type %q in supported_apis", serviceID, apiType) + logger.Error().Err(err).Msg("Validation failed") + return err + } + } + + return nil +} + +// GetSupportedAPIs returns the supported RPC types as a map of RPCType enum values +func (c *Config) GetSupportedAPIs() map[sharedtypes.RPCType]struct{} { + result := make(map[sharedtypes.RPCType]struct{}, len(c.SupportedAPIs)) + + for apiType := range c.SupportedAPIs { + if rpcTypeValue, exists := sharedtypes.RPCType_value[apiType]; exists { + result[sharedtypes.RPCType(rpcTypeValue)] = struct{}{} + } + } + + return result +} diff --git a/qos/cosmos/qos.go b/qos/cosmos/qos.go index 39e68fc88..677772ca4 100644 --- a/qos/cosmos/qos.go +++ b/qos/cosmos/qos.go @@ -32,16 +32,15 @@ type QoS struct { } // NewQoSInstance builds and returns an instance of the CosmosSDK QoS service. -func NewQoSInstance(logger polylog.Logger, config CosmosSDKServiceQoSConfig) *QoS { - serviceId := config.GetServiceID() +func NewQoSInstance(logger polylog.Logger, serviceID protocol.ServiceID, config Config) *QoS { + cosmosChainID := config.CosmosChainID - cosmosChainID := config.getCosmosSDKChainID() // Some CosmosSDK services may have an EVM chain ID. For example, XRPLEVM. - evmChainID := config.getEVMChainID() + evmChainID := config.EVMChainID logger = logger.With( "qos_instance", "cosmossdk", - "service_id", serviceId, + "service_id", serviceID, "cosmos_chain_id", cosmosChainID, "evm_chain_id", evmChainID, ) @@ -60,11 +59,11 @@ func NewQoSInstance(logger polylog.Logger, config CosmosSDKServiceQoSConfig) *Qo requestValidator := &requestValidator{ logger: logger, - serviceID: serviceId, + serviceID: serviceID, cosmosChainID: cosmosChainID, evmChainID: evmChainID, serviceState: serviceState, - supportedAPIs: config.getSupportedAPIs(), + supportedAPIs: config.GetSupportedAPIs(), } return &QoS{ diff --git a/qos/cosmos/request_validator_checks.go b/qos/cosmos/request_validator_checks.go index f47ddb2ff..6dd25d27c 100644 --- a/qos/cosmos/request_validator_checks.go +++ b/qos/cosmos/request_validator_checks.go @@ -20,7 +20,7 @@ var _ gateway.QoSEndpointCheckGenerator = &requestValidator{} // CheckWebsocketConnection returns true if the endpoint supports Websocket connections. func (rv *requestValidator) CheckWebsocketConnection() bool { - _, supportsWebsockets := rv.serviceState.serviceQoSConfig.getSupportedAPIs()[sharedtypes.RPCType_WEBSOCKET] + _, supportsWebsockets := rv.serviceState.serviceQoSConfig.GetSupportedAPIs()[sharedtypes.RPCType_WEBSOCKET] return supportsWebsockets } @@ -31,7 +31,7 @@ func (rv *requestValidator) GetRequiredQualityChecks(endpointAddr protocol.Endpo endpoint := rv.serviceState.endpointStore.getEndpoint(endpointAddr) // Get the RPC types supported by the CosmosSDK service. - supportedAPIs := rv.serviceState.serviceQoSConfig.getSupportedAPIs() + supportedAPIs := rv.serviceState.serviceQoSConfig.GetSupportedAPIs() // List of all synthetic QoS checks required for the endpoint. var checks []gateway.RequestQoSContext diff --git a/qos/cosmos/service_qos_config.go b/qos/cosmos/service_qos_config.go deleted file mode 100644 index 33e9de88d..000000000 --- a/qos/cosmos/service_qos_config.go +++ /dev/null @@ -1,105 +0,0 @@ -package cosmos - -import ( - sharedtypes "github.com/pokt-network/poktroll/x/shared/types" - - "github.com/buildwithgrove/path/protocol" -) - -// QoSType is the QoS type for the CosmosSDK blockchain. -const QoSType = "cosmossdk" - -// defaultCosmosSDKBlockNumberSyncAllowance is the default sync allowance for CosmosSDK-based chains. -// This number indicates how many blocks behind the perceived -// block number the endpoint may be and still be considered valid. -const defaultCosmosSDKBlockNumberSyncAllowance = 5 - -// ServiceQoSConfig defines the base interface for service QoS configurations. -// This avoids circular dependency with the config package. -type ServiceQoSConfig interface { - GetServiceID() protocol.ServiceID - GetServiceQoSType() string -} - -// CosmosSDKServiceQoSConfig is the configuration for the CosmosSDK service QoS. -type CosmosSDKServiceQoSConfig interface { - ServiceQoSConfig // Using locally defined interface to avoid circular dependency - getCosmosSDKChainID() string - getEVMChainID() string - getSyncAllowance() uint64 - getSupportedAPIs() map[sharedtypes.RPCType]struct{} -} - -// NewCosmosSDKServiceQoSConfig creates a new CosmosSDK service configuration. -func NewCosmosSDKServiceQoSConfig( - serviceID protocol.ServiceID, - cosmosSDKChainID string, - evmChainID string, - supportedAPIs map[sharedtypes.RPCType]struct{}, -) CosmosSDKServiceQoSConfig { - return cosmosSDKServiceQoSConfig{ - serviceID: serviceID, - cosmosSDKChainID: cosmosSDKChainID, - evmChainID: evmChainID, - supportedAPIs: supportedAPIs, - } -} - -// Ensure implementation satisfies interface -var _ CosmosSDKServiceQoSConfig = (*cosmosSDKServiceQoSConfig)(nil) - -type cosmosSDKServiceQoSConfig struct { - serviceID protocol.ServiceID - cosmosSDKChainID string - evmChainID string - syncAllowance uint64 - supportedAPIs map[sharedtypes.RPCType]struct{} -} - -// GetServiceID returns the ID of the service. -// Implements the ServiceQoSConfig interface. -func (c cosmosSDKServiceQoSConfig) GetServiceID() protocol.ServiceID { - return c.serviceID -} - -// GetServiceQoSType returns the QoS type of the service. -// Implements the ServiceQoSConfig interface. -func (cosmosSDKServiceQoSConfig) GetServiceQoSType() string { - return QoSType -} - -// getCosmosSDKChainID returns the chain ID. -// Implements the CosmosSDKServiceQoSConfig interface. -func (c cosmosSDKServiceQoSConfig) getCosmosSDKChainID() string { - return c.cosmosSDKChainID -} - -// getEVMChainID returns the EVM chain ID. -// This is necessary for Cosmos chains that have native EVM support; XRPLEVM, etc... -// Implements the CosmosSDKServiceQoSConfig interface. -func (c cosmosSDKServiceQoSConfig) getEVMChainID() string { - return c.evmChainID -} - -// getSyncAllowance returns the amount of blocks behind the perceived -// block number the endpoint may be and still be considered valid. -func (c cosmosSDKServiceQoSConfig) getSyncAllowance() uint64 { - if c.syncAllowance == 0 { - c.syncAllowance = defaultCosmosSDKBlockNumberSyncAllowance - } - return c.syncAllowance -} - -// getSupportedAPIs returns the RPC types supported by the service. -// For example, XRPLEVM supports the following RPC types: -// - JSON_RPC -// - REST -// - COMET_BFT -// - WEBSOCKET (does not currently have a QoS quality check system in PATH) -// -// This is used: -// 1. to validate the request and whether this service supports the request's RPC type -// 2. to determine the appropriate synthetic QoS endpoint checks to run -func (c cosmosSDKServiceQoSConfig) getSupportedAPIs() map[sharedtypes.RPCType]struct{} { - return c.supportedAPIs -} diff --git a/qos/cosmos/service_state.go b/qos/cosmos/service_state.go index e8c9b0780..44fa8b3ba 100644 --- a/qos/cosmos/service_state.go +++ b/qos/cosmos/service_state.go @@ -33,7 +33,7 @@ type serviceState struct { serviceStateLock sync.RWMutex // serviceQoSConfig maintains the QoS configs for this service - serviceQoSConfig CosmosSDKServiceQoSConfig + serviceQoSConfig Config // endpointStore maintains the set of available endpoints and their quality data endpointStore *endpointStore diff --git a/qos/cosmos/service_state_endpoint_validation.go b/qos/cosmos/service_state_endpoint_validation.go index cf5e9c972..7c658b491 100644 --- a/qos/cosmos/service_state_endpoint_validation.go +++ b/qos/cosmos/service_state_endpoint_validation.go @@ -57,7 +57,7 @@ func (ss *serviceState) basicEndpointValidation(endpoint endpoint) error { } // Get the RPC types supported by the CosmosSDK service. - supportedAPIs := ss.serviceQoSConfig.getSupportedAPIs() + supportedAPIs := ss.serviceQoSConfig.GetSupportedAPIs() // If the service supports CometBFT, validate the endpoint's CometBFT checks. if _, ok := supportedAPIs[sharedtypes.RPCType_COMET_BFT]; ok { @@ -136,7 +136,7 @@ func (ss *serviceState) isCometBFTStatusValid(check endpointCheckCometBFTStatus) return fmt.Errorf("%w: %v", errNoCometBFTStatusObs, err) } - expectedChainID := ss.serviceQoSConfig.getCosmosSDKChainID() + expectedChainID := ss.serviceQoSConfig.CosmosChainID if chainID != expectedChainID { return fmt.Errorf("%w: chain ID %s does not match expected chain ID %s", errInvalidCometBFTChainIDObs, chainID, expectedChainID) @@ -205,7 +205,7 @@ func (ss *serviceState) isCosmosStatusValid(check endpointCheckCosmosStatus) err // validateBlockHeightSyncAllowance returns an error if: // - The endpoint's block height is outside the latest block height minus the sync allowance. func (ss *serviceState) validateBlockHeightSyncAllowance(latestBlockHeight uint64) error { - syncAllowance := ss.serviceQoSConfig.getSyncAllowance() + syncAllowance := ss.serviceQoSConfig.SyncAllowance minAllowedBlockNumber := ss.perceivedBlockNumber - syncAllowance if latestBlockHeight < minAllowedBlockNumber { return fmt.Errorf("%w: block number %d is outside the sync allowance relative to min allowed block number %d and sync allowance %d", @@ -234,7 +234,7 @@ func (ss *serviceState) isEVMChainIDValid(check endpointCheckEVMChainID) error { return err } - expectedEVMChainID := ss.serviceQoSConfig.getEVMChainID() + expectedEVMChainID := ss.serviceQoSConfig.EVMChainID if evmChainID != expectedEVMChainID { return fmt.Errorf("%w: chain ID %s does not match expected chain ID %s", errInvalidEVMChainIDObs, evmChainID, expectedEVMChainID) From 6c44ac611f57250e83d05e004e10533782132619 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 30 Sep 2025 15:06:23 -0400 Subject: [PATCH 6/9] Add logging to QoS configs --- cmd/qos.go | 79 +-- config/config.go | 1 + config/qos.go | 149 ++++++ config/service_qos_config.go | 997 ----------------------------------- qos/cosmos/config.go | 11 + qos/cosmos/qos.go | 2 +- qos/cosmos/service_state.go | 2 +- qos/evm/config.go | 24 + qos/evm/qos.go | 2 +- qos/evm/service_state.go | 2 +- qos/solana/config.go | 25 + qos/solana/qos.go | 2 +- 12 files changed, 225 insertions(+), 1071 deletions(-) create mode 100644 config/qos.go delete mode 100644 config/service_qos_config.go diff --git a/cmd/qos.go b/cmd/qos.go index d23dec5dc..833703deb 100644 --- a/cmd/qos.go +++ b/cmd/qos.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "strings" "github.com/pokt-network/poktroll/pkg/polylog" @@ -9,9 +8,6 @@ import ( "github.com/buildwithgrove/path/config" "github.com/buildwithgrove/path/gateway" "github.com/buildwithgrove/path/protocol" - "github.com/buildwithgrove/path/qos/cosmos" - "github.com/buildwithgrove/path/qos/evm" - "github.com/buildwithgrove/path/qos/solana" ) // getServiceQoSInstances returns all QoS instances to be used by the Gateway and the EndpointHydrator. @@ -20,10 +16,6 @@ func getServiceQoSInstances( gatewayConfig config.GatewayConfig, protocolInstance gateway.Protocol, ) (map[protocol.ServiceID]gateway.QoSService, error) { - // TODO_TECHDEBT(@adshmh): refactor this function to remove the - // need to manually add entries for every new QoS implementation. - qosServices := make(map[protocol.ServiceID]gateway.QoSService) - // Create a logger for this function's own messages with method-specific context hydratedLogger := logger.With("module", "qos").With("method", "getServiceQoSInstances").With("protocol", protocolInstance.Name()) @@ -42,59 +34,18 @@ func getServiceQoSInstances( gatewayServiceIDs := protocolInstance.ConfiguredServiceIDs() logGatewayServiceIDs(hydratedLogger, gatewayServiceIDs) - // Get the service configs for the current protocol - qosServiceConfigs := config.QoSServiceConfigs.GetServiceConfigs(gatewayConfig) - logQoSServiceConfigs(hydratedLogger, qosServiceConfigs) - - // Initialize QoS services for all service IDs with a corresponding QoS - // implementation, as defined in the `config/service_qos.go` file. - for _, qosServiceConfig := range qosServiceConfigs { - serviceID := qosServiceConfig.GetServiceID() - // Skip service IDs that are not configured for the PATH instance. - if _, found := gatewayServiceIDs[serviceID]; !found { - hydratedLogger.Warn().Msgf("Service ID %s has an available QoS configuration but is not configured for the gateway. Skipping...", serviceID) - continue - } - - switch qosServiceConfig.GetServiceQoSType() { - case evm.QoSType: - evmServiceQoSConfig, ok := qosServiceConfig.(evm.EVMServiceQoSConfig) - if !ok { - return nil, fmt.Errorf("SHOULD NEVER HAPPEN: error building QoS instances: service ID %q is not an EVM service", serviceID) - } - - evmQoS := evm.NewQoSInstance(qosLogger, evmServiceQoSConfig) - qosServices[serviceID] = evmQoS - - hydratedLogger.With("service_id", serviceID).Debug().Msg("Added EVM QoS instance for the service ID.") - - case cosmos.QoSType: - cosmosSDKServiceQoSConfig, ok := qosServiceConfig.(cosmos.CosmosSDKServiceQoSConfig) - if !ok { - return nil, fmt.Errorf("SHOULD NEVER HAPPEN: error building QoS instances: service ID %q is not a CosmosSDK service", serviceID) - } - - cosmosSDKQoS := cosmos.NewQoSInstance(qosLogger, cosmosSDKServiceQoSConfig) - qosServices[serviceID] = cosmosSDKQoS - - hydratedLogger.With("service_id", serviceID).Debug().Msg("Added CosmosSDK QoS instance for the service ID.") - - case solana.QoSType: - solanaServiceQoSConfig, ok := qosServiceConfig.(solana.SolanaServiceQoSConfig) - if !ok { - return nil, fmt.Errorf("SHOULD NEVER HAPPEN: error building QoS instances: service ID %q is not a Solana service", serviceID) - } - - solanaQoS := solana.NewQoSInstance(qosLogger, solanaServiceQoSConfig) - qosServices[serviceID] = solanaQoS - - hydratedLogger.With("service_id", serviceID).Debug().Msg("Added Solana QoS instance for the service ID.") - default: - return nil, fmt.Errorf("SHOULD NEVER HAPPEN: error building QoS instances: service ID %q not supported by PATH", serviceID) - } + // TODO_TECHDEBT(@adshmh): Refactor to move the Validate method call to GatewayConfig struct. + // + // Validate the QoS services config. + if err := gatewayConfig.ServicesQoSConfigs.Validate(hydratedLogger, gatewayServiceIDs); err != nil { + return nil, err } - return qosServices, nil + // Log services QoS configs. + gatewayConfig.ServicesQoSConfigs.LogServicesConfigs(hydratedLogger) + + // Use the services QoS configs to build QoS instances. + return gatewayConfig.ServicesQoSConfigs.BuildQoSInstances(qosLogger) } // logGatewayServiceIDs outputs the available service IDs for the gateway. @@ -106,13 +57,3 @@ func logGatewayServiceIDs(logger polylog.Logger, serviceConfigs map[protocol.Ser } logger.Info().Msgf("Service IDs configured by the gateway: %s.", strings.Join(serviceIDs, ", ")) } - -// logQoSServiceConfigs outputs the configured service IDs for the gateway. -func logQoSServiceConfigs(logger polylog.Logger, serviceConfigs []config.ServiceQoSConfig) { - // Output service IDs with QoS configurations - serviceIDs := make([]string, 0, len(serviceConfigs)) - for _, serviceConfig := range serviceConfigs { - serviceIDs = append(serviceIDs, string(serviceConfig.GetServiceID())) - } - logger.Info().Msgf("Service IDs with available QoS configurations: %s.", strings.Join(serviceIDs, ", ")) -} diff --git a/config/config.go b/config/config.go index f3b9314b9..29427a204 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,7 @@ type GatewayConfig struct { HydratorConfig EndpointHydratorConfig `yaml:"hydrator_config"` MessagingConfig MessagingConfig `yaml:"messaging_config"` DataReporterConfig HTTPDataReporterConfig `yaml:"data_reporter_config"` + ServicesQoSConfigs *ServicesQoSConfig `yaml:"services_qos_configs"` } // LoadGatewayConfigFromYAML reads a YAML configuration file from the specified path diff --git a/config/qos.go b/config/qos.go new file mode 100644 index 000000000..b2a887b26 --- /dev/null +++ b/config/qos.go @@ -0,0 +1,149 @@ +package config + +import ( + "fmt" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/gateway" + "github.com/buildwithgrove/path/protocol" + "github.com/buildwithgrove/path/qos/cosmos" + "github.com/buildwithgrove/path/qos/evm" + "github.com/buildwithgrove/path/qos/solana" +) + +// ServicesQoSConfig represents the top-level configuration for all services +type ServicesQoSConfig struct { + Services map[string]ServiceQoSConfig `yaml:"services"` +} + +// Validate ensures the ServicesQoSConfig is valid +func (sc *ServicesQoSConfig) Validate(logger polylog.Logger, gatewayServices map[protocol.ServiceID]struct{}) error { + if sc.Services == nil { + err := fmt.Errorf("services map cannot be nil") + logger.Error().Err(err).Msg("Validation failed") + return err + } + + hasErrors := false + + for serviceIDStr, serviceConfig := range sc.Services { + serviceID := protocol.ServiceID(serviceIDStr) + + // Check if service is configured in the gateway + if _, found := gatewayServices[serviceID]; !found { + logger.Warn().Msgf("Service ID %s has an available QoS configuration but is not configured for the gateway. Skipping...", serviceID) + continue + } + + // Validate the service configuration + if err := serviceConfig.Validate(logger, serviceID); err != nil { + logger.Error().Err(err).Msg("Validation failed for service") + hasErrors = true + } + } + + if hasErrors { + return fmt.Errorf("validation failed for one or more services") + } + + return nil +} + +// BuildQoSInstances creates QoS instances for all configured services +func (sc *ServicesQoSConfig) BuildQoSInstances(logger polylog.Logger) (map[protocol.ServiceID]gateway.QoSService, error) { + qosServices := make(map[protocol.ServiceID]gateway.QoSService) + + qosLogger := logger.With("module", "qos") + + for serviceIDStr, serviceConfig := range sc.Services { + serviceID := protocol.ServiceID(serviceIDStr) + + qosInstance, err := serviceConfig.BuildQoSInstance(qosLogger, serviceID) + if err != nil { + return nil, fmt.Errorf("failed to build QoS instance for service %q: %w", serviceID, err) + } + + qosServices[serviceID] = qosInstance + qosLogger.With("service_id", serviceID).Debug().Msg("Added QoS instance for the service ID.") + } + + return qosServices, nil +} + +// LogServicesConfigs logs the configuration for every service ID +func (sc *ServicesQoSConfig) LogServicesConfigs(logger polylog.Logger) { + if sc.Services == nil || len(sc.Services) == 0 { + logger.Warn().Msg("No services configured in QoS config") + return + } + + logger.Info().Msgf("Logging QoS configuration for %d service(s)", len(sc.Services)) + + for serviceIDStr, serviceConfig := range sc.Services { + serviceID := protocol.ServiceID(serviceIDStr) + serviceLogger := logger.With("service_id", serviceID) + serviceConfig.LogConfig(serviceLogger) + } +} + +// ServiceQoSConfig represents a single service configuration entry +type ServiceQoSConfig struct { + // EVM-specific configuration (non-nil indicates this is an EVM service) + EVM *evm.Config `yaml:"evm,omitempty"` + + // Cosmos SDK-specific configuration (non-nil indicates this is a Cosmos SDK service) + Cosmos *cosmos.Config `yaml:"cosmos,omitempty"` + + // Solana-specific configuration (non-nil indicates this is a Solana service) + Solana *solana.Config `yaml:"solana,omitempty"` +} + +// Validate validates the service configuration +func (sc *ServiceQoSConfig) Validate(logger polylog.Logger, serviceID protocol.ServiceID) error { + if sc.EVM != nil { + return sc.EVM.Validate(logger, serviceID) + } + + if sc.Cosmos != nil { + return sc.Cosmos.Validate(logger, serviceID) + } + + if sc.Solana != nil { + return sc.Solana.Validate(logger, serviceID) + } + + return fmt.Errorf("service %q has no configuration: all config fields are nil", serviceID) +} + +// BuildQoSInstance creates a QoS instance for this service configuration +func (sc *ServiceQoSConfig) BuildQoSInstance(logger polylog.Logger, serviceID protocol.ServiceID) (gateway.QoSService, error) { + if sc.EVM != nil { + return evm.NewQoSInstance(logger, serviceID, sc.EVM), nil + } + + if sc.Cosmos != nil { + return cosmos.NewQoSInstance(logger, serviceID, sc.Cosmos), nil + } + + if sc.Solana != nil { + return solana.NewQoSInstance(logger, serviceID, sc.Solana), nil + } + + return nil, fmt.Errorf("service %q has no valid configuration", serviceID) +} + +// LogConfig logs the configuration for this service +func (sc *ServiceQoSConfig) LogConfig(logger polylog.Logger) { + if sc.EVM != nil { + sc.EVM.LogConfig(logger) + } + + if sc.Cosmos != nil { + sc.Cosmos.LogConfig(logger) + } + + if sc.Solana != nil { + sc.Solana.LogConfig(logger) + } +} diff --git a/config/service_qos_config.go b/config/service_qos_config.go deleted file mode 100644 index faa64e6c1..000000000 --- a/config/service_qos_config.go +++ /dev/null @@ -1,997 +0,0 @@ -package config - -import ( - sharedtypes "github.com/pokt-network/poktroll/x/shared/types" - - "github.com/buildwithgrove/path/protocol" - "github.com/buildwithgrove/path/qos/cosmos" - "github.com/buildwithgrove/path/qos/evm" - "github.com/buildwithgrove/path/qos/solana" -) - -// How to add archival checks: https://path.grove.city/learn/qos/adding_new_archival - -// IMPORTANT: PATH requires service IDs to be registered here for Quality of Service (QoS) endpoint checks. -// Unregistered services use NoOp QoS type with random endpoint selection and no monitoring. - -var _ ServiceQoSConfig = (evm.EVMServiceQoSConfig)(nil) -var _ ServiceQoSConfig = (cosmos.CosmosSDKServiceQoSConfig)(nil) -var _ ServiceQoSConfig = (solana.SolanaServiceQoSConfig)(nil) - -type ServiceQoSConfig interface { - GetServiceID() protocol.ServiceID - GetServiceQoSType() string -} - -// qosServiceConfigs captures the list of blockchains that PATH supports QoS for. -type qosServiceConfigs struct { - shannonServices []ServiceQoSConfig -} - -// GetServiceConfigs returns the service configs for the provided protocol supported by the Gateway. -func (c qosServiceConfigs) GetServiceConfigs(config GatewayConfig) []ServiceQoSConfig { - return shannonServices -} - -// The QoSServiceConfigs map associates each supported service ID with a specific -// implementation of the gateway.QoSService interface. -var QoSServiceConfigs = qosServiceConfigs{ - shannonServices: shannonServices, -} - -const ( - defaultEVMChainID = "0x1" // ETH Mainnet (1) - defaultCosmosSDKChainID = "cosmoshub-4" -) - -// shannonServices is the list of QoS service configs for the Shannon protocol. -var shannonServices = []ServiceQoSConfig{ - // *** EVM Services (Archival) *** - - // Arbitrum One - evm.NewEVMServiceQoSConfig( - "arb-one", "0xa4b1", - evm.NewEVMArchivalCheckConfig( - // https://arbiscan.io/address/0xb38e8c17e38363af6ebdcb3dae12e0243582891d - "0xb38e8c17e38363af6ebdcb3dae12e0243582891d", - // Contract start block - 3_057_700, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Arbitrum Sepolia Testnet - evm.NewEVMServiceQoSConfig( - "arb-sepolia-testnet", - "0x66EEE", - evm.NewEVMArchivalCheckConfig( - // https://sepolia.arbiscan.io/address/0x22b65d0b9b59af4d3ed59f18b9ad53f5f4908b54 - "0x22b65d0b9b59af4d3ed59f18b9ad53f5f4908b54", - // Contract start block - 132_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Avalanche - evm.NewEVMServiceQoSConfig( - "avax", - "0xa86a", - evm.NewEVMArchivalCheckConfig( - // https://avascan.info/blockchain/c/address/0x9f8c163cBA728e99993ABe7495F06c0A3c8Ac8b9 - "0x9f8c163cBA728e99993ABe7495F06c0A3c8Ac8b9", - // Contract start block - 5_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Avalanche-DFK - evm.NewEVMServiceQoSConfig( - "avax-dfk", - "0xd2af", - evm.NewEVMArchivalCheckConfig( - // https://avascan.info/blockchain/dfk/address/0xCCb93dABD71c8Dad03Fc4CE5559dC3D89F67a260 - "0xCCb93dABD71c8Dad03Fc4CE5559dC3D89F67a260", - // Contract start block - 45_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Base - evm.NewEVMServiceQoSConfig( - "base", - "0x2105", - evm.NewEVMArchivalCheckConfig( - // https://basescan.org/address/0x3304e22ddaa22bcdc5fca2269b418046ae7b566a - "0x3304E22DDaa22bCdC5fCa2269b418046aE7b566A", - // Contract start block - 4_504_400, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - sharedtypes.RPCType_WEBSOCKET: {}, - }, - ), - - // Base Sepolia Testnet - evm.NewEVMServiceQoSConfig( - "base-sepolia-testnet", - "0x14a34", - evm.NewEVMArchivalCheckConfig( - // https://sepolia.basescan.org/address/0xbab76e4365a2dff89ddb2d3fc9994103b48886c0 - "0xbab76e4365a2dff89ddb2d3fc9994103b48886c0", - // Contract start block - 13_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Berachain - evm.NewEVMServiceQoSConfig( - "bera", - "0x138de", - evm.NewEVMArchivalCheckConfig( - // https://berascan.com/address/0x6969696969696969696969696969696969696969 - "0x6969696969696969696969696969696969696969", - // Contract start block - 2_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Blast - evm.NewEVMServiceQoSConfig( - "blast", - "0x13e31", - evm.NewEVMArchivalCheckConfig( - // https://blastscan.io/address/0x4300000000000000000000000000000000000004 - "0x4300000000000000000000000000000000000004", - // Contract start block - 1_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // BNB Smart Chain - evm.NewEVMServiceQoSConfig( - "bsc", - "0x38", - evm.NewEVMArchivalCheckConfig( - // https://bsctrace.com/address/0xfb50526f49894b78541b776f5aaefe43e3bd8590 - "0xfb50526f49894b78541b776f5aaefe43e3bd8590", - // Contract start block - 33_049_200, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - sharedtypes.RPCType_WEBSOCKET: {}, - }, - ), - - // Boba - evm.NewEVMServiceQoSConfig( - "boba", - "0x120", - evm.NewEVMArchivalCheckConfig( - // https://bobascan.com/address/0x3A92cA39476fF84Dc579C868D4D7dE125513B034 - "0x3A92cA39476fF84Dc579C868D4D7dE125513B034", - // Contract start block - 3_060_300, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Celo - evm.NewEVMServiceQoSConfig( - "celo", - "0xa4ec", - evm.NewEVMArchivalCheckConfig( - // https://celo.blockscout.com/address/0xf89d7b9c864f589bbF53a82105107622B35EaA40 - "0xf89d7b9c864f589bbF53a82105107622B35EaA40", - // Contract start block - 20_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Ethereum - evm.NewEVMServiceQoSConfig( - "eth", - defaultEVMChainID, - evm.NewEVMArchivalCheckConfig( - // https://etherscan.io/address/0x28C6c06298d514Db089934071355E5743bf21d60 - "0x28C6c06298d514Db089934071355E5743bf21d60", - // Contract start block - 12_300_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - sharedtypes.RPCType_WEBSOCKET: {}, - }, - ), - - // Ethereum Holesky Testnet - evm.NewEVMServiceQoSConfig( - "eth-holesky-testnet", - "0x4268", - evm.NewEVMArchivalCheckConfig( - // https://holesky.etherscan.io/address/0xc6392ad8a14794ea57d237d12017e7295bea2363 - "0xc6392ad8a14794ea57d237d12017e7295bea2363", - // Contract start block - 1_900_384, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Ethereum Sepolia Testnet - evm.NewEVMServiceQoSConfig( - "eth-sepolia-testnet", - "0xaa36a7", - evm.NewEVMArchivalCheckConfig( - // https://sepolia.etherscan.io/address/0xc0f3833b7e7216eecd9f6bc2c7927a7aa36ab58b - "0xc0f3833b7e7216eecd9f6bc2c7927a7aa36ab58b", - // Contract start block - 6_412_177, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Fantom - evm.NewEVMServiceQoSConfig( - "fantom", - "0xfa", - evm.NewEVMArchivalCheckConfig( - // https://explorer.fantom.network/address/0xaabf86ab3646a7064aa2f61e5959e39129ca46b6 - "0xaabf86ab3646a7064aa2f61e5959e39129ca46b6", - // Contract start block - 110_633_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Fuse - evm.NewEVMServiceQoSConfig( - "fuse", - "0x7a", - evm.NewEVMArchivalCheckConfig( - // https://explorer.fuse.io/address/0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 - "0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79", - // Contract start block - 15_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Giwa - // TODO_NEXT(@commoddity): Update to use correct EVM chain ID once `giwa` mainnet is live. - // TODO_NEXT(@commoddity): Add archival check config for Giwa once `giwa` mainnet is live. - evm.NewEVMServiceQoSConfig("giwa", "0x1", nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // Giwa Sepolia Testnet - evm.NewEVMServiceQoSConfig( - "giwa-sepolia-testnet", - "0x164ce", - evm.NewEVMArchivalCheckConfig( - // https://sepolia-explorer.giwa.io/address/0xA2a51Cca837B8ebc00dA2810e72F386Ee0dD08a0 - "0xA2a51Cca837B8ebc00dA2810e72F386Ee0dD08a0", - // Contract start block - 3_456_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Gnosis - evm.NewEVMServiceQoSConfig( - "gnosis", - "0x64", - evm.NewEVMArchivalCheckConfig( - // https://gnosisscan.io/address/0xe91d153e0b41518a2ce8dd3d7944fa863463a97d - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", - // Contract start block - 20_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Harmony-0 - evm.NewEVMServiceQoSConfig( - "harmony", - "0x63564c40", - evm.NewEVMArchivalCheckConfig( - // https://explorer.harmony.one/address/one19senwle0ezp3he6ed9xkc7zeg5rs94r0ecpp0a?shard=0 - "one19senwle0ezp3he6ed9xkc7zeg5rs94r0ecpp0a", - // Contract start block - 60_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Ink - evm.NewEVMServiceQoSConfig( - "ink", - "0xdef1", - evm.NewEVMArchivalCheckConfig( - // https://explorer.inkonchain.com/address/0x4200000000000000000000000000000000000006 - "0x4200000000000000000000000000000000000006", - // Contract start block - 4_500_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // IoTeX - evm.NewEVMServiceQoSConfig( - "iotex", - "0x1251", - evm.NewEVMArchivalCheckConfig( - // https://iotexscan.io/address/0x0a7f9ea31ca689f346e1661cf73a47c69d4bd883#transactions - "0x0a7f9ea31ca689f346e1661cf73a47c69d4bd883", - // Contract start block - 6_440_916, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Kaia - evm.NewEVMServiceQoSConfig( - "kaia", - "0x2019", - evm.NewEVMArchivalCheckConfig( - // https://www.kaiascan.io/address/0x0051ef9259c7ec0644a80e866ab748a2f30841b3 - "0x0051ef9259c7ec0644a80e866ab748a2f30841b3", - // Contract start block - 170_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Linea - evm.NewEVMServiceQoSConfig( - "linea", - "0xe708", - evm.NewEVMArchivalCheckConfig( - // https://lineascan.build/address/0xf89d7b9c864f589bbf53a82105107622b35eaa40 - "0xf89d7b9c864f589bbf53a82105107622b35eaa40", - // Contract start block - 10_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Mantle - evm.NewEVMServiceQoSConfig( - "mantle", - "0x1388", - evm.NewEVMArchivalCheckConfig( - // https://explorer.mantle.xyz/address/0x588846213A30fd36244e0ae0eBB2374516dA836C - "0x588846213A30fd36244e0ae0eBB2374516dA836C", - // Contract start block - 60_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Metis - evm.NewEVMServiceQoSConfig( - "metis", - "0x440", - evm.NewEVMArchivalCheckConfig( - // https://explorer.metis.io/address/0xfad31cd4d45Ac7C4B5aC6A0044AA05Ca7C017e62 - "0xfad31cd4d45Ac7C4B5aC6A0044AA05Ca7C017e62", - // Contract start block - 15_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Moonbeam - evm.NewEVMServiceQoSConfig( - "moonbeam", - "0x504", - evm.NewEVMArchivalCheckConfig( - // https://moonscan.io/address/0xf89d7b9c864f589bbf53a82105107622b35eaa40 - "0xf89d7b9c864f589bbf53a82105107622b35eaa40", - // Contract start block - 677_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Oasys - evm.NewEVMServiceQoSConfig( - "oasys", - "0xf8", - evm.NewEVMArchivalCheckConfig( - // https://explorer.oasys.games/address/0xf89d7b9c864f589bbF53a82105107622B35EaA40 - "0xf89d7b9c864f589bbF53a82105107622B35EaA40", - // Contract start block - 424_300, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Optimism - evm.NewEVMServiceQoSConfig( - "op", - "0xa", - evm.NewEVMArchivalCheckConfig( - // https://optimistic.etherscan.io/address/0xacd03d601e5bb1b275bb94076ff46ed9d753435a - "0xacD03D601e5bB1B275Bb94076fF46ED9D753435A", - // Contract start block - 8_121_800, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Optimism Sepolia Testnet - evm.NewEVMServiceQoSConfig( - "op-sepolia-testnet", - "0xAA37DC", - evm.NewEVMArchivalCheckConfig( - // https://sepolia-optimism.etherscan.io/address/0x734d539a7efee15714a2755caa4280e12ef3d7e4 - "0x734d539a7efee15714a2755caa4280e12ef3d7e4", - // Contract start block - 18_241_388, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Polygon - evm.NewEVMServiceQoSConfig( - "poly", - "0x89", - evm.NewEVMArchivalCheckConfig( - // https://polygonscan.com/address/0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270 - "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", - // Contract start block - 5_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Polygon Amoy Testnet - evm.NewEVMServiceQoSConfig( - "poly-amoy-testnet", - "0x13882", evm.NewEVMArchivalCheckConfig( - // https://amoy.polygonscan.com/address/0x54d03ec0c462e9a01f77579c090cde0fc2617817 - "0x54d03ec0c462e9a01f77579c090cde0fc2617817", - // Contract start block - 10_453_569, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Polygon zkEVM - evm.NewEVMServiceQoSConfig( - "poly-zkevm", - "0x44d", - evm.NewEVMArchivalCheckConfig( - // https://zkevm.polygonscan.com/address/0xee1727f5074e747716637e1776b7f7c7133f16b1 - "0xee1727f5074E747716637e1776B7F7C7133f16b1", - // Contract start block - 111, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Scroll - evm.NewEVMServiceQoSConfig( - "scroll", - "0x82750", - evm.NewEVMArchivalCheckConfig( - // https://scrollscan.com/address/0x5300000000000000000000000000000000000004 - "0x5300000000000000000000000000000000000004", - // Contract start block - 5_000_000, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Sonic - evm.NewEVMServiceQoSConfig( - "sonic", - "0x92", - evm.NewEVMArchivalCheckConfig( - // https://sonicscan.org/address/0xfc00face00000000000000000000000000000000 - "0xfc00face00000000000000000000000000000000", - // Contract start block - 10_769_279, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Taiko - evm.NewEVMServiceQoSConfig( - "taiko", - "0x28c58", - evm.NewEVMArchivalCheckConfig( - // https://taikoscan.io/address/0x1670000000000000000000000000000000000001 - "0x1670000000000000000000000000000000000001", - // Contract start block - 170_163, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // Taiko Hekla Testnet - evm.NewEVMServiceQoSConfig( - "taiko-hekla-testnet", - "0x28c61", - evm.NewEVMArchivalCheckConfig( - // https://hekla.taikoscan.io/address/0x1670090000000000000000000000000000010001 - "0x1670090000000000000000000000000000010001", - // Contract start block - 420_139, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - // zkLink - evm.NewEVMServiceQoSConfig( - "zklink-nova", - "0xc5cc4", evm.NewEVMArchivalCheckConfig( - // https://explorer.zklink.io/address/0xa3cb8648d12bD36e713af27D92968B370D7A9546 - "0xa3cb8648d12bD36e713af27D92968B370D7A9546", - // Contract start block - 5_004_627, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // zkSync - evm.NewEVMServiceQoSConfig( - "zksync-era", - "0x144", - evm.NewEVMArchivalCheckConfig( - // https://explorer.zksync.io/address/0x03AC0b1b952C643d66A4Dc1fBc75118109cC074C - "0x03AC0b1b952C643d66A4Dc1fBc75118109cC074C", - // Contract start block - 55_405_668, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // *** EVM Services (testing) *** - - // Anvil - Ethereum development/testing - evm.NewEVMServiceQoSConfig("anvil", "0x7a69", nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // Anvil WebSockets - Ethereum WebSockets development/testing - evm.NewEVMServiceQoSConfig("anvilws", "0x7a69", nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // *** EVM Services (Non-Archival) *** - - // Fraxtal - evm.NewEVMServiceQoSConfig("fraxtal", "0xfc", nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // Kava - evm.NewEVMServiceQoSConfig("kava", "0x8ae", nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // Moonriver - evm.NewEVMServiceQoSConfig("moonriver", "0x505", nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // opBNB - evm.NewEVMServiceQoSConfig("opbnb", "0xcc", nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // Sui - evm.NewEVMServiceQoSConfig("sui", "0x101", nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // TRON - evm.NewEVMServiceQoSConfig("tron", "0x2b6653dc", nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // Sei - evm.NewEVMServiceQoSConfig("sei", "0x531", nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // Hey - // TODO_TECHDEBT(@olshansk): Either remove or format this correctly - evm.NewEVMServiceQoSConfig("hey", defaultEVMChainID, nil, map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // Hyperliquid - evm.NewEVMServiceQoSConfig("hyperliquid", "0x3e7", - evm.NewEVMArchivalCheckConfig( - // https://app.hyperliquid.xyz/explorer/address/0x162cc7c861ebd0c06b3d72319201150482518185 - // https://hypurrscan.io/address/0x162cc7c861ebd0c06b3d72319201150482518185 - "0x162cc7c861ebd0c06b3d72319201150482518185", - // First block (649106292) taken from this post: https://x.com/Crypto_Noddy/status/1943780258370428938: - // Adding 1_000 blocks as a buffer for the archival check baseline. - 649_107_292, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // Unichain - evm.NewEVMServiceQoSConfig("unichain", "0x82", - evm.NewEVMArchivalCheckConfig( - // https://unichain.blockscout.com/address/0x1F98400000000000000000000000000000000004 - "0x1F98400000000000000000000000000000000004", - // Below is not the first block, but one that is sufficiently long ago for archival check baseline. - // https://unichain.blockscout.com/address/0x1F98400000000000000000000000000000000004?tab=txs&page=100000 - 21_495_984, - ), - map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, - }, - ), - - // TODO_TECHDEBT: Add support for Radix QoS - // Radix - // radix.NewRadixServiceQoSConfig("radix", "", nil), - - // TODO_TECHDEBT: Add support for Near QoS - // Near - // near.NewNearServiceQoSConfig("near", "", nil), - - // *** Cosmos SDK Services *** - - // Akash - https://github.com/cosmos/chain-registry/blob/master/akash/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("akash", "akashnet-2", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Arkeo - https://github.com/cosmos/chain-registry/blob/master/arkeo/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("arkeo", "arkeo-main-v1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // AtomOne - https://github.com/cosmos/chain-registry/blob/master/atomone/chain.json#L5 - cosmos.NewCosmosSDKServiceQoSConfig("atomone", "atomone-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Babylon - https://github.com/cosmos/chain-registry/blob/master/babylon/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("babylon", "bbn-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Celestia - https://github.com/cosmos/chain-registry/blob/master/celestia/chain.json#L5 - cosmos.NewCosmosSDKServiceQoSConfig("celestia", "celestia", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Cheqd - https://github.com/cosmos/chain-registry/blob/master/cheqd/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("cheqd", "cheqd-mainnet-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Chihuahua - https://github.com/cosmos/chain-registry/blob/master/chihuahua/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("chihuahua", "chihuahua-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Cosmos Hub - https://github.com/cosmos/chain-registry/blob/master/cosmoshub/chain.json#L5 - cosmos.NewCosmosSDKServiceQoSConfig("cosmoshub", "cosmoshub-4", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Dungeon Chain - https://github.com/cosmos/chain-registry/blob/master/dungeon1/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("dungeon-chain", "dungeon-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Elys Network - https://github.com/cosmos/chain-registry/blob/master/elys/chain.json#L8 - cosmos.NewCosmosSDKServiceQoSConfig("elys-network", "elys-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Fetch - https://github.com/cosmos/chain-registry/blob/master/fetchhub/chain.json#L8 - cosmos.NewCosmosSDKServiceQoSConfig("fetch", "fetchhub-4", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Jackal - https://github.com/cosmos/chain-registry/blob/master/jackal/chain.json#L5 - cosmos.NewCosmosSDKServiceQoSConfig("jackal", "jackal-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Juno - https://github.com/cosmos/chain-registry/blob/master/juno/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("juno", "juno-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // KYVE - https://github.com/cosmos/chain-registry/blob/master/kyve/chain.json#L5 - cosmos.NewCosmosSDKServiceQoSConfig("kyve", "kyve-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Namada TODO_TECHDEBT(@commoddity): Namada is not a conventional Cosmos SDK chain and likely requires a custom implementation. - // Reference: https://github.com/buildwithgrove/path/issues/376#issuecomment-3127611273 - // cosmos.NewCosmosSDKServiceQoSConfig("namada", "","", map[sharedtypes.RPCType]struct{}{ - // sharedtypes.RPCType_REST: {}, // CosmosSDK - // sharedtypes.RPCType_COMET_BFT: {}, - // }), - - // Neutron - https://github.com/cosmos/chain-registry/blob/master/neutron/chain.json#L8 - cosmos.NewCosmosSDKServiceQoSConfig("neutron", "neutron-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Nillion - https://github.com/cosmos/chain-registry/blob/master/nillion/chain.json#L8 - cosmos.NewCosmosSDKServiceQoSConfig("nillion", "nillion-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Osmosis - https://github.com/cosmos/chain-registry/blob/master/osmosis/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("osmosis", "osmosis-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Passage - https://github.com/cosmos/chain-registry/blob/master/passage/chain.json#L5 - cosmos.NewCosmosSDKServiceQoSConfig("passage", "passage-2", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Persistence - https://github.com/cosmos/chain-registry/blob/master/persistence/chain.json#L5 - cosmos.NewCosmosSDKServiceQoSConfig("persistence", "core-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Provenance - https://github.com/cosmos/chain-registry/blob/master/provenance/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("provenance", "pio-mainnet-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Pocket Mainnet - https://github.com/cosmos/chain-registry/blob/master/pocket/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("pocket", "pocket", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Pocket Alpha Testnet - (Not in the chain registry - present here for onchain load testing) - cosmos.NewCosmosSDKServiceQoSConfig("pocket-alpha", "pocket-alpha", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Pocket Beta Testnet - https://github.com/cosmos/chain-registry/blob/master/testnets/pockettestnet/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("pocket-beta", "pocket-beta", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Pocket Beta Testnet 1 - (Not in the chain registry - present here for onchain load testing) - cosmos.NewCosmosSDKServiceQoSConfig("pocket-beta1", "pocket-beta", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Pocket Beta Testnet 2 - (Not in the chain registry - present here for onchain load testing) - cosmos.NewCosmosSDKServiceQoSConfig("pocket-beta2", "pocket-beta", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Pocket Beta Testnet 3 - (Not in the chain registry - present here for onchain load testing) - cosmos.NewCosmosSDKServiceQoSConfig("pocket-beta3", "pocket-beta", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Pocket Beta Testnet 4 - (Not in the chain registry - present here for onchain load testing) - cosmos.NewCosmosSDKServiceQoSConfig("pocket-beta4", "pocket-beta", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Quicksilver - https://github.com/cosmos/chain-registry/blob/master/quicksilver/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("quicksilver", "quicksilver-2", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Router - https://github.com/cosmos/chain-registry/blob/master/routerchain/chain.json#L5 - cosmos.NewCosmosSDKServiceQoSConfig("router", "router_9600-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - sharedtypes.RPCType_JSON_RPC: {}, - }), - - // Seda - https://github.com/cosmos/chain-registry/blob/master/seda/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("seda", "seda-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Shentu - https://github.com/cosmos/chain-registry/blob/master/shentu/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("shentu", "shentu-2.2", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Bitway - https://github.com/cosmos/chain-registry/blob/master/bitway/chain.json - // FORMERLY KNOWN AS: Side Protocol - https://github.com/cosmos/chain-registry/blob/master/sidechain/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("bitway", "bitway-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Stargaze - https://github.com/cosmos/chain-registry/blob/master/stargaze/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("stargaze", "stargaze-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // Stride - https://github.com/cosmos/chain-registry/blob/master/stride/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("stride", "stride-1", "", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - }), - - // XRPLEVM - https://github.com/cosmos/chain-registry/blob/master/xrplevm/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("xrplevm", "xrplevm_1440000-1", "0x15f900", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, // XRPLEVM supports the EVM API over JSON-RPC. - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - sharedtypes.RPCType_WEBSOCKET: {}, // XRPLEVM supports the EVM API over JSON-RPC WebSockets. - }), - - // XRPLEVM Testnet - https://github.com/cosmos/chain-registry/blob/master/testnets/xrplevmtestnet/chain.json#L9 - cosmos.NewCosmosSDKServiceQoSConfig("xrplevm-testnet", "xrplevm_1449000-1", "0x161c28", map[sharedtypes.RPCType]struct{}{ - sharedtypes.RPCType_JSON_RPC: {}, // XRPLEVM supports the EVM API over JSON-RPC. - sharedtypes.RPCType_REST: {}, // CosmosSDK - sharedtypes.RPCType_COMET_BFT: {}, - sharedtypes.RPCType_WEBSOCKET: {}, // XRPLEVM supports the EVM API over JSON-RPC WebSockets. - }), - - // *** Solana Services *** - - // Solana - solana.NewSolanaServiceQoSConfig("solana", "solana"), - - // *** LLM Services *** - -} - -// 3. deepseek - -// TODO(@olshansk): Make sure all of these are supported -// sonieum -// atomone -// akash -// fetch -// persistence -// router -// seda -// shentu -// arkeo -// babylon -// celestia -// cheqd -// chihuahua -// cosmoshub -// elys-network -// jackal -// juno -// kyve -// namada -// neutron -// nillion -// passage -// provenance -// quicksilver -// side-protocol -// stargaze -// stride -// grok -// qwen -// gpt-oss -// bittensor -// bittensor-testnet -// planq -// stellar-soroban -// stellar-soroban-testnet -// stellar-horizon -// stellar-horizon-testnet diff --git a/qos/cosmos/config.go b/qos/cosmos/config.go index f79b71714..d93d82bb5 100644 --- a/qos/cosmos/config.go +++ b/qos/cosmos/config.go @@ -75,3 +75,14 @@ func (c *Config) GetSupportedAPIs() map[sharedtypes.RPCType]struct{} { return result } + +// LogConfig logs the Cosmos service configuration +func (c *Config) LogConfig(logger polylog.Logger) { + logger.Info(). + Str("type", "Cosmos"). + Str("cosmos_chain_id", c.CosmosChainID). + Str("evm_chain_id", c.EVMChainID). + Uint64("sync_allowance", c.SyncAllowance). + Int("supported_apis_count", len(c.SupportedAPIs)). + Msg("Cosmos service configuration") +} diff --git a/qos/cosmos/qos.go b/qos/cosmos/qos.go index 677772ca4..174246a63 100644 --- a/qos/cosmos/qos.go +++ b/qos/cosmos/qos.go @@ -32,7 +32,7 @@ type QoS struct { } // NewQoSInstance builds and returns an instance of the CosmosSDK QoS service. -func NewQoSInstance(logger polylog.Logger, serviceID protocol.ServiceID, config Config) *QoS { +func NewQoSInstance(logger polylog.Logger, serviceID protocol.ServiceID, config *Config) *QoS { cosmosChainID := config.CosmosChainID // Some CosmosSDK services may have an EVM chain ID. For example, XRPLEVM. diff --git a/qos/cosmos/service_state.go b/qos/cosmos/service_state.go index 44fa8b3ba..ff2f5da1c 100644 --- a/qos/cosmos/service_state.go +++ b/qos/cosmos/service_state.go @@ -33,7 +33,7 @@ type serviceState struct { serviceStateLock sync.RWMutex // serviceQoSConfig maintains the QoS configs for this service - serviceQoSConfig Config + serviceQoSConfig *Config // endpointStore maintains the set of available endpoints and their quality data endpointStore *endpointStore diff --git a/qos/evm/config.go b/qos/evm/config.go index b5f34d39f..fb0f13fda 100644 --- a/qos/evm/config.go +++ b/qos/evm/config.go @@ -75,3 +75,27 @@ func (a *ArchivalCheckConfig) Validate(logger polylog.Logger, serviceID protocol return nil } + +// LogConfig logs the EVM service configuration +func (c *Config) LogConfig(logger polylog.Logger) { + logger.Info(). + Str("type", "EVM"). + Str("chain_id", c.ChainID). + Uint64("sync_allowance", c.SyncAllowance). + Int("supported_apis_count", len(c.SupportedAPIs)). + Bool("has_archival_check", c.ArchivalCheck != nil). + Msg("EVM service configuration") + + if c.ArchivalCheck != nil { + c.ArchivalCheck.LogConfig(logger) + } +} + +// LogConfig logs the archival check configuration +func (a *ArchivalCheckConfig) LogConfig(logger polylog.Logger) { + logger.Debug(). + Str("contract_address", a.ContractAddress). + Uint64("contract_start_block", a.ContractStartBlock). + Uint64("threshold", a.Threshold). + Msg("EVM archival check configuration") +} diff --git a/qos/evm/qos.go b/qos/evm/qos.go index 4046a7129..83d03b525 100644 --- a/qos/evm/qos.go +++ b/qos/evm/qos.go @@ -32,7 +32,7 @@ type QoS struct { } // NewQoSInstance builds and returns an instance of the EVM QoS service. -func NewQoSInstance(logger polylog.Logger, serviceID protocol.ServiceID, serviceConfig Config) *QoS { +func NewQoSInstance(logger polylog.Logger, serviceID protocol.ServiceID, serviceConfig *Config) *QoS { logger = logger.With( "qos_instance", "evm", "service_id", serviceID, diff --git a/qos/evm/service_state.go b/qos/evm/service_state.go index 540a12d27..f78106d0f 100644 --- a/qos/evm/service_state.go +++ b/qos/evm/service_state.go @@ -38,7 +38,7 @@ type serviceState struct { serviceStateLock sync.RWMutex // serviceQoSConfig maintains the QoS configs for this service - serviceQoSConfig Config + serviceQoSConfig *Config // endpointStore maintains the set of available endpoints and their quality data endpointStore *endpointStore diff --git a/qos/solana/config.go b/qos/solana/config.go index d9fe2f3a5..a5b0eaeda 100644 --- a/qos/solana/config.go +++ b/qos/solana/config.go @@ -1,7 +1,32 @@ package solana +import ( + "fmt" + + "github.com/pokt-network/poktroll/pkg/polylog" + + "github.com/buildwithgrove/path/protocol" +) + // Config represents Solana-specific service configuration type Config struct { // Chain ID (e.g., "solana", "mainnet-beta") ChainID string `yaml:"chain_id"` } + +// Validate validates the Config struct +func (c *Config) Validate(logger polylog.Logger, serviceID protocol.ServiceID) error { + if c.ChainID == "" { + return fmt.Errorf("service %s: chain_id is required", serviceID) + } + + return nil +} + +// LogConfig logs the Solana service configuration +func (c *Config) LogConfig(logger polylog.Logger) { + logger.Info(). + Str("type", "Solana"). + Str("chain_id", c.ChainID). + Msg("Solana service configuration") +} diff --git a/qos/solana/qos.go b/qos/solana/qos.go index bfcb4bd27..59d860b72 100644 --- a/qos/solana/qos.go +++ b/qos/solana/qos.go @@ -7,7 +7,7 @@ import ( ) // NewQoSInstance builds and returns an instance of the Solana QoS service. -func NewQoSInstance(logger polylog.Logger, serviceID protocol.ServiceID, serviceConfig Config) *QoS { +func NewQoSInstance(logger polylog.Logger, serviceID protocol.ServiceID, serviceConfig *Config) *QoS { chainID := serviceConfig.ChainID logger = logger.With( From b446762575813dd03fed544c2358b4343c056862 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Tue, 30 Sep 2025 18:18:49 -0400 Subject: [PATCH 7/9] Fix supported API types field in EVM and Cosmos --- qos/cosmos/config.go | 19 ++++++++++++++----- qos/evm/config.go | 11 ++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/qos/cosmos/config.go b/qos/cosmos/config.go index d93d82bb5..c69db0794 100644 --- a/qos/cosmos/config.go +++ b/qos/cosmos/config.go @@ -21,7 +21,7 @@ type Config struct { SyncAllowance uint64 `yaml:"sync_allowance"` // Supported RPC types for this service - SupportedAPIs map[string]struct{} `yaml:"supported_apis"` + SupportedAPIs []string `yaml:"supported_apis"` } // Validate validates the Cosmos service configuration @@ -45,13 +45,22 @@ func (c *Config) Validate(logger polylog.Logger, serviceID protocol.ServiceID) e } if c.SupportedAPIs == nil || len(c.SupportedAPIs) == 0 { - err := fmt.Errorf("service %q: supported_apis map cannot be empty", serviceID) + err := fmt.Errorf("service %q: supported_apis cannot be empty", serviceID) logger.Error().Err(err).Msg("Validation failed") return err } - // Validate each API type against the RPCType enum - for apiType := range c.SupportedAPIs { + // Check for duplicate API types + seen := make(map[string]bool) + for _, apiType := range c.SupportedAPIs { + if seen[apiType] { + err := fmt.Errorf("service %q: duplicate RPC type %q in supported_apis", serviceID, apiType) + logger.Error().Err(err).Msg("Validation failed") + return err + } + seen[apiType] = true + + // Validate each API type against the RPCType enum rpcTypeValue, exists := sharedtypes.RPCType_value[apiType] if !exists || rpcTypeValue <= 0 { err := fmt.Errorf("service %q: invalid RPC type %q in supported_apis", serviceID, apiType) @@ -67,7 +76,7 @@ func (c *Config) Validate(logger polylog.Logger, serviceID protocol.ServiceID) e func (c *Config) GetSupportedAPIs() map[sharedtypes.RPCType]struct{} { result := make(map[sharedtypes.RPCType]struct{}, len(c.SupportedAPIs)) - for apiType := range c.SupportedAPIs { + for _, apiType := range c.SupportedAPIs { if rpcTypeValue, exists := sharedtypes.RPCType_value[apiType]; exists { result[sharedtypes.RPCType(rpcTypeValue)] = struct{}{} } diff --git a/qos/evm/config.go b/qos/evm/config.go index fb0f13fda..0de18635d 100644 --- a/qos/evm/config.go +++ b/qos/evm/config.go @@ -20,7 +20,7 @@ type Config struct { ArchivalCheck *ArchivalCheckConfig `yaml:"archival_check,omitempty"` // Supported RPC types for this service - SupportedAPIs map[string]struct{} `yaml:"supported_apis"` + SupportedAPIs []string `yaml:"supported_apis"` } // Validate validates the Config struct @@ -37,6 +37,15 @@ func (c *Config) Validate(logger polylog.Logger, serviceID protocol.ServiceID) e return fmt.Errorf("service %s: supported_apis must contain at least one API", serviceID) } + // Check for duplicate API types + seen := make(map[string]bool) + for _, apiType := range c.SupportedAPIs { + if seen[apiType] { + return fmt.Errorf("service %s: duplicate API type %q in supported_apis", serviceID, apiType) + } + seen[apiType] = true + } + // Validate ArchivalCheck if present if c.ArchivalCheck != nil { if err := c.ArchivalCheck.Validate(logger, serviceID); err != nil { From bf2706dbbe06883e8eeed3fec3e6428c728026cc Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Thu, 2 Oct 2025 14:06:55 -0400 Subject: [PATCH 8/9] Update config.schema.yaml with service QoS configs fields --- config/config.schema.yaml | 105 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/config/config.schema.yaml b/config/config.schema.yaml index ff65a397e..6ac8583af 100644 --- a/config/config.schema.yaml +++ b/config/config.schema.yaml @@ -220,3 +220,108 @@ properties: description: "Timeout in milliseconds for HTTP POST operations. If zero or negative, a default timeout of 10000ms (10s) is used." type: integer default: 10000 + + # Services QoS Configuration (optional) + services_qos_configs: + description: "Configuration for Quality of Service (QoS) checks for each supported service. Services not listed here will use NoOp QoS with random endpoint selection and no monitoring." + type: object + additionalProperties: false + properties: + services: + description: "Map of service IDs to their QoS configurations." + type: object + patternProperties: + "^[a-zA-Z0-9_-]+$": + description: "Service QoS configuration. Each service must specify exactly one of: evm, cosmos, or solana." + type: object + additionalProperties: false + properties: + evm: + description: "EVM-specific service configuration." + type: object + additionalProperties: false + required: + - chain_id + - sync_allowance + - supported_apis + properties: + chain_id: + description: "Chain ID in hex format (e.g., '0x1' for Ethereum mainnet)." + type: string + pattern: "^0x[0-9a-fA-F]+$" + sync_allowance: + description: "Maximum number of blocks an endpoint can be behind before being considered out of sync." + type: integer + minimum: 1 + default: 5 + supported_apis: + description: "List of supported RPC types for this service." + type: array + minItems: 1 + uniqueItems: true + items: + type: string + enum: ["JSON_RPC", "WEBSOCKET", "REST", "COMET_BFT"] + archival_check: + description: "Optional archival check configuration for verifying node has historical data." + type: object + additionalProperties: false + required: + - contract_address + - contract_start_block + - threshold + properties: + contract_address: + description: "Contract address to check for archival balance (in hex format)." + type: string + pattern: "^(0x)?[0-9a-fA-F]{40}$|^one1[0-9a-z]{38}$" + contract_start_block: + description: "The block number when the contract first had a balance." + type: integer + minimum: 0 + threshold: + description: "Minimum balance threshold for archival check." + type: integer + minimum: 1 + default: 128 + cosmos: + description: "Cosmos SDK-specific service configuration." + type: object + additionalProperties: false + required: + - chain_id + - evm_chain_id + - sync_allowance + - supported_apis + properties: + chain_id: + description: "Cosmos SDK chain ID (e.g., 'cosmoshub-4')." + type: string + minLength: 1 + evm_chain_id: + description: "EVM chain ID in hex format for Cosmos chains with native EVM support (e.g., XRPLEVM). Use empty string if not applicable." + type: string + sync_allowance: + description: "Maximum number of blocks an endpoint can be behind before being considered out of sync." + type: integer + minimum: 1 + default: 5 + supported_apis: + description: "List of supported RPC types for this service." + type: array + minItems: 1 + uniqueItems: true + items: + type: string + enum: ["JSON_RPC", "REST", "COMET_BFT", "WEBSOCKET"] + solana: + description: "Solana-specific service configuration." + type: object + additionalProperties: false + required: + - chain_id + properties: + chain_id: + description: "Solana chain ID (e.g., 'solana', 'mainnet-beta')." + type: string + minLength: 1 From bf1f5e5b2b7536167539ed0137de2ba824d7b015 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Thu, 16 Oct 2025 14:17:08 -0400 Subject: [PATCH 9/9] Add QoS mappings section to example config --- config/examples/config.shannon_example.yaml | 1557 +++++++++++++++++++ 1 file changed, 1557 insertions(+) diff --git a/config/examples/config.shannon_example.yaml b/config/examples/config.shannon_example.yaml index ad0389fae..914a68eff 100644 --- a/config/examples/config.shannon_example.yaml +++ b/config/examples/config.shannon_example.yaml @@ -79,3 +79,1560 @@ logger_config: # Valid values are: debug, info, warn, error # Defaults to info if not specified level: "error" + +# QoS mappings and options for all services. +services_qos_configs: + + # *** EVM Services (Archival) *** + + arb-one: + evm: + chain_id: "0xa4b1" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xb38e8c17e38363af6ebdcb3dae12e0243582891d" + contract_start_block: 3057700 + threshold: 128 + + arb-sepolia-testnet: + evm: + chain_id: "0x66EEE" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x22b65d0b9b59af4d3ed59f18b9ad53f5f4908b54" + contract_start_block: 132000000 + threshold: 128 + + avax: + evm: + chain_id: "0xa86a" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x9f8c163cBA728e99993ABe7495F06c0A3c8Ac8b9" + contract_start_block: 5000000 + threshold: 128 + + avax-dfk: + evm: + chain_id: "0xd2af" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xCCb93dABD71c8Dad03Fc4CE5559dC3D89F67a260" + contract_start_block: 45000000 + threshold: 128 + + base: + evm: + chain_id: "0x2105" + sync_allowance: 5 + supported_apis: + - JSON_RPC + - WEBSOCKET + archival_check: + contract_address: "0x3304E22DDaa22bCdC5fCa2269b418046aE7b566A" + contract_start_block: 4504400 + threshold: 128 + + base-sepolia-testnet: + evm: + chain_id: "0x14a34" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xbab76e4365a2dff89ddb2d3fc9994103b48886c0" + contract_start_block: 13000000 + threshold: 128 + + bera: + evm: + chain_id: "0x138de" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x6969696969696969696969696969696969696969" + contract_start_block: 2000000 + threshold: 128 + + blast: + evm: + chain_id: "0x13e31" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x4300000000000000000000000000000000000004" + contract_start_block: 1000000 + threshold: 128 + + bsc: + evm: + chain_id: "0x38" + sync_allowance: 5 + supported_apis: + - JSON_RPC + - WEBSOCKET + archival_check: + contract_address: "0xfb50526f49894b78541b776f5aaefe43e3bd8590" + contract_start_block: 33049200 + threshold: 128 + + boba: + evm: + chain_id: "0x120" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x3A92cA39476fF84Dc579C868D4D7dE125513B034" + contract_start_block: 3060300 + threshold: 128 + + celo: + evm: + chain_id: "0xa4ec" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xf89d7b9c864f589bbF53a82105107622B35EaA40" + contract_start_block: 20000000 + threshold: 128 + + eth: + evm: + chain_id: "0x1" + sync_allowance: 5 + supported_apis: + - JSON_RPC + - WEBSOCKET + archival_check: + contract_address: "0x28C6c06298d514Db089934071355E5743bf21d60" + contract_start_block: 12300000 + threshold: 128 + + eth-holesky-testnet: + evm: + chain_id: "0x4268" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xc6392ad8a14794ea57d237d12017e7295bea2363" + contract_start_block: 1900384 + threshold: 128 + + eth-sepolia-testnet: + evm: + chain_id: "0xaa36a7" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xc0f3833b7e7216eecd9f6bc2c7927a7aa36ab58b" + contract_start_block: 6412177 + threshold: 128 + + fantom: + evm: + chain_id: "0xfa" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xaabf86ab3646a7064aa2f61e5959e39129ca46b6" + contract_start_block: 110633000 + threshold: 128 + + fuse: + evm: + chain_id: "0x7a" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79" + contract_start_block: 15000000 + threshold: 128 + + giwa: + evm: + chain_id: "0x1" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + giwa-sepolia-testnet: + evm: + chain_id: "0x164ce" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xA2a51Cca837B8ebc00dA2810e72F386Ee0dD08a0" + contract_start_block: 3456000 + threshold: 128 + + gnosis: + evm: + chain_id: "0x64" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + contract_start_block: 20000000 + threshold: 128 + + harmony: + evm: + chain_id: "0x63564c40" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "one19senwle0ezp3he6ed9xkc7zeg5rs94r0ecpp0a" + contract_start_block: 60000000 + threshold: 128 + + ink: + evm: + chain_id: "0xdef1" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x4200000000000000000000000000000000000006" + contract_start_block: 4500000 + threshold: 128 + + iotex: + evm: + chain_id: "0x1251" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x0a7f9ea31ca689f346e1661cf73a47c69d4bd883" + contract_start_block: 6440916 + threshold: 128 + + kaia: + evm: + chain_id: "0x2019" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x0051ef9259c7ec0644a80e866ab748a2f30841b3" + contract_start_block: 170000000 + threshold: 128 + + linea: + evm: + chain_id: "0xe708" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xf89d7b9c864f589bbf53a82105107622b35eaa40" + contract_start_block: 10000000 + threshold: 128 + + mantle: + evm: + chain_id: "0x1388" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x588846213A30fd36244e0ae0eBB2374516dA836C" + contract_start_block: 60000000 + threshold: 128 + + metis: + evm: + chain_id: "0x440" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xfad31cd4d45Ac7C4B5aC6A0044AA05Ca7C017e62" + contract_start_block: 15000000 + threshold: 128 + + moonbeam: + evm: + chain_id: "0x504" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xf89d7b9c864f589bbf53a82105107622b35eaa40" + contract_start_block: 677000 + threshold: 128 + + oasys: + evm: + chain_id: "0xf8" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xf89d7b9c864f589bbF53a82105107622B35EaA40" + contract_start_block: 424300 + threshold: 128 + + op: + evm: + chain_id: "0xa" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xacD03D601e5bB1B275Bb94076fF46ED9D753435A" + contract_start_block: 8121800 + threshold: 128 + + op-sepolia-testnet: + evm: + chain_id: "0xAA37DC" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x734d539a7efee15714a2755caa4280e12ef3d7e4" + contract_start_block: 18241388 + threshold: 128 + + poly: + evm: + chain_id: "0x89" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270" + contract_start_block: 5000000 + threshold: 128 + + poly-amoy-testnet: + evm: + chain_id: "0x13882" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x54d03ec0c462e9a01f77579c090cde0fc2617817" + contract_start_block: 10453569 + threshold: 128 + + poly-zkevm: + evm: + chain_id: "0x44d" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xee1727f5074E747716637e1776B7F7C7133f16b1" + contract_start_block: 111 + threshold: 128 + + scroll: + evm: + chain_id: "0x82750" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x5300000000000000000000000000000000000004" + contract_start_block: 5000000 + threshold: 128 + + sonic: + evm: + chain_id: "0x92" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xfc00face00000000000000000000000000000000" + contract_start_block: 10769279 + threshold: 128 + + taiko: + evm: + chain_id: "0x28c58" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x1670000000000000000000000000000000000001" + contract_start_block: 170163 + threshold: 128 + + taiko-hekla-testnet: + evm: + chain_id: "0x28c61" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x1670090000000000000000000000000000010001" + contract_start_block: 420139 + threshold: 128 + + zklink-nova: + evm: + chain_id: "0xc5cc4" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0xa3cb8648d12bD36e713af27D92968B370D7A9546" + contract_start_block: 5004627 + threshold: 128 + + zksync-era: + evm: + chain_id: "0x144" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x03AC0b1b952C643d66A4Dc1fBc75118109cC074C" + contract_start_block: 55405668 + threshold: 128 + + # *** EVM Services (Testing) *** + + anvil: + evm: + chain_id: "0x7a69" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + anvilws: + evm: + chain_id: "0x7a69" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + # *** EVM Services (Non-Archival) *** + + fraxtal: + evm: + chain_id: "0xfc" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + kava: + evm: + chain_id: "0x8ae" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + moonriver: + evm: + chain_id: "0x505" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + opbnb: + evm: + chain_id: "0xcc" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + sui: + evm: + chain_id: "0x101" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + tron: + evm: + chain_id: "0x2b6653dc" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + sei: + evm: + chain_id: "0x531" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + hey: + evm: + chain_id: "0x1" + sync_allowance: 5 + supported_apis: + - JSON_RPC + + hyperliquid: + evm: + chain_id: "0x3e7" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x162cc7c861ebd0c06b3d72319201150482518185" + contract_start_block: 649107292 + threshold: 128 + + unichain: + evm: + chain_id: "0x82" + sync_allowance: 5 + supported_apis: + - JSON_RPC + archival_check: + contract_address: "0x1F98400000000000000000000000000000000004" + contract_start_block: 21495984 + threshold: 128 + contract_address: "0xb38e8c17e38363af6ebdcb3dae12e0243582891d" + contract_start_block: 3057700 + threshold: 128 + + arb-sepolia-testnet: + evm: + chain_id: "0x66EEE" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x22b65d0b9b59af4d3ed59f18b9ad53f5f4908b54" + contract_start_block: 132000000 + threshold: 128 + + avax: + evm: + chain_id: "0xa86a" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x9f8c163cBA728e99993ABe7495F06c0A3c8Ac8b9" + contract_start_block: 5000000 + threshold: 128 + + avax-dfk: + evm: + chain_id: "0xd2af" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xCCb93dABD71c8Dad03Fc4CE5559dC3D89F67a260" + contract_start_block: 45000000 + threshold: 128 + + base: + evm: + chain_id: "0x2105" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + WEBSOCKET: {} + archival_check: + contract_address: "0x3304E22DDaa22bCdC5fCa2269b418046aE7b566A" + contract_start_block: 4504400 + threshold: 128 + + base-sepolia-testnet: + evm: + chain_id: "0x14a34" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xbab76e4365a2dff89ddb2d3fc9994103b48886c0" + contract_start_block: 13000000 + threshold: 128 + + bera: + evm: + chain_id: "0x138de" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x6969696969696969696969696969696969696969" + contract_start_block: 2000000 + threshold: 128 + + blast: + evm: + chain_id: "0x13e31" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x4300000000000000000000000000000000000004" + contract_start_block: 1000000 + threshold: 128 + + bsc: + evm: + chain_id: "0x38" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + WEBSOCKET: {} + archival_check: + contract_address: "0xfb50526f49894b78541b776f5aaefe43e3bd8590" + contract_start_block: 33049200 + threshold: 128 + + boba: + evm: + chain_id: "0x120" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x3A92cA39476fF84Dc579C868D4D7dE125513B034" + contract_start_block: 3060300 + threshold: 128 + + celo: + evm: + chain_id: "0xa4ec" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xf89d7b9c864f589bbF53a82105107622B35EaA40" + contract_start_block: 20000000 + threshold: 128 + + eth: + evm: + chain_id: "0x1" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + WEBSOCKET: {} + archival_check: + contract_address: "0x28C6c06298d514Db089934071355E5743bf21d60" + contract_start_block: 12300000 + threshold: 128 + + eth-holesky-testnet: + evm: + chain_id: "0x4268" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xc6392ad8a14794ea57d237d12017e7295bea2363" + contract_start_block: 1900384 + threshold: 128 + + eth-sepolia-testnet: + evm: + chain_id: "0xaa36a7" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xc0f3833b7e7216eecd9f6bc2c7927a7aa36ab58b" + contract_start_block: 6412177 + threshold: 128 + + fantom: + evm: + chain_id: "0xfa" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xaabf86ab3646a7064aa2f61e5959e39129ca46b6" + contract_start_block: 110633000 + threshold: 128 + + fuse: + evm: + chain_id: "0x7a" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79" + contract_start_block: 15000000 + threshold: 128 + + giwa: + evm: + chain_id: "0x1" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + giwa-sepolia-testnet: + evm: + chain_id: "0x164ce" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xA2a51Cca837B8ebc00dA2810e72F386Ee0dD08a0" + contract_start_block: 3456000 + threshold: 128 + + gnosis: + evm: + chain_id: "0x64" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d" + contract_start_block: 20000000 + threshold: 128 + + harmony: + evm: + chain_id: "0x63564c40" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "one19senwle0ezp3he6ed9xkc7zeg5rs94r0ecpp0a" + contract_start_block: 60000000 + threshold: 128 + + ink: + evm: + chain_id: "0xdef1" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x4200000000000000000000000000000000000006" + contract_start_block: 4500000 + threshold: 128 + + iotex: + evm: + chain_id: "0x1251" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x0a7f9ea31ca689f346e1661cf73a47c69d4bd883" + contract_start_block: 6440916 + threshold: 128 + + kaia: + evm: + chain_id: "0x2019" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x0051ef9259c7ec0644a80e866ab748a2f30841b3" + contract_start_block: 170000000 + threshold: 128 + + linea: + evm: + chain_id: "0xe708" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xf89d7b9c864f589bbf53a82105107622b35eaa40" + contract_start_block: 10000000 + threshold: 128 + + mantle: + evm: + chain_id: "0x1388" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x588846213A30fd36244e0ae0eBB2374516dA836C" + contract_start_block: 60000000 + threshold: 128 + + metis: + evm: + chain_id: "0x440" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xfad31cd4d45Ac7C4B5aC6A0044AA05Ca7C017e62" + contract_start_block: 15000000 + threshold: 128 + + moonbeam: + evm: + chain_id: "0x504" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xf89d7b9c864f589bbf53a82105107622b35eaa40" + contract_start_block: 677000 + threshold: 128 + + oasys: + evm: + chain_id: "0xf8" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xf89d7b9c864f589bbF53a82105107622B35EaA40" + contract_start_block: 424300 + threshold: 128 + + op: + evm: + chain_id: "0xa" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xacD03D601e5bB1B275Bb94076fF46ED9D753435A" + contract_start_block: 8121800 + threshold: 128 + + op-sepolia-testnet: + evm: + chain_id: "0xAA37DC" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x734d539a7efee15714a2755caa4280e12ef3d7e4" + contract_start_block: 18241388 + threshold: 128 + + poly: + evm: + chain_id: "0x89" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270" + contract_start_block: 5000000 + threshold: 128 + + poly-amoy-testnet: + evm: + chain_id: "0x13882" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x54d03ec0c462e9a01f77579c090cde0fc2617817" + contract_start_block: 10453569 + threshold: 128 + + poly-zkevm: + evm: + chain_id: "0x44d" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xee1727f5074E747716637e1776B7F7C7133f16b1" + contract_start_block: 111 + threshold: 128 + + scroll: + evm: + chain_id: "0x82750" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x5300000000000000000000000000000000000004" + contract_start_block: 5000000 + threshold: 128 + + sonic: + evm: + chain_id: "0x92" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xfc00face00000000000000000000000000000000" + contract_start_block: 10769279 + threshold: 128 + + taiko: + evm: + chain_id: "0x28c58" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x1670000000000000000000000000000000000001" + contract_start_block: 170163 + threshold: 128 + + taiko-hekla-testnet: + evm: + chain_id: "0x28c61" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x1670090000000000000000000000000000010001" + contract_start_block: 420139 + threshold: 128 + + zklink-nova: + evm: + chain_id: "0xc5cc4" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xa3cb8648d12bD36e713af27D92968B370D7A9546" + contract_start_block: 5004627 + threshold: 128 + + zksync-era: + evm: + chain_id: "0x144" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x03AC0b1b952C643d66A4Dc1fBc75118109cC074C" + contract_start_block: 55405668 + threshold: 128 + + # *** EVM Services (Testing) *** + + anvil: + evm: + chain_id: "0x7a69" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + anvilws: + evm: + chain_id: "0x7a69" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + # *** EVM Services (Non-Archival) *** + + fraxtal: + evm: + chain_id: "0xfc" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + kava: + evm: + chain_id: "0x8ae" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + moonriver: + evm: + chain_id: "0x505" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + opbnb: + evm: + chain_id: "0xcc" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + sui: + evm: + chain_id: "0x101" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + tron: + evm: + chain_id: "0x2b6653dc" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + sei: + evm: + chain_id: "0x531" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + hey: + evm: + chain_id: "0x1" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + + hyperliquid: + evm: + chain_id: "0x3e7" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x162cc7c861ebd0c06b3d72319201150482518185" + contract_start_block: 649107292 + threshold: 128 + + unichain: + evm: + chain_id: "0x82" + sync_allowance: 5 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x1F98400000000000000000000000000000000004" + contract_start_block: 21495984 + threshold: 128 "0x54d03ec0c462e9a01f77579c090cde0fc2617817" + contract_start_block: 10453569 + threshold: 1000 + + poly-zkevm: + evm: + chain_id: "0x44d" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xee1727f5074E747716637e1776B7F7C7133f16b1" + contract_start_block: 111 + threshold: 1000 + + scroll: + evm: + chain_id: "0x82750" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x5300000000000000000000000000000000000004" + contract_start_block: 5000000 + threshold: 1000 + + sonic: + evm: + chain_id: "0x92" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xfc00face00000000000000000000000000000000" + contract_start_block: 10769279 + threshold: 1000 + + taiko: + evm: + chain_id: "0x28c58" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x1670000000000000000000000000000000000001" + contract_start_block: 170163 + threshold: 1000 + + taiko-hekla-testnet: + evm: + chain_id: "0x28c61" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x1670090000000000000000000000000000010001" + contract_start_block: 420139 + threshold: 1000 + + zklink-nova: + evm: + chain_id: "0xc5cc4" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0xa3cb8648d12bD36e713af27D92968B370D7A9546" + contract_start_block: 5004627 + threshold: 1000 + + zksync-era: + evm: + chain_id: "0x144" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x03AC0b1b952C643d66A4Dc1fBc75118109cC074C" + contract_start_block: 55405668 + threshold: 1000 + + # *** EVM Services (Testing) *** + + anvil: + evm: + chain_id: "0x7a69" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + + anvilws: + evm: + chain_id: "0x7a69" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + + # *** EVM Services (Non-Archival) *** + + fraxtal: + evm: + chain_id: "0xfc" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + + kava: + evm: + chain_id: "0x8ae" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + + moonriver: + evm: + chain_id: "0x505" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + + opbnb: + evm: + chain_id: "0xcc" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + + sui: + evm: + chain_id: "0x101" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + + tron: + evm: + chain_id: "0x2b6653dc" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + + sei: + evm: + chain_id: "0x531" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + + hey: + evm: + chain_id: "0x1" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + + hyperliquid: + evm: + chain_id: "0x3e7" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x162cc7c861ebd0c06b3d72319201150482518185" + contract_start_block: 649107292 + threshold: 1000 + + unichain: + evm: + chain_id: "0x82" + sync_allowance: 100 + supported_apis: + JSON_RPC: {} + archival_check: + contract_address: "0x1F98400000000000000000000000000000000004" + contract_start_block: 21495984 + threshold: 1000 + + # *** Cosmos SDK Services *** + + akash: + cosmos: + chain_id: "akashnet-2" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + arkeo: + cosmos: + chain_id: "arkeo-main-v1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + atomone: + cosmos: + chain_id: "atomone-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + babylon: + cosmos: + chain_id: "bbn-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + celestia: + cosmos: + chain_id: "celestia" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + cheqd: + cosmos: + chain_id: "cheqd-mainnet-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + chihuahua: + cosmos: + chain_id: "chihuahua-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + cosmoshub: + cosmos: + chain_id: "cosmoshub-4" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + dungeon-chain: + cosmos: + chain_id: "dungeon-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + elys-network: + cosmos: + chain_id: "elys-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + fetch: + cosmos: + chain_id: "fetchhub-4" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + jackal: + cosmos: + chain_id: "jackal-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + juno: + cosmos: + chain_id: "juno-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + kyve: + cosmos: + chain_id: "kyve-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + neutron: + cosmos: + chain_id: "neutron-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + nillion: + cosmos: + chain_id: "nillion-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + osmosis: + cosmos: + chain_id: "osmosis-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + passage: + cosmos: + chain_id: "passage-2" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + persistence: + cosmos: + chain_id: "core-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + provenance: + cosmos: + chain_id: "pio-mainnet-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + pocket: + cosmos: + chain_id: "pocket" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + pocket-alpha: + cosmos: + chain_id: "pocket-alpha" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + pocket-beta: + cosmos: + chain_id: "pocket-beta" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + pocket-beta1: + cosmos: + chain_id: "pocket-beta" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + pocket-beta2: + cosmos: + chain_id: "pocket-beta" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + pocket-beta3: + cosmos: + chain_id: "pocket-beta" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + pocket-beta4: + cosmos: + chain_id: "pocket-beta" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + quicksilver: + cosmos: + chain_id: "quicksilver-2" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + router: + cosmos: + chain_id: "router_9600-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + - JSON_RPC + + seda: + cosmos: + chain_id: "seda-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + shentu: + cosmos: + chain_id: "shentu-2.2" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + bitway: + cosmos: + chain_id: "bitway-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + stargaze: + cosmos: + chain_id: "stargaze-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + stride: + cosmos: + chain_id: "stride-1" + evm_chain_id: "" + sync_allowance: 5 + supported_apis: + - REST + - COMET_BFT + + xrplevm: + cosmos: + chain_id: "xrplevm_1440000-1" + evm_chain_id: "0x15f900" + sync_allowance: 5 + supported_apis: + - JSON_RPC + - REST + - COMET_BFT + - WEBSOCKET + + xrplevm-testnet: + cosmos: + chain_id: "xrplevm_1449000-1" + evm_chain_id: "0x161c28" + sync_allowance: 5 + supported_apis: + - JSON_RPC + - REST + - COMET_BFT + - WEBSOCKET + + # *** Solana Services *** + + solana: + solana: + chain_id: "solana"