Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/go-co-op/gocron v1.18.0
github.com/julienschmidt/httprouter v1.3.0
github.com/nanmu42/gzip v1.2.0
github.com/pk910/dynamic-ssz v1.1.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.23.2
github.com/sirupsen/logrus v1.9.3
Expand All @@ -20,7 +21,9 @@ require (
)

require (
github.com/OffchainLabs/hashtree v0.2.1-0.20250530191054-577f0b75c7f7 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/casbin/govaluate v1.8.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
Expand Down Expand Up @@ -52,7 +55,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pk910/dynamic-ssz v0.0.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
Expand All @@ -77,7 +79,8 @@ require (
golang.org/x/text v0.31.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace github.com/ethpandaops/beacon => github.com/ethpandaops/beacon v0.65.1-0.20251202112837-969b8bfb898a
14 changes: 8 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
github.com/OffchainLabs/hashtree v0.2.1-0.20250530191054-577f0b75c7f7 h1:0r1HjExe/tyypkt380UTpjvILd5kLw51Xzl6a+hknQ8=
github.com/OffchainLabs/hashtree v0.2.1-0.20250530191054-577f0b75c7f7/go.mod h1:b07+cRZs+eAR8TR57CB9TQlt5Gnl/06Xs76xt/1wq0M=
github.com/attestantio/go-eth2-client v0.27.2 h1:VjA9R39ovy8ryb7IpFfD5eLYBg/20biztxh6fKZ7/K0=
github.com/attestantio/go-eth2-client v0.27.2/go.mod h1:i56XBegxVt7wXupnLBOj9IyGwy5cqaoTsCSKlwTubEU=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/casbin/govaluate v1.8.0 h1:1dUaV/I0LFP2tcY1uNQEb6wBCbp8GMTcC/zhwQDWvZo=
github.com/casbin/govaluate v1.8.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4=
Expand All @@ -22,8 +26,8 @@ github.com/emicklei/dot v1.6.4 h1:cG9ycT67d9Yw22G+mAb4XiuUz6E6H1S0zePp/5Cwe/c=
github.com/emicklei/dot v1.6.4/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/ethereum/go-ethereum v1.16.4 h1:H6dU0r2p/amA7cYg6zyG9Nt2JrKKH6oX2utfcqrSpkQ=
github.com/ethereum/go-ethereum v1.16.4/go.mod h1:P7551slMFbjn2zOQaKrJShZVN/d8bGxp4/I6yZVlb5w=
github.com/ethpandaops/beacon v0.65.0 h1:ssnab73uiuzhmhtU56q9D3ecVrEYv5n+tuqrXRK4YAg=
github.com/ethpandaops/beacon v0.65.0/go.mod h1:lgzrJjQVV77wZ+PJymsY3bQbAK4jrtP8n3WOwMf1Pcs=
github.com/ethpandaops/beacon v0.65.1-0.20251202112837-969b8bfb898a h1:tKi7hYEUIwkxcyP/mrom3gCUU80s9zsydi5UsF2wIC8=
github.com/ethpandaops/beacon v0.65.1-0.20251202112837-969b8bfb898a/go.mod h1:zymhKjSomMfgCi5Azg3Y9ZpeUVYaH5oSsv0RWQKs344=
github.com/ethpandaops/ethwallclock v0.2.0 h1:EeFKtZ7v6TAdn/oAh0xaPujD7N4amjBxrWIByraUfLM=
github.com/ethpandaops/ethwallclock v0.2.0/go.mod h1:y0Cu+mhGLlem19vnAV2x0hpFS5KZ7oOi2SWYayv9l24=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
Expand Down Expand Up @@ -152,8 +156,8 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pk910/dynamic-ssz v0.0.4 h1:DT29+1055tCEPCaR4V/ez+MOKW7BzBsmjyFvBRqx0ME=
github.com/pk910/dynamic-ssz v0.0.4/go.mod h1:b6CrLaB2X7pYA+OSEEbkgXDEcRnjLOZIxZTsMuO/Y9c=
github.com/pk910/dynamic-ssz v1.1.1 h1:b8sPR8fyhBvz8SHa2RH20SNtt5VDzAEY6fKsPCUcYX4=
github.com/pk910/dynamic-ssz v1.1.1/go.mod h1:3zyemisUysY2PWACZ8LeZS2tAw8AkuTb2GaLmqYsg1I=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down Expand Up @@ -280,8 +284,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
38 changes: 37 additions & 1 deletion pkg/beacon/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import (
"github.com/ethpandaops/checkpointz/pkg/eth"
"github.com/ethpandaops/ethwallclock"
"github.com/go-co-op/gocron"
dynssz "github.com/pk910/dynamic-ssz"
perrors "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)

type Default struct {
Expand All @@ -43,6 +45,7 @@ type Default struct {

specMutex sync.Mutex
spec *state.Spec
dynSsz *dynssz.DynSsz
genesis *v1.Genesis

historicalSlotFailures map[phase0.Slot]int
Expand Down Expand Up @@ -454,6 +457,24 @@ func (d *Default) Spec() (*state.Spec, error) {
return &copied, nil
}

func (d *Default) setDynSsz(dynSsz *dynssz.DynSsz) {
d.specMutex.Lock()
defer d.specMutex.Unlock()

d.dynSsz = dynSsz
}

func (d *Default) DynSsz() (*dynssz.DynSsz, error) {
d.specMutex.Lock()
defer d.specMutex.Unlock()

if d.dynSsz == nil {
return nil, errors.New("dynamic SSZ encoder not yet available")
}

return d.dynSsz, nil
}

func (d *Default) OperatingMode() OperatingMode {
return d.config.Mode
}
Expand Down Expand Up @@ -514,8 +535,23 @@ func (d *Default) refreshSpec(ctx context.Context) error {
return err
}

// store the beacon state spec
// Initialize dynamic SSZ encoder following dora's approach
staticSpec := map[string]any{}

specYaml, err := yaml.Marshal(s)
if err == nil {
if err := yaml.Unmarshal(specYaml, &staticSpec); err != nil {
d.log.WithError(err).Warn("Failed to unmarshal spec for dynamic SSZ, using empty spec")
}
}

dynSsz := dynssz.NewDynSsz(staticSpec)

// store the beacon state spec and dynamic SSZ encoder
d.setSpec(s)
d.setDynSsz(dynSsz)

d.log.WithField("preset", s.PresetBase).Info("Initialized dynamic SSZ encoder with preset")

d.log.Debug("Fetched beacon spec")

Expand Down
3 changes: 3 additions & 0 deletions pkg/beacon/finality_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ethpandaops/beacon/pkg/beacon/api/types"
"github.com/ethpandaops/beacon/pkg/beacon/state"
"github.com/ethpandaops/checkpointz/pkg/eth"
dynssz "github.com/pk910/dynamic-ssz"
)

// FinalityProvider is a provider of finality information.
Expand All @@ -34,6 +35,8 @@ type FinalityProvider interface {
Genesis(ctx context.Context) (*v1.Genesis, error)
// Spec returns the chain spec.
Spec() (*state.Spec, error)
// DynSsz returns the dynamic SSZ encoder initialized with the chain spec.
DynSsz() (*dynssz.DynSsz, error)
// UpstreamsStatus returns the status of all the upstreams.
UpstreamsStatus(ctx context.Context) (map[string]*UpstreamStatus, error)
// GetBlockBySlot returns the block at the given slot.
Expand Down
191 changes: 191 additions & 0 deletions pkg/beacon/preset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package beacon_test

import (
"testing"

"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/ethpandaops/beacon/pkg/beacon/state"
"github.com/ethpandaops/checkpointz/pkg/beacon"
"github.com/ethpandaops/checkpointz/pkg/beacon/node"
dynssz "github.com/pk910/dynamic-ssz"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)

// createMainnetSpec creates a mock spec with mainnet preset values
func createMainnetSpec() *state.Spec {
specData := map[string]interface{}{
"PRESET_BASE": "mainnet",
"CONFIG_NAME": "mainnet",
"SLOTS_PER_EPOCH": "32",
"SECONDS_PER_SLOT": "12",
"MAX_VALIDATORS_PER_COMMITTEE": "2048",
"TARGET_COMMITTEE_SIZE": "128",
"MAX_EFFECTIVE_BALANCE": "32000000000",
"MIN_DEPOSIT_AMOUNT": "1000000000",
"EFFECTIVE_BALANCE_INCREMENT": "1000000000",
}

spec := state.NewSpec(specData)

return &spec
}

// createMinimalSpec creates a mock spec with minimal preset values
func createMinimalSpec() *state.Spec {
specData := map[string]interface{}{
"PRESET_BASE": "minimal",
"CONFIG_NAME": "minimal",
"SLOTS_PER_EPOCH": "8",
"SECONDS_PER_SLOT": "6",
"MAX_VALIDATORS_PER_COMMITTEE": "2048",
"TARGET_COMMITTEE_SIZE": "4",
"MAX_EFFECTIVE_BALANCE": "32000000000",
"MIN_DEPOSIT_AMOUNT": "1000000000",
"EFFECTIVE_BALANCE_INCREMENT": "1000000000",
}

spec := state.NewSpec(specData)

return &spec
}

// initializeDynSsz mimics the initialization logic from refreshSpec
func initializeDynSsz(spec *state.Spec) (*dynssz.DynSsz, error) {
staticSpec := map[string]any{}
specYaml, err := yaml.Marshal(spec)

if err != nil {
return nil, err
}

if err := yaml.Unmarshal(specYaml, &staticSpec); err != nil {
return nil, err
}

return dynssz.NewDynSsz(staticSpec), nil
}

func TestMainnetPresetDetection(t *testing.T) {
spec := createMainnetSpec()

if spec.PresetBase != "mainnet" {
t.Errorf("Expected PresetBase to be 'mainnet', got '%s'", spec.PresetBase)
}

if spec.SlotsPerEpoch != phase0.Slot(32) {
t.Errorf("Expected SlotsPerEpoch to be 32, got %d", spec.SlotsPerEpoch)
}
}

func TestMinimalPresetDetection(t *testing.T) {
spec := createMinimalSpec()

if spec.PresetBase != "minimal" {
t.Errorf("Expected PresetBase to be 'minimal', got '%s'", spec.PresetBase)
}

if spec.SlotsPerEpoch != phase0.Slot(8) {
t.Errorf("Expected SlotsPerEpoch to be 8, got %d", spec.SlotsPerEpoch)
}
}

func TestDynSszInitializationMainnet(t *testing.T) {
spec := createMainnetSpec()

dynSsz, err := initializeDynSsz(spec)
if err != nil {
t.Fatalf("Failed to initialize DynSsz: %v", err)
}

if dynSsz == nil {
t.Error("Expected DynSsz to be initialized, got nil")
}
}

func TestDynSszInitializationMinimal(t *testing.T) {
spec := createMinimalSpec()

dynSsz, err := initializeDynSsz(spec)
if err != nil {
t.Fatalf("Failed to initialize DynSsz: %v", err)
}

if dynSsz == nil {
t.Error("Expected DynSsz to be initialized, got nil")
}
}

func TestDynSszBeforeInitialization(t *testing.T) {
// Create a Default provider without initializing spec/dynSsz
log := logrus.New()
log.SetLevel(logrus.PanicLevel) // Suppress log output during tests

config := &beacon.Config{
Mode: beacon.OperatingModeFull,
}

provider := beacon.NewDefaultProvider("test", log, []node.Config{}, config)

// Try to get DynSsz before it's initialized
_, err := provider.DynSsz()
if err == nil {
t.Error("Expected error when accessing DynSsz before initialization, got nil")
}

expectedErrMsg := "dynamic SSZ encoder not yet available"
if err.Error() != expectedErrMsg {
t.Errorf("Expected error message '%s', got '%s'", expectedErrMsg, err.Error())
}
}

func TestPresetSpecificValues(t *testing.T) {
t.Parallel()

tests := []struct {
name string
createSpec func() *state.Spec
expectedBase string
expectedSlots phase0.Slot
}{
{
name: "Mainnet",
createSpec: createMainnetSpec,
expectedBase: "mainnet",
expectedSlots: 32,
},
{
name: "Minimal",
createSpec: createMinimalSpec,
expectedBase: "minimal",
expectedSlots: 8,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

spec := tt.createSpec()

if spec.PresetBase != tt.expectedBase {
t.Errorf("Expected PresetBase '%s', got '%s'", tt.expectedBase, spec.PresetBase)
}

if spec.SlotsPerEpoch != tt.expectedSlots {
t.Errorf("Expected SlotsPerEpoch %d, got %d", tt.expectedSlots, spec.SlotsPerEpoch)
}

// Test that DynSsz can be initialized with this spec
dynSsz, err := initializeDynSsz(spec)
if err != nil {
t.Fatalf("Failed to initialize DynSsz for %s: %v", tt.name, err)
}

if dynSsz == nil {
t.Errorf("Expected DynSsz to be initialized for %s, got nil", tt.name)
}
})
}
}
Loading