Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d857130
feat: add eth_config to el page view
barnabasbusa Jul 24, 2025
cdaf7d5
fix wrapping issue
barnabasbusa Jul 24, 2025
d5396e6
fix fmt
barnabasbusa Jul 24, 2025
3f90071
fix fmt
barnabasbusa Jul 24, 2025
1c56761
make it better
barnabasbusa Jul 24, 2025
6c5b916
cleaner
barnabasbusa Jul 24, 2025
d7e839b
cleanup
barnabasbusa Jul 24, 2025
08f1f63
Merge branch 'master' into bbusa/el-fork-view
barnabasbusa Jul 24, 2025
2ff94f9
Merge branch 'master' into bbusa/el-fork-view
barnabasbusa Jul 29, 2025
66cab04
Merge branch 'master' into bbusa/el-fork-view
barnabasbusa Jul 29, 2025
503b6d5
fix: update to latest spec
barnabasbusa Aug 1, 2025
866b700
Merge branch 'master' into bbusa/el-fork-view
barnabasbusa Aug 4, 2025
8f386a0
refactoring
barnabasbusa Aug 4, 2025
d1f6d2f
Merge branch 'bbusa/el-fork-view' of github.com:ethpandaops/dora into…
barnabasbusa Aug 4, 2025
5bc7388
go fmt
barnabasbusa Aug 4, 2025
f8041a3
fixes
barnabasbusa Aug 4, 2025
d05bca8
fixes
barnabasbusa Aug 4, 2025
1570813
fix go fmt
barnabasbusa Aug 4, 2025
1ccf10f
Merge branch 'master' into bbusa/el-fork-view
barnabasbusa Aug 4, 2025
6d12769
Merge branch 'master' into bbusa/el-fork-view
barnabasbusa Aug 14, 2025
2b37884
Merge branch 'master' into bbusa/el-fork-view
barnabasbusa Aug 15, 2025
e71fb6a
Merge branch 'master' into bbusa/el-fork-view
barnabasbusa Aug 18, 2025
097fd45
Merge branch 'master' into bbusa/el-fork-view
barnabasbusa Sep 4, 2025
5ade3eb
Merge branch 'master' into bbusa/el-fork-view
barnabasbusa Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions clients/execution/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ type Client struct {
nodeInfo *p2p.NodeInfo
peers []*p2p.PeerInfo
didFetchPeers bool
ethConfig *EthConfig
ethConfigMutex sync.RWMutex
}

func (pool *Pool) newPoolClient(clientIdx uint16, endpoint *ClientConfig) (*Client, error) {
Expand Down Expand Up @@ -92,6 +94,16 @@ func (client *Client) GetNodeInfo() *p2p.NodeInfo {
return client.nodeInfo
}

func (client *Client) GetEthConfig(ctx context.Context) (map[string]interface{}, error) {
return client.rpcClient.GetEthConfig(ctx)
}

func (client *Client) GetCachedEthConfig() *EthConfig {
client.ethConfigMutex.RLock()
defer client.ethConfigMutex.RUnlock()
return client.ethConfig
}

func (client *Client) GetEndpointConfig() *ClientConfig {
return client.endpointConfig
}
Expand Down
17 changes: 17 additions & 0 deletions clients/execution/clientlogic.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,23 @@ func (client *Client) updateNodeMetadata(ctx context.Context) error {
client.peers = peers
client.didFetchPeers = true

// get eth_config
rawEthConfig, err := client.rpcClient.GetEthConfig(ctx)
if err != nil {
client.logger.Debugf("could not get eth_config: %v", err)
// Don't return error since eth_config is optional
} else {
parsedConfig, err := ParseEthConfig(rawEthConfig)
if err != nil {
client.logger.Warnf("could not parse eth_config: %v", err)
} else {
client.ethConfigMutex.Lock()
client.ethConfig = parsedConfig
client.ethConfigMutex.Unlock()
client.logger.Debugf("updated eth_config data")
}
}

return nil
}

Expand Down
138 changes: 138 additions & 0 deletions clients/execution/ethconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package execution

import (
"fmt"
"strconv"
"time"
)

type EthConfigFork struct {
ActivationTime uint64 `json:"activationTime"`
ChainID string `json:"chainId"`
ForkID string `json:"forkId"`
BlobSchedule map[string]string `json:"blobSchedule"`
Precompiles map[string]string `json:"precompiles"`
SystemContracts map[string]string `json:"systemContracts"`
}

type EthConfig struct {
Current *EthConfigFork `json:"current"`
Next *EthConfigFork `json:"next"`
Last *EthConfigFork `json:"last"`
}

// ParseEthConfig parses the raw eth_config response into a structured EthConfig
func ParseEthConfig(rawConfig map[string]interface{}) (*EthConfig, error) {
if rawConfig == nil {
return nil, nil
}

config := &EthConfig{}

if current, ok := rawConfig["current"].(map[string]interface{}); ok {
parsed, err := parseEthConfigFork(current)
if err != nil {
return nil, fmt.Errorf("failed to parse current config: %w", err)
}
config.Current = parsed
}

if next, ok := rawConfig["next"].(map[string]interface{}); ok {
parsed, err := parseEthConfigFork(next)
if err != nil {
return nil, fmt.Errorf("failed to parse next config: %w", err)
}
config.Next = parsed
}

if last, ok := rawConfig["last"].(map[string]interface{}); ok {
parsed, err := parseEthConfigFork(last)
if err != nil {
return nil, fmt.Errorf("failed to parse last config: %w", err)
}
config.Last = parsed
}

return config, nil
}

func parseEthConfigFork(raw map[string]interface{}) (*EthConfigFork, error) {
fork := &EthConfigFork{}

// Parse activation time
if activationTime, ok := raw["activationTime"]; ok {
switch v := activationTime.(type) {
case float64:
fork.ActivationTime = uint64(v)
case string:
parsed, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse activationTime: %w", err)
}
fork.ActivationTime = parsed
}
}

// Parse chain ID
if chainID, ok := raw["chainId"].(string); ok {
fork.ChainID = chainID
}

// Parse fork ID
if forkID, ok := raw["forkId"].(string); ok {
fork.ForkID = forkID
}

// Parse blob schedule
if blobSchedule, ok := raw["blobSchedule"].(map[string]interface{}); ok {
fork.BlobSchedule = parseStringMap(blobSchedule)
}

// Parse precompiles
if precompiles, ok := raw["precompiles"].(map[string]interface{}); ok {
fork.Precompiles = parseStringMap(precompiles)
}

// Parse system contracts
if systemContracts, ok := raw["systemContracts"].(map[string]interface{}); ok {
fork.SystemContracts = parseStringMap(systemContracts)
}

return fork, nil
}

func parseStringMap(raw map[string]interface{}) map[string]string {
result := make(map[string]string)
for key, value := range raw {
if str, ok := value.(string); ok {
result[key] = str
}
}
return result
}

// GetActivationTime returns the activation time as a time.Time
func (f *EthConfigFork) GetActivationTime() time.Time {
return time.Unix(int64(f.ActivationTime), 0)
}

// GetSystemContractAddress returns the address of a specific system contract
func (f *EthConfigFork) GetSystemContractAddress(contractType string) string {
if f.SystemContracts == nil {
return ""
}

// Map common contract type names to their actual keys
keyMap := map[string]string{
"deposit": "depositContract",
"withdrawal": "withdrawalContract",
"consolidation": "consolidationContract",
}

if key, exists := keyMap[contractType]; exists {
return f.SystemContracts[key]
}

// Also allow direct key lookup for flexibility
return f.SystemContracts[contractType]
}
6 changes: 6 additions & 0 deletions clients/execution/rpc/executionapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ func (ec *ExecutionClient) GetAdminNodeInfo(ctx context.Context) (*p2p.NodeInfo,
return result, err
}

func (ec *ExecutionClient) GetEthConfig(ctx context.Context) (map[string]interface{}, error) {
var result map[string]interface{}
err := ec.rpcClient.CallContext(ctx, &result, "eth_config")
return result, err
}

func (ec *ExecutionClient) GetNodeSyncing(ctx context.Context) (*SyncStatus, error) {
status, err := ec.ethClient.SyncProgress(ctx)
if err != nil {
Expand Down
53 changes: 53 additions & 0 deletions handlers/clients_el.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethpandaops/dora/clients/execution"
"github.com/ethpandaops/dora/services"
"github.com/ethpandaops/dora/templates"
"github.com/ethpandaops/dora/types/models"
Expand Down Expand Up @@ -283,6 +284,8 @@ func buildELClientsPageData(sortOrder string) (*models.ClientsELPageData, time.D
PeerID: peerID,
}

forkConfig := buildForkConfig(client)

resNode := &models.ClientsELPageDataNode{
Name: client.GetName(),
Version: client.GetVersion(),
Expand All @@ -291,6 +294,7 @@ func buildELClientsPageData(sortOrder string) (*models.ClientsELPageData, time.D
PeerID: peerID,
PeerName: peerName,
DidFetchPeers: client.DidFetchPeers(),
ForkConfig: forkConfig,
}

if pageData.ShowSensitivePeerInfos {
Expand Down Expand Up @@ -397,3 +401,52 @@ func buildELClientsPageData(sortOrder string) (*models.ClientsELPageData, time.D

return pageData, cacheTime
}

func buildForkConfig(client *execution.Client) *models.ClientELPageDataForkConfig {
ethConfig := client.GetCachedEthConfig()
if ethConfig == nil {
return nil
}

forkConfig := &models.ClientELPageDataForkConfig{}

if ethConfig.Current != nil {
forkConfig.Current = convertEthConfigFork(ethConfig.Current)
}

if ethConfig.Next != nil {
forkConfig.Next = convertEthConfigFork(ethConfig.Next)
}

if ethConfig.Last != nil {
forkConfig.Last = convertEthConfigFork(ethConfig.Last)
}

return forkConfig
}

func convertEthConfigFork(fork *execution.EthConfigFork) *models.EthConfigObject {
obj := &models.EthConfigObject{}

obj.ActivationTime = fork.ActivationTime
obj.ChainId = fork.ChainID
obj.ForkId = fork.ForkID

// Convert string maps to interface{} maps for model compatibility
obj.BlobSchedule = convertStringMapToInterface(fork.BlobSchedule)
obj.Precompiles = convertStringMapToInterface(fork.Precompiles)
obj.SystemContracts = convertStringMapToInterface(fork.SystemContracts)

return obj
}

func convertStringMapToInterface(stringMap map[string]string) map[string]interface{} {
if stringMap == nil {
return nil
}
interfaceMap := make(map[string]interface{})
for key, value := range stringMap {
interfaceMap[key] = value
}
return interfaceMap
}
8 changes: 7 additions & 1 deletion handlers/submit_consolidation.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,18 @@ func buildSubmitConsolidationPageData() (*models.SubmitConsolidationPageData, ti
chainState := services.GlobalBeaconService.GetChainState()
specs := chainState.GetSpecs()

// Get consolidation contract address from client config, fallback to default
consolidationContract := services.GlobalBeaconService.GetSystemContractAddress("consolidation")
if consolidationContract == "" {
consolidationContract = execution.DefaultConsolidationContractAddr
}

pageData := &models.SubmitConsolidationPageData{
NetworkName: specs.ConfigName,
PublicRPCUrl: utils.Config.Frontend.PublicRPCUrl,
RainbowkitProjectId: utils.Config.Frontend.RainbowkitProjectId,
ChainId: specs.DepositChainId,
ConsolidationContract: execution.ConsolidationContractAddr,
ConsolidationContract: consolidationContract,
ExplorerUrl: utils.Config.Frontend.EthExplorerLink,
}

Expand Down
8 changes: 7 additions & 1 deletion handlers/submit_withdrawal.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,18 @@ func buildSubmitWithdrawalPageData() (*models.SubmitWithdrawalPageData, time.Dur
chainState := services.GlobalBeaconService.GetChainState()
specs := chainState.GetSpecs()

// Get withdrawal contract address from client config, fallback to default
withdrawalContract := services.GlobalBeaconService.GetSystemContractAddress("withdrawal")
if withdrawalContract == "" {
withdrawalContract = execution.DefaultWithdrawalContractAddr
}

pageData := &models.SubmitWithdrawalPageData{
NetworkName: specs.ConfigName,
PublicRPCUrl: utils.Config.Frontend.PublicRPCUrl,
RainbowkitProjectId: utils.Config.Frontend.RainbowkitProjectId,
ChainId: specs.DepositChainId,
WithdrawalContract: execution.WithdrawalContractAddr,
WithdrawalContract: withdrawalContract,
ExplorerUrl: utils.Config.Frontend.EthExplorerLink,
MinValidatorBalance: specs.MinActivationBalance,
}
Expand Down
13 changes: 11 additions & 2 deletions indexer/execution/consolidation_indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/ethpandaops/dora/utils"
)

const ConsolidationContractAddr = "0x0000BBdDc7CE488642fb579F8B00f3a590007251"
const DefaultConsolidationContractAddr = "0x0000BBdDc7CE488642fb579F8B00f3a590007251"

// ConsolidationIndexer is the indexer for the eip-7251 consolidation system contract
type ConsolidationIndexer struct {
Expand Down Expand Up @@ -53,7 +53,7 @@ func NewConsolidationIndexer(indexer *IndexerCtx) *ConsolidationIndexer {
&contractIndexerOptions[dbtypes.ConsolidationRequestTx]{
stateKey: "indexer.consolidationindexer",
batchSize: batchSize,
contractAddress: common.HexToAddress(ConsolidationContractAddr),
contractAddress: common.HexToAddress(ci.getConsolidationContractAddr()),
deployBlock: uint64(utils.Config.ExecutionApi.ElectraDeployBlock),
dequeueRate: specs.MaxConsolidationRequestsPerPayload,

Expand Down Expand Up @@ -82,6 +82,15 @@ func NewConsolidationIndexer(indexer *IndexerCtx) *ConsolidationIndexer {
return ci
}

// getConsolidationContractAddr returns the consolidation contract address from config or falls back to default
func (ci *ConsolidationIndexer) getConsolidationContractAddr() string {
if addr := ci.indexerCtx.GetSystemContractAddress("consolidation"); addr != "" {
return addr
}
ci.logger.Warnf("using default consolidation contract address, could not get from client config")
return DefaultConsolidationContractAddr
}

// GetMatcherHeight returns the last processed el block number from the transaction matcher
func (ci *ConsolidationIndexer) GetMatcherHeight() uint64 {
return ci.matcher.GetMatcherHeight()
Expand Down
28 changes: 28 additions & 0 deletions indexer/execution/indexerctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,31 @@ func (ictx *IndexerCtx) getForksWithClients(clientType execution.ClientType) []*

return forksWithClients
}

// GetSystemContractAddress returns the address of a system contract from the first available client's config
func (ictx *IndexerCtx) GetSystemContractAddress(contractType string) string {
// Try canonical clients first
for _, forkWithClients := range ictx.getForksWithClients(execution.AnyClient) {
if forkWithClients.canonical && len(forkWithClients.clients) > 0 {
client := forkWithClients.clients[0]
config := client.GetCachedEthConfig()
if config != nil && config.Current != nil {
if addr := config.Current.GetSystemContractAddress(contractType); addr != "" {
return addr
}
}
}
}

// Fallback to any available client if no canonical fork found
for client := range ictx.executionClients {
config := client.GetCachedEthConfig()
if config != nil && config.Current != nil {
if addr := config.Current.GetSystemContractAddress(contractType); addr != "" {
return addr
}
}
}

return ""
}
Loading