Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 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
a6a09f4
add el genesis config setting & parsing
pk910 Sep 5, 2025
e9ecce3
Merge branch 'bbusa/el-fork-view' into pk910/el-config
pk910 Sep 5, 2025
0add011
refactor eth_client parsing & comparison; add blob base fee to slot …
pk910 Sep 5, 2025
3bb4277
`go fmt`
pk910 Sep 5, 2025
499f1cb
make gofmt happy
pk910 Sep 6, 2025
446e518
visualize spec/config diffs on clients UI
pk910 Sep 8, 2025
01875b2
`go fmt`
pk910 Sep 8, 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
4 changes: 4 additions & 0 deletions .hack/devnet/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ kurtosis files inspect "$ENCLAVE_NAME" validator-ranges validator-ranges.yaml |
# Get dora config
kurtosis files inspect "$ENCLAVE_NAME" dora-config dora-config.yaml | tail -n +2 > "${__dir}/generated-dora-kt-config.yaml"

# Get el genesis config
kurtosis files inspect "$ENCLAVE_NAME" el_cl_genesis_data "./genesis.json" | tail -n +1 > "${__dir}/generated-el-genesis.json"

# Extract network name from config
NETWORK_NAME=$(grep -E "^\s*network:" "${config_file}" | sed 's/.*network:\s*//' | tr -d '"'\'' ')

Expand Down Expand Up @@ -105,6 +108,7 @@ $(for node in $BEACON_NODES; do
echo " - { name: $name, url: http://$ip:$port }"
done)
executionapi:
genesisConfig: "${__dir}/generated-el-genesis.json"
depositLogBatchSize: 1000
endpoints:
$(for node in $EXECUTION_NODES; do
Expand Down
6 changes: 6 additions & 0 deletions clients/consensus/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ type Client struct {
blockDispatcher utils.Dispatcher[*v1.BlockEvent]
headDispatcher utils.Dispatcher[*v1.HeadEvent]
checkpointDispatcher utils.Dispatcher[*v1.Finality]

specWarnings []string // warnings from incomplete spec checks
}

func (pool *Pool) newPoolClient(clientIdx uint16, endpoint *ClientConfig) (*Client, error) {
Expand Down Expand Up @@ -175,3 +177,7 @@ func (client *Client) GetNodePeers() []*v1.Peer {
}
return client.peers
}

func (client *Client) GetSpecWarnings() []string {
return client.specWarnings
}
3 changes: 3 additions & 0 deletions clients/consensus/clientlogic.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ func (client *Client) checkClient() error {

if warning != nil {
client.logger.Warnf("incomplete chain specs: %v", warning)
client.specWarnings = []string{warning.Error()}
} else {
client.specWarnings = nil
}

// init wallclock
Expand Down
273 changes: 271 additions & 2 deletions clients/execution/chainstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ package execution
import (
"fmt"
"math/big"
"sort"
"strings"
"sync"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethpandaops/dora/clients/execution/rpc"
)

type ChainState struct {
specMutex sync.RWMutex
specs *rpc.ChainSpec
specMutex sync.RWMutex
specs *rpc.ChainSpec
clientConfigMutex sync.RWMutex
clientConfig *rpc.EthConfig
config *core.Genesis
}

func newChainState() *ChainState {
Expand All @@ -34,6 +41,58 @@ func (cache *ChainState) SetClientSpecs(specs *rpc.ChainSpec) error {
return nil
}

func (cache *ChainState) SetClientConfig(config *rpc.EthConfig) ([]error, error) {
cache.clientConfigMutex.Lock()
defer cache.clientConfigMutex.Unlock()

if config.Current == nil {
return nil, fmt.Errorf("current config is nil")
}

warnings := []error{}

if cache.clientConfig != nil {
currentConfig := config.Current
nextConfig := config.Next
lastConfig := config.Last

// check current config
mismatches := cache.clientConfig.Current.CheckMismatch(currentConfig)
if len(mismatches) > 0 && cache.clientConfig.Current.ActivationTime < currentConfig.ActivationTime && currentConfig.ActivationTime >= uint64(time.Now().Unix()) {
// current config changed
cache.clientConfig.Last = cache.clientConfig.Current
cache.clientConfig.Current = currentConfig
cache.clientConfig.Next = nextConfig
} else if len(mismatches) > 0 {
return nil, fmt.Errorf("client config mismatch: %v", strings.Join(mismatches, ", "))
}

if lastConfig == nil && cache.clientConfig.Last != nil {
warnings = append(warnings, fmt.Errorf("last config is nil"))
} else if lastConfig != nil && cache.clientConfig.Last == nil {
cache.clientConfig.Last = lastConfig
} else if lastConfig != nil {
if lastMismatches := cache.clientConfig.Last.CheckMismatch(lastConfig); len(lastMismatches) > 0 {
warnings = append(warnings, fmt.Errorf("last config mismatch: %v", strings.Join(lastMismatches, ", ")))
}
}

if nextConfig == nil && cache.clientConfig.Next != nil {
warnings = append(warnings, fmt.Errorf("next config is nil"))
} else if nextConfig != nil && cache.clientConfig.Next == nil {
cache.clientConfig.Next = nextConfig
} else if nextConfig != nil {
if nextMismatches := cache.clientConfig.Next.CheckMismatch(nextConfig); len(nextMismatches) > 0 {
warnings = append(warnings, fmt.Errorf("next config mismatch: %v", strings.Join(nextMismatches, ", ")))
}
}
} else {
cache.clientConfig = config
}

return warnings, nil
}

func (cache *ChainState) GetSpecs() *rpc.ChainSpec {
cache.specMutex.RLock()
defer cache.specMutex.RUnlock()
Expand All @@ -58,3 +117,213 @@ func (cache *ChainState) GetChainID() *big.Int {

return chainID
}

func (cache *ChainState) getCurrentEthConfig() *rpc.EthConfigFork {
cache.clientConfigMutex.RLock()
defer cache.clientConfigMutex.RUnlock()

if cache.clientConfig == nil || cache.clientConfig.Current == nil {
return nil
}

currentConfig := cache.clientConfig.Current

if cache.clientConfig.Next != nil && time.Since(currentConfig.GetActivationTime()) < time.Second*10 {
return cache.clientConfig.Next
}

return cache.clientConfig.Current
}

func (cache *ChainState) GetSystemContractAddress(systemContract string) *common.Address {
currentConfig := cache.getCurrentEthConfig()
if currentConfig == nil {
return nil
}

return currentConfig.GetSystemContractAddress(systemContract)
}

func (cache *ChainState) SetGenesisConfig(genesis *core.Genesis) {
cache.config = genesis
}

func (cache *ChainState) GetGenesisConfig() *core.Genesis {
return cache.config
}

func (cache *ChainState) GetBlobScheduleForTimestamp(timestamp time.Time) *rpc.EthConfigBlobSchedule {
forkSchedule := cache.getBlobScheduleForTimestampFromConfig(timestamp)
if forkSchedule == nil && cache.config != nil {
// extract fork schedule from genesis config
forkSchedules := cache.GetFullBlobSchedule()
for _, schedule := range forkSchedules {
if schedule.Timestamp.Before(timestamp) {
forkSchedule = &schedule.Schedule
}
}
}

return forkSchedule
}

func (cache *ChainState) getBlobScheduleForTimestampFromConfig(timestamp time.Time) *rpc.EthConfigBlobSchedule {
cache.clientConfigMutex.RLock()
defer cache.clientConfigMutex.RUnlock()

if cache.clientConfig == nil || cache.clientConfig.Current == nil {
return nil
}

var forkSchedule *rpc.EthConfigBlobSchedule

if cache.clientConfig.Next != nil && !cache.clientConfig.Next.GetActivationTime().After(timestamp) {
forkSchedule = &cache.clientConfig.Current.BlobSchedule
} else if cache.clientConfig.Current != nil && !cache.clientConfig.Current.GetActivationTime().After(timestamp) {
forkSchedule = &cache.clientConfig.Current.BlobSchedule
} else if cache.clientConfig.Last != nil && !cache.clientConfig.Last.GetActivationTime().After(timestamp) {
forkSchedule = &cache.clientConfig.Last.BlobSchedule
}

return forkSchedule
}

type BlobScheduleEntry struct {
Timestamp time.Time
Schedule rpc.EthConfigBlobSchedule
}

func (cache *ChainState) GetFullBlobSchedule() []BlobScheduleEntry {
forkSchedules := []BlobScheduleEntry{}

if cache.config == nil {
return forkSchedules
}

if cache.config.Config.CancunTime != nil && cache.config.Config.BlobScheduleConfig.Cancun != nil {
forkSchedules = append(forkSchedules, BlobScheduleEntry{
Timestamp: time.Unix(int64(*cache.config.Config.CancunTime), 0),
Schedule: rpc.EthConfigBlobSchedule{
Max: uint64(cache.config.Config.BlobScheduleConfig.Cancun.Max),
Target: uint64(cache.config.Config.BlobScheduleConfig.Cancun.Target),
BaseFeeUpdateFraction: uint64(cache.config.Config.BlobScheduleConfig.Cancun.UpdateFraction),
},
})
}

if cache.config.Config.PragueTime != nil && cache.config.Config.BlobScheduleConfig.Prague != nil {
forkSchedules = append(forkSchedules, BlobScheduleEntry{
Timestamp: time.Unix(int64(*cache.config.Config.PragueTime), 0),
Schedule: rpc.EthConfigBlobSchedule{
Max: uint64(cache.config.Config.BlobScheduleConfig.Prague.Max),
Target: uint64(cache.config.Config.BlobScheduleConfig.Prague.Target),
BaseFeeUpdateFraction: uint64(cache.config.Config.BlobScheduleConfig.Prague.UpdateFraction),
},
})
}

if cache.config.Config.OsakaTime != nil && cache.config.Config.BlobScheduleConfig.Osaka != nil {
forkSchedules = append(forkSchedules, BlobScheduleEntry{
Timestamp: time.Unix(int64(*cache.config.Config.OsakaTime), 0),
Schedule: rpc.EthConfigBlobSchedule{
Max: uint64(cache.config.Config.BlobScheduleConfig.Osaka.Max),
Target: uint64(cache.config.Config.BlobScheduleConfig.Osaka.Target),
BaseFeeUpdateFraction: uint64(cache.config.Config.BlobScheduleConfig.Osaka.UpdateFraction),
},
})
}

if cache.config.Config.BPO1Time != nil && cache.config.Config.BlobScheduleConfig.BPO1 != nil {
forkSchedules = append(forkSchedules, BlobScheduleEntry{
Timestamp: time.Unix(int64(*cache.config.Config.BPO1Time), 0),
Schedule: rpc.EthConfigBlobSchedule{
Max: uint64(cache.config.Config.BlobScheduleConfig.BPO1.Max),
Target: uint64(cache.config.Config.BlobScheduleConfig.BPO1.Target),
BaseFeeUpdateFraction: uint64(cache.config.Config.BlobScheduleConfig.BPO1.UpdateFraction),
},
})
}

if cache.config.Config.BPO2Time != nil && cache.config.Config.BlobScheduleConfig.BPO2 != nil {
forkSchedules = append(forkSchedules, BlobScheduleEntry{
Timestamp: time.Unix(int64(*cache.config.Config.BPO2Time), 0),
Schedule: rpc.EthConfigBlobSchedule{
Max: uint64(cache.config.Config.BlobScheduleConfig.BPO2.Max),
Target: uint64(cache.config.Config.BlobScheduleConfig.BPO2.Target),
BaseFeeUpdateFraction: uint64(cache.config.Config.BlobScheduleConfig.BPO2.UpdateFraction),
},
})
}

if cache.config.Config.BPO3Time != nil && cache.config.Config.BlobScheduleConfig.BPO3 != nil {
forkSchedules = append(forkSchedules, BlobScheduleEntry{
Timestamp: time.Unix(int64(*cache.config.Config.BPO3Time), 0),
Schedule: rpc.EthConfigBlobSchedule{
Max: uint64(cache.config.Config.BlobScheduleConfig.BPO3.Max),
Target: uint64(cache.config.Config.BlobScheduleConfig.BPO3.Target),
BaseFeeUpdateFraction: uint64(cache.config.Config.BlobScheduleConfig.BPO3.UpdateFraction),
},
})
}

if cache.config.Config.BPO4Time != nil && cache.config.Config.BlobScheduleConfig.BPO4 != nil {
forkSchedules = append(forkSchedules, BlobScheduleEntry{
Timestamp: time.Unix(int64(*cache.config.Config.BPO4Time), 0),
Schedule: rpc.EthConfigBlobSchedule{
Max: uint64(cache.config.Config.BlobScheduleConfig.BPO4.Max),
Target: uint64(cache.config.Config.BlobScheduleConfig.BPO4.Target),
BaseFeeUpdateFraction: uint64(cache.config.Config.BlobScheduleConfig.BPO4.UpdateFraction),
},
})
}

if cache.config.Config.BPO5Time != nil && cache.config.Config.BlobScheduleConfig.BPO5 != nil {
forkSchedules = append(forkSchedules, BlobScheduleEntry{
Timestamp: time.Unix(int64(*cache.config.Config.BPO5Time), 0),
Schedule: rpc.EthConfigBlobSchedule{
Max: uint64(cache.config.Config.BlobScheduleConfig.BPO5.Max),
Target: uint64(cache.config.Config.BlobScheduleConfig.BPO5.Target),
BaseFeeUpdateFraction: uint64(cache.config.Config.BlobScheduleConfig.BPO5.UpdateFraction),
},
})
}

sort.Slice(forkSchedules, func(i, j int) bool {
return forkSchedules[i].Timestamp.Before(forkSchedules[j].Timestamp)
})

return forkSchedules
}

const MinBaseFeePerBlobGas = 1 // wei, per EIP-4844

// CalcBaseFeePerBlobGas computes base_fee_per_blob_gas from excess_blob_gas and updateFraction.
// Arguments:
// - excessBlobGas: from block header
// - blockTimestamp
//
// Returns base_fee_per_blob_gas as *big.Int
func (cache *ChainState) CalcBaseFeePerBlobGas(excessBlobGas uint64, updateFraction uint64) *big.Int {
// fake_exponential(minBaseFee, excessBlobGas, updateFraction)
// base = minBaseFee
// factor = excessBlobGas / updateFraction
// remainder = excessBlobGas % updateFraction
// result = base * (2^factor) * (updateFraction + remainder) / updateFraction

base := big.NewInt(MinBaseFeePerBlobGas)

factor := new(big.Int).Div(new(big.Int).SetUint64(excessBlobGas), new(big.Int).SetUint64(updateFraction))
remainder := new(big.Int).Mod(new(big.Int).SetUint64(excessBlobGas), new(big.Int).SetUint64(updateFraction))

// base << factor (multiply by 2^factor)
base.Lsh(base, uint(factor.Uint64()))

// multiply by (updateFraction + remainder)
num := new(big.Int).Add(new(big.Int).SetUint64(updateFraction), remainder)
base.Mul(base, num)

// divide by updateFraction
base.Div(base, new(big.Int).SetUint64(updateFraction))

return base
}
10 changes: 10 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 *rpc.EthConfig
configWarnings []string // warnings from eth_config checks
}

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

func (client *Client) GetEthConfig() *rpc.EthConfig {
return client.ethConfig
}

func (client *Client) GetEndpointConfig() *ClientConfig {
return client.endpointConfig
}
Expand Down Expand Up @@ -141,3 +147,7 @@ func (client *Client) DidFetchPeers() bool {
func (client *Client) ForceUpdatePeerData(ctx context.Context) error {
return client.updateNodeMetadata(ctx)
}

func (client *Client) GetConfigWarnings() []string {
return client.configWarnings
}
Loading