diff --git a/cmd/blsync/api.go b/beacon/beaclient/client.go
similarity index 85%
rename from cmd/blsync/api.go
rename to beacon/beaclient/client.go
index 0de379f03d..323096ef73 100644
--- a/cmd/blsync/api.go
+++ b/beacon/beaclient/client.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package main
+package beaclient
import (
"context"
@@ -27,6 +27,7 @@ import (
eth2api "github.com/attestantio/go-eth2-client/api"
eth2http "github.com/attestantio/go-eth2-client/http"
eth2spec "github.com/attestantio/go-eth2-client/spec"
+ "github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common"
"github.com/rs/zerolog"
)
@@ -38,13 +39,13 @@ var (
beaconLightClientFinalityUpdate = "eth/v1/beacon/light_client/finality_update"
)
-type BeaconClient struct {
+type Client struct {
ctx context.Context
url string
client eth2client.Service
}
-func NewBeaconClient(ctx context.Context, server string) (*BeaconClient, error) {
+func NewClient(ctx context.Context, server string) (*Client, error) {
client, err := eth2http.New(
ctx,
eth2http.WithAddress(server),
@@ -54,14 +55,14 @@ func NewBeaconClient(ctx context.Context, server string) (*BeaconClient, error)
if err != nil {
return nil, err
}
- return &BeaconClient{
+ return &Client{
ctx: ctx,
url: server,
client: client,
}, nil
}
-func (c *BeaconClient) Bootstrap(root common.Hash) (*Bootstrap, error) {
+func (c *Client) Bootstrap(root common.Hash) (*types.Bootstrap, error) {
resp, err := http.Get(fmt.Sprintf("%s/%s/%s", c.url, beaconLightClientBootstrap, root.String()))
if err != nil {
return nil, fmt.Errorf("failed http request: %w", err)
@@ -70,14 +71,14 @@ func (c *BeaconClient) Bootstrap(root common.Hash) (*Bootstrap, error) {
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
- var bs Bootstrap
+ var bs types.Bootstrap
if err := json.Unmarshal(b, &bs); err != nil {
return nil, fmt.Errorf("failed to unmarshal data: %v", err)
}
return &bs, nil
}
-func (c *BeaconClient) GetRangeUpdate(start, count int) ([]*LightClientUpdate, error) {
+func (c *Client) GetRangeUpdate(start, count int) ([]*types.LightClientUpdate, error) {
resp, err := http.Get(fmt.Sprintf("%s/%s?start_period=%d&count=%d", c.url, beaconLightClientUpdate, start, count))
if err != nil {
return nil, fmt.Errorf("failed http request: %w", err)
@@ -86,7 +87,7 @@ func (c *BeaconClient) GetRangeUpdate(start, count int) ([]*LightClientUpdate, e
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
- var u []*LightClientUpdate
+ var u []*types.LightClientUpdate
if err := json.Unmarshal(b, &u); err != nil {
return nil, fmt.Errorf("failed to unmarshal data: %v", err)
}
@@ -94,7 +95,7 @@ func (c *BeaconClient) GetRangeUpdate(start, count int) ([]*LightClientUpdate, e
}
-func (c *BeaconClient) GetOptimisticUpdate() (*LightClientUpdate, error) {
+func (c *Client) GetOptimisticUpdate() (*types.LightClientUpdate, error) {
resp, err := http.Get(fmt.Sprintf("%s/%s", c.url, beaconLightClientOptimisticUpdate))
if err != nil {
return nil, fmt.Errorf("failed http request: %w", err)
@@ -103,14 +104,14 @@ func (c *BeaconClient) GetOptimisticUpdate() (*LightClientUpdate, error) {
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
- var u LightClientUpdate
+ var u types.LightClientUpdate
if err := json.Unmarshal(b, &u); err != nil {
return nil, fmt.Errorf("failed to unmarshal data: %v", err)
}
return &u, nil
}
-func (c *BeaconClient) GetFinalityUpdate() (*LightClientUpdate, error) {
+func (c *Client) GetFinalityUpdate() (*types.LightClientUpdate, error) {
resp, err := http.Get(fmt.Sprintf("%s/%s", c.url, beaconLightClientFinalityUpdate))
if err != nil {
return nil, fmt.Errorf("failed http request: %w", err)
@@ -119,7 +120,7 @@ func (c *BeaconClient) GetFinalityUpdate() (*LightClientUpdate, error) {
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
- var u LightClientUpdate
+ var u types.LightClientUpdate
if err := json.Unmarshal(b, &u); err != nil {
return nil, fmt.Errorf("failed to unmarshal data: %v", err)
}
@@ -127,7 +128,7 @@ func (c *BeaconClient) GetFinalityUpdate() (*LightClientUpdate, error) {
}
-func (c *BeaconClient) GetBlock(root common.Hash) (*eth2spec.VersionedSignedBeaconBlock, error) {
+func (c *Client) GetBlock(root common.Hash) (*eth2spec.VersionedSignedBeaconBlock, error) {
provider, ok := c.client.(eth2client.SignedBeaconBlockProvider)
if !ok {
return nil, fmt.Errorf("beacon server does not support retrieving blocks")
diff --git a/cmd/blsync/chain.go b/beacon/light/chain.go
similarity index 54%
rename from cmd/blsync/chain.go
rename to beacon/light/chain.go
index a1b3ad549d..d108184355 100644
--- a/cmd/blsync/chain.go
+++ b/beacon/light/chain.go
@@ -1,4 +1,4 @@
-// Copyright 2023 The go-ethereum Authors
+// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
@@ -14,16 +14,21 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package main
+package light
import (
"context"
"fmt"
+ "math/big"
"time"
+ eth2spec "github.com/attestantio/go-eth2-client/spec"
+ "github.com/ethereum/go-ethereum/beacon/beaclient"
"github.com/ethereum/go-ethereum/beacon/engine"
+ "github.com/ethereum/go-ethereum/beacon/params"
"github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common"
+ ctypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
)
@@ -31,16 +36,16 @@ import (
// LightClient tracks the head of the chain using the light client protocol,
// which assumes the majority of beacon chain sync committe is honest.
type LightClient struct {
- beacon *BeaconClient
+ beacon *beaclient.Client
store *store
chainHeadFeed event.Feed
}
-// bootstrap retrieves a light client bootstrap and authenticates it against the
+// Bootstrap retrieves a light client bootstrap and authenticates it against the
// provided trusted root.
-func bootstrap(ctx context.Context, server string, root common.Hash) (*LightClient, error) {
- api, err := NewBeaconClient(ctx, server)
+func Bootstrap(ctx context.Context, server string, root common.Hash) (*LightClient, error) {
+ api, err := beaclient.NewClient(ctx, server)
if err != nil {
return nil, fmt.Errorf("failed to connect to beacon server: %w", err)
}
@@ -58,7 +63,7 @@ func bootstrap(ctx context.Context, server string, root common.Hash) (*LightClie
return &LightClient{
beacon: api,
store: &store{
- config: SepoliaChainConfig,
+ config: params.SepoliaChainConfig,
current: current,
optimistic: &bs.Header.Header,
finalized: &bs.Header.Header,
@@ -88,7 +93,7 @@ func (c *LightClient) Finalized() *types.Header {
func (c *LightClient) Start() {
log.Info("beacon light client starting")
var (
- ticker = time.NewTicker(SecondsPerSlot * time.Second)
+ ticker = time.NewTicker(params.SlotLength * time.Second)
lastFinality = time.Now()
)
for ; ; <-ticker.C {
@@ -108,7 +113,7 @@ func (c *LightClient) Start() {
}
var (
- update *LightClientUpdate
+ update *types.LightClientUpdate
err error
)
if time.Since(lastFinality) > time.Minute*5 {
@@ -159,3 +164,114 @@ func (c *LightClient) getExecutableData(head common.Hash) (*engine.ExecutableDat
}
return versionedBlockToExecutableData(block), nil
}
+
+// versionedBlockToExecutableData parses versioned blocks and returns a generic
+// execution payload object.
+func versionedBlockToExecutableData(block *eth2spec.VersionedSignedBeaconBlock) *engine.ExecutableData {
+ var ep *engine.ExecutableData
+ switch block.Version {
+ case eth2spec.DataVersionPhase0:
+ panic("phase0 block has no execution payload to send")
+ case eth2spec.DataVersionAltair:
+ panic("altair block has no execution payload to send")
+ case eth2spec.DataVersionBellatrix:
+ p := block.Bellatrix.Message.Body.ExecutionPayload
+ ep = &engine.ExecutableData{
+ ParentHash: common.Hash(p.ParentHash),
+ FeeRecipient: common.Address(p.FeeRecipient),
+ StateRoot: p.StateRoot,
+ ReceiptsRoot: p.ReceiptsRoot,
+ LogsBloom: p.LogsBloom[:],
+ Random: p.PrevRandao,
+ Number: p.BlockNumber,
+ GasLimit: p.GasLimit,
+ GasUsed: p.GasUsed,
+ Timestamp: p.Timestamp,
+ ExtraData: p.ExtraData,
+ BaseFeePerGas: new(big.Int).SetBytes(reverse(p.BaseFeePerGas[:])),
+ BlockHash: common.Hash(p.BlockHash),
+ Transactions: [][]byte{},
+ Withdrawals: nil,
+ BlobGasUsed: nil,
+ ExcessBlobGas: nil,
+ }
+ for _, tx := range p.Transactions {
+ ep.Transactions = append(ep.Transactions, tx)
+ }
+ case eth2spec.DataVersionCapella:
+ p := block.Capella.Message.Body.ExecutionPayload
+ ep = &engine.ExecutableData{
+ ParentHash: common.Hash(p.ParentHash),
+ FeeRecipient: common.Address(p.FeeRecipient),
+ StateRoot: p.StateRoot,
+ ReceiptsRoot: p.ReceiptsRoot,
+ LogsBloom: p.LogsBloom[:],
+ Random: p.PrevRandao,
+ Number: p.BlockNumber,
+ GasLimit: p.GasLimit,
+ GasUsed: p.GasUsed,
+ Timestamp: p.Timestamp,
+ ExtraData: p.ExtraData,
+ BaseFeePerGas: new(big.Int).SetBytes(reverse(p.BaseFeePerGas[:])),
+ BlockHash: common.Hash(p.BlockHash),
+ Transactions: [][]byte{},
+ Withdrawals: nil,
+ BlobGasUsed: nil,
+ ExcessBlobGas: nil,
+ }
+ for _, tx := range p.Transactions {
+ ep.Transactions = append(ep.Transactions, tx)
+ }
+ for _, wx := range p.Withdrawals {
+ ep.Withdrawals = append(ep.Withdrawals, &ctypes.Withdrawal{
+ Index: uint64(wx.Index),
+ Validator: uint64(wx.ValidatorIndex),
+ Address: common.Address(wx.Address),
+ Amount: uint64(wx.Amount),
+ })
+ }
+ case eth2spec.DataVersionDeneb:
+ p := block.Deneb.Message.Body.ExecutionPayload
+ ep = &engine.ExecutableData{
+ ParentHash: common.Hash(p.ParentHash),
+ FeeRecipient: common.Address(p.FeeRecipient),
+ StateRoot: common.Hash(p.StateRoot),
+ ReceiptsRoot: common.Hash(p.ReceiptsRoot),
+ LogsBloom: p.LogsBloom[:],
+ Random: p.PrevRandao,
+ Number: p.BlockNumber,
+ GasLimit: p.GasLimit,
+ GasUsed: p.GasUsed,
+ Timestamp: p.Timestamp,
+ ExtraData: p.ExtraData,
+ BaseFeePerGas: nil, // TODO: convert this []uint64 correctly to big.Int
+ BlockHash: common.Hash(p.BlockHash),
+ Transactions: [][]byte{},
+ Withdrawals: nil,
+ BlobGasUsed: &p.BlobGasUsed,
+ ExcessBlobGas: &p.ExcessBlobGas,
+ }
+ for _, tx := range p.Transactions {
+ ep.Transactions = append(ep.Transactions, tx)
+ }
+ for _, wx := range p.Withdrawals {
+ ep.Withdrawals = append(ep.Withdrawals, &ctypes.Withdrawal{
+ Index: uint64(wx.Index),
+ Validator: uint64(wx.ValidatorIndex),
+ Address: common.Address(wx.Address),
+ Amount: uint64(wx.Amount),
+ })
+ }
+ default:
+ panic("unknown beacon block version")
+ }
+ return ep
+}
+
+func reverse(b []byte) []byte {
+ for i := 0; i < len(b)/2; i++ {
+ j := len(b) - i - 1
+ b[i], b[j] = b[j], b[i]
+ }
+ return b
+}
diff --git a/cmd/blsync/chain_test.go b/beacon/light/chain_test.go
similarity index 99%
rename from cmd/blsync/chain_test.go
rename to beacon/light/chain_test.go
index 9cc52d40fe..8bee55343e 100644
--- a/cmd/blsync/chain_test.go
+++ b/beacon/light/chain_test.go
@@ -1,4 +1,4 @@
-// Copyright 2023 The go-ethereum Authors
+// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package main
+package light
import (
"bytes"
diff --git a/cmd/blsync/store.go b/beacon/light/store.go
similarity index 81%
rename from cmd/blsync/store.go
rename to beacon/light/store.go
index 6041b33649..3775f4d0fe 100644
--- a/cmd/blsync/store.go
+++ b/beacon/light/store.go
@@ -14,23 +14,35 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package main
+package light
import (
+ "crypto/sha256"
+ "errors"
"fmt"
"github.com/ethereum/go-ethereum/beacon/merkle"
"github.com/ethereum/go-ethereum/beacon/params"
"github.com/ethereum/go-ethereum/beacon/types"
+ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
+var (
+ errNotEnoughParticipants = errors.New("not enough sync committee participants")
+ errWrongPeriod = errors.New("update not from active period")
+ errUselessUpdate = errors.New("useless update")
+ errInvalidFinalityBranch = errors.New("invalid finality branch")
+ errInvalidNextSyncCommitteeBranch = errors.New("invalid next sync committee branch")
+ errInvalidSyncCommitteeSignature = errors.New("invalid sync committee signature")
+)
+
// store implements the light client state machine LightClientStore from the
// light client specification.
//
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientstore
type store struct {
- config *ChainConfig
+ config *params.ChainConfig
finalized *types.Header
optimistic *types.Header
@@ -38,7 +50,7 @@ type store struct {
current *types.SyncCommittee
next *types.SyncCommittee
- best *LightClientUpdate
+ best *types.LightClientUpdate
prevActive uint64
currActive uint64
@@ -49,8 +61,8 @@ func (s *store) copy() *store {
return shallow
}
-func (s *store) validate(update *LightClientUpdate) error {
- if update.SyncAggregate.SignerCount() <= MinSyncCommitteeParticipants {
+func (s *store) validate(update *types.LightClientUpdate) error {
+ if update.SyncAggregate.SignerCount() <= params.SyncCommitteeMinParticipants {
return errNotEnoughParticipants
}
var (
@@ -93,7 +105,7 @@ func (s *store) validate(update *LightClientUpdate) error {
// Validate sync committee signature.
var (
- domain = s.config.Domain(SyncCommitteeDomain, update.SignatureSlot)
+ domain = s.config.Domain(params.SyncCommitteeDomain, update.SignatureSlot)
signingRoot = computeSigningRoot(update.AttestedHeader.Hash(), domain)
)
committee := s.current
@@ -115,7 +127,7 @@ func (s *store) finalizedPeriod() int {
return int(types.SyncPeriod(s.finalized.Slot))
}
-func (s *store) Insert(update *LightClientUpdate) error {
+func (s *store) Insert(update *types.LightClientUpdate) error {
if err := s.validate(update); err != nil {
return err
}
@@ -181,3 +193,18 @@ func max(x, y uint64) uint64 {
}
return x
}
+
+func computeSigningRoot(root, domain common.Hash) common.Hash {
+ return hash(root.Bytes(), domain.Bytes())
+}
+
+func hash(left, right []byte) common.Hash {
+ var (
+ hasher = sha256.New()
+ sum common.Hash
+ )
+ hasher.Write(left)
+ hasher.Write(right)
+ hasher.Sum(sum[:0])
+ return sum
+}
diff --git a/cmd/blsync/config.go b/beacon/params/config.go
similarity index 80%
rename from cmd/blsync/config.go
rename to beacon/params/config.go
index a6a8a989d9..27df065b72 100644
--- a/cmd/blsync/config.go
+++ b/beacon/params/config.go
@@ -1,4 +1,4 @@
-// Copyright 2023 The go-ethereum Authors
+// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
@@ -14,9 +14,11 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package main
+package params
import (
+ "crypto/sha256"
+
"github.com/ethereum/go-ethereum/common"
)
@@ -51,7 +53,7 @@ type ChainConfig struct {
// Version returns the active version for a given slot.
func (c *ChainConfig) Version(slot uint64) []byte {
- epoch := slot / SlotsPerEpoch
+ epoch := slot / SlotLength
switch {
case c.Capella.Epoch <= epoch:
return c.Capella.Version
@@ -74,3 +76,21 @@ func (c *ChainConfig) Domain(typ []byte, slot uint64) common.Hash {
copy(domain[4:], forkData[0:28])
return domain
}
+
+// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_fork_data_root
+func computeForkDataRoot(version []byte, genesisValidatorsRoot common.Hash) common.Hash {
+ var padded common.Hash
+ copy(padded[:], version)
+ return hash(padded.Bytes(), genesisValidatorsRoot.Bytes())
+}
+
+func hash(left, right []byte) common.Hash {
+ var (
+ hasher = sha256.New()
+ sum common.Hash
+ )
+ hasher.Write(left)
+ hasher.Write(right)
+ hasher.Sum(sum[:0])
+ return sum
+}
diff --git a/beacon/params/params.go b/beacon/params/params.go
index ee9feb1acb..916c082c30 100644
--- a/beacon/params/params.go
+++ b/beacon/params/params.go
@@ -17,15 +17,18 @@
package params
const (
+ SlotLength = 12
EpochLength = 32
SyncPeriodLength = 8192
BLSSignatureSize = 96
BLSPubkeySize = 48
- SyncCommitteeSize = 512
- SyncCommitteeBitmaskSize = SyncCommitteeSize / 8
- SyncCommitteeSupermajority = (SyncCommitteeSize*2 + 2) / 3
+ SyncCommitteeSize = 512
+ SyncCommitteeBitmaskSize = SyncCommitteeSize / 8
+ SyncCommitteeSupermajority = (SyncCommitteeSize*2 + 2) / 3
+ SyncCommitteeMinParticipants = 1
+ SyncCommitteeEpochsPerPeriod = 256
)
const (
diff --git a/beacon/types/light_client.go b/beacon/types/light_client.go
new file mode 100644
index 0000000000..b251927e5b
--- /dev/null
+++ b/beacon/types/light_client.go
@@ -0,0 +1,190 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package types
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/beacon/merkle"
+ "github.com/ethereum/go-ethereum/beacon/params"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// LightClientHeader is a wrapper around a beacon header with special, nested
+// marshalling.
+type LightClientHeader struct {
+ Header
+}
+
+type lightClientHeaderMarshaling struct {
+ Beacon Header `json:"beacon"`
+}
+
+// MarshalJSON marshals as JSON.
+func (h LightClientHeader) MarshalJSON() ([]byte, error) {
+ var enc lightClientHeaderMarshaling
+ enc.Beacon = h.Header
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (h *LightClientHeader) UnmarshalJSON(input []byte) error {
+ var dec lightClientHeaderMarshaling
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ *h = LightClientHeader{dec.Beacon}
+ return nil
+}
+
+// Boostrap is response to the bootstap endpoint in the beacon API.
+type Bootstrap struct {
+ Header LightClientHeader `json:"header"`
+ Committee *SerializedSyncCommittee `json:"current_sync_committee"`
+ CommitteeBranch merkle.Values `json:"current_sync_committee_branch"`
+}
+
+// Valid verifies the current committee root is correctly encoded in the beacon
+// state of the weak-subjectivity checkpoint.
+func (b *Bootstrap) Valid() error {
+ root := merkle.Value(b.Committee.Root())
+ if err := merkle.VerifyProof(b.Header.StateRoot, params.StateIndexSyncCommittee, b.CommitteeBranch, root); err != nil {
+ return err
+ }
+ return nil
+}
+
+type bootstrapMarshaling struct {
+ Data struct {
+ Header LightClientHeader `json:"header"`
+ Committee *SerializedSyncCommittee `json:"current_sync_committee"`
+ CommitteeBranch merkle.Values `json:"current_sync_committee_branch"`
+ } `json:"data"`
+}
+
+// MarshalJSON marshals as JSON.
+func (b Bootstrap) MarshalJSON() ([]byte, error) {
+ var enc bootstrapMarshaling
+ enc.Data.Header = b.Header
+ enc.Data.Committee = b.Committee
+ enc.Data.CommitteeBranch = b.CommitteeBranch
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (b *Bootstrap) UnmarshalJSON(input []byte) error {
+ var dec bootstrapMarshaling
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ b.Header = dec.Data.Header
+ b.Committee = dec.Data.Committee
+ b.CommitteeBranch = dec.Data.CommitteeBranch
+ return nil
+}
+
+// LightClientUpdate represents the possible light client updates the beacon api
+// may respond with.
+type LightClientUpdate struct {
+ AttestedHeader LightClientHeader // Arbitrary header out of the period signed by the sync committee
+ SyncAggregate SyncAggregate // BLS aggregate signature from sync committee
+ SignatureSlot uint64 // Slot at which the signature is computed
+ NextSyncCommittee *SerializedSyncCommittee // Sync committee of the next period advertised in the current one
+ NextSyncCommitteeBranch *merkle.Values // Proof for the next period's sync committee
+ FinalizedHeader *LightClientHeader // Optional header to announce a point of finality
+ FinalityBranch *merkle.Values // Proof for the announced finality
+}
+
+type lightClientUpdateMarshaling struct {
+ Data struct {
+ AttestedHeader LightClientHeader `json:"attested_header"`
+ SyncAggregate SyncAggregate `json:"sync_aggregate"`
+ SignatureSlot common.Decimal `json:"signature_slot"`
+ NextSyncCommittee *SerializedSyncCommittee `json:"next_sync_committee"`
+ NextSyncCommitteeBranch *merkle.Values `json:"next_sync_committee_branch"`
+ FinalizedHeader *LightClientHeader `json:"finalized_header"`
+ FinalityBranch *merkle.Values `json:"finality_branch"`
+ } `json:"data"`
+}
+
+// MarshalJSON marshals to JSON.
+func (u LightClientUpdate) MarshalJSON() ([]byte, error) {
+ var enc lightClientUpdateMarshaling
+ enc.Data.AttestedHeader = u.AttestedHeader
+ enc.Data.SyncAggregate = u.SyncAggregate
+ enc.Data.SignatureSlot = common.Decimal(u.SignatureSlot)
+ enc.Data.NextSyncCommittee = u.NextSyncCommittee
+ enc.Data.NextSyncCommitteeBranch = u.NextSyncCommitteeBranch
+ enc.Data.FinalizedHeader = u.FinalizedHeader
+ enc.Data.FinalityBranch = u.FinalityBranch
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (u *LightClientUpdate) UnmarshalJSON(input []byte) error {
+ var dec lightClientUpdateMarshaling
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ u.AttestedHeader = dec.Data.AttestedHeader
+ u.SyncAggregate = dec.Data.SyncAggregate
+ u.SignatureSlot = uint64(dec.Data.SignatureSlot)
+ u.NextSyncCommittee = dec.Data.NextSyncCommittee
+ u.NextSyncCommitteeBranch = dec.Data.NextSyncCommitteeBranch
+ u.FinalizedHeader = dec.Data.FinalizedHeader
+ u.FinalityBranch = dec.Data.FinalityBranch
+ return nil
+}
+
+// Compare will compare two light client updates and determine the better one.
+// If next is not better than curr, it will error.
+func (curr *LightClientUpdate) Compare(next *LightClientUpdate) error {
+ if curr == nil {
+ // Nothing to compare.
+ return nil
+ }
+ var (
+ maxActiveParticipants = params.SyncCommitteeSize
+ newNumActiveParticipants = next.SyncAggregate.SignerCount()
+ oldNumActiveParticipants = curr.SyncAggregate.SignerCount()
+ newHasSupermajority = newNumActiveParticipants*3 >= maxActiveParticipants*2
+ oldHasSupermajority = oldNumActiveParticipants*3 >= maxActiveParticipants*2
+ )
+ if newHasSupermajority && !oldHasSupermajority {
+ return nil
+ } else if !newHasSupermajority && oldHasSupermajority {
+ return fmt.Errorf("new update does not have supermajority while old does")
+ }
+ if !newHasSupermajority && newNumActiveParticipants > oldNumActiveParticipants {
+ return nil
+ } else if !newHasSupermajority && newNumActiveParticipants <= oldNumActiveParticipants {
+ return fmt.Errorf("more active participants in old update")
+ }
+
+ // TODO: implement all tie breakers from spec
+ // var (
+ // sigPeriod = slotToSyncCommitteePeriod(next.SignatureSlot)
+ // newHasRelevantSyncCommitteeUpdate = next.NextSyncCommittee != nil && (slotToSyncCommitteePeriod(next.AttestedHeader.Slot) == sigPeriod)
+ // oldHasRelevantSyncCommitteeUpdate = curr.NextSyncCommittee != nil && (slotToSyncCommitteePeriod(curr.AttestedHeader.Slot) == sigPeriod)
+ // )
+ // if !newHasRelevantSyncCommitteeUpdate && oldHasRelevantSyncCommitteeUpdate {
+ // return fmt.Errorf("old update also includes sync committee update")
+ // }
+
+ return nil
+}
diff --git a/cmd/blsync/beacon.go b/cmd/blsync/beacon.go
deleted file mode 100644
index a7a73f45a0..0000000000
--- a/cmd/blsync/beacon.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package main
-
-import (
- "crypto/sha256"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-const (
- MinSyncCommitteeParticipants = 1
- SecondsPerSlot = 12
- SlotsPerEpoch = 32
- EpochsPerSyncCommitteePeriod = 256
-)
-
-// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_fork_data_root
-func computeForkDataRoot(version []byte, genesisValidatorsRoot common.Hash) common.Hash {
- var padded common.Hash
- copy(padded[:], version)
- return hash(padded.Bytes(), genesisValidatorsRoot.Bytes())
-}
-
-func computeSigningRoot(root, domain common.Hash) common.Hash {
- return hash(root.Bytes(), domain.Bytes())
-}
-
-func hash(left, right []byte) common.Hash {
- var (
- hasher = sha256.New()
- sum common.Hash
- )
- hasher.Write(left)
- hasher.Write(right)
- hasher.Sum(sum[:0])
- return sum
-}
diff --git a/cmd/blsync/errors.go b/cmd/blsync/errors.go
deleted file mode 100644
index 35fd0494e0..0000000000
--- a/cmd/blsync/errors.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package main
-
-import "errors"
-
-var (
- errNotEnoughParticipants = errors.New("not enough sync committee participants")
- errWrongPeriod = errors.New("update not from active period")
- errUselessUpdate = errors.New("useless update")
- errInvalidFinalityBranch = errors.New("invalid finality branch")
- errInvalidNextSyncCommitteeBranch = errors.New("invalid next sync committee branch")
- errInvalidSyncCommitteeSignature = errors.New("invalid sync committee signature")
-)
diff --git a/cmd/blsync/main.go b/cmd/blsync/main.go
index e24db35074..4e4fefe206 100644
--- a/cmd/blsync/main.go
+++ b/cmd/blsync/main.go
@@ -24,6 +24,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/beacon/engine"
+ "github.com/ethereum/go-ethereum/beacon/light"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/flags"
@@ -99,13 +100,13 @@ func run(ctx *cli.Context) error {
engine = makeRPCClient(ctx)
server = ctx.String(LightClientServerFlag.Name)
)
- chain, err := bootstrap(context.Background(), server, common.HexToHash(root))
+ chain, err := light.Bootstrap(context.Background(), server, common.HexToHash(root))
if err != nil {
return fmt.Errorf("failed to bootstrap: %v", err)
}
go chain.Start()
- headCh := make(chan ChainHeadEvent)
+ headCh := make(chan light.ChainHeadEvent)
chain.SubscribeChainHeadEvent(headCh)
// Send new head events to engine api.
diff --git a/cmd/blsync/types.go b/cmd/blsync/types.go
deleted file mode 100644
index 5e9a828981..0000000000
--- a/cmd/blsync/types.go
+++ /dev/null
@@ -1,306 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package main
-
-import (
- "encoding/json"
- "fmt"
- "math/big"
-
- eth2spec "github.com/attestantio/go-eth2-client/spec"
- "github.com/ethereum/go-ethereum/beacon/engine"
- "github.com/ethereum/go-ethereum/beacon/merkle"
- "github.com/ethereum/go-ethereum/beacon/params"
- "github.com/ethereum/go-ethereum/beacon/types"
- "github.com/ethereum/go-ethereum/common"
- ctypes "github.com/ethereum/go-ethereum/core/types"
-)
-
-// LightClientHeader is a wrapper around a beacon header with special, nested
-// marshalling.
-type LightClientHeader struct {
- types.Header
-}
-
-type lightClientHeaderMarshaling struct {
- Beacon types.Header `json:"beacon"`
-}
-
-// MarshalJSON marshals as JSON.
-func (h LightClientHeader) MarshalJSON() ([]byte, error) {
- var enc lightClientHeaderMarshaling
- enc.Beacon = h.Header
- return json.Marshal(&enc)
-}
-
-// UnmarshalJSON unmarshals from JSON.
-func (h *LightClientHeader) UnmarshalJSON(input []byte) error {
- var dec lightClientHeaderMarshaling
- if err := json.Unmarshal(input, &dec); err != nil {
- return err
- }
- *h = LightClientHeader{dec.Beacon}
- return nil
-}
-
-// Boostrap is response to the bootstap endpoint in the beacon API.
-type Bootstrap struct {
- Header LightClientHeader `json:"header"`
- Committee *types.SerializedSyncCommittee `json:"current_sync_committee"`
- CommitteeBranch merkle.Values `json:"current_sync_committee_branch"`
-}
-
-// Valid verifies the current committee root is correctly encoded in the beacon
-// state of the weak-subjectivity checkpoint.
-func (b *Bootstrap) Valid() error {
- root := merkle.Value(b.Committee.Root())
- if err := merkle.VerifyProof(b.Header.StateRoot, params.StateIndexSyncCommittee, b.CommitteeBranch, root); err != nil {
- return err
- }
- return nil
-}
-
-type bootstrapMarshaling struct {
- Data struct {
- Header LightClientHeader `json:"header"`
- Committee *types.SerializedSyncCommittee `json:"current_sync_committee"`
- CommitteeBranch merkle.Values `json:"current_sync_committee_branch"`
- } `json:"data"`
-}
-
-// MarshalJSON marshals as JSON.
-func (b Bootstrap) MarshalJSON() ([]byte, error) {
- var enc bootstrapMarshaling
- enc.Data.Header = b.Header
- enc.Data.Committee = b.Committee
- enc.Data.CommitteeBranch = b.CommitteeBranch
- return json.Marshal(&enc)
-}
-
-// UnmarshalJSON unmarshals from JSON.
-func (b *Bootstrap) UnmarshalJSON(input []byte) error {
- var dec bootstrapMarshaling
- if err := json.Unmarshal(input, &dec); err != nil {
- return err
- }
- b.Header = dec.Data.Header
- b.Committee = dec.Data.Committee
- b.CommitteeBranch = dec.Data.CommitteeBranch
- return nil
-}
-
-// LightClientUpdate represents the possible light client updates the beacon api
-// may respond with.
-type LightClientUpdate struct {
- AttestedHeader LightClientHeader // Arbitrary header out of the period signed by the sync committee
- SyncAggregate types.SyncAggregate // BLS aggregate signature from sync committee
- SignatureSlot uint64 // Slot at which the signature is computed
- NextSyncCommittee *types.SerializedSyncCommittee // Sync committee of the next period advertised in the current one
- NextSyncCommitteeBranch *merkle.Values // Proof for the next period's sync committee
- FinalizedHeader *LightClientHeader // Optional header to announce a point of finality
- FinalityBranch *merkle.Values // Proof for the announced finality
-}
-
-type lightClientUpdateMarshaling struct {
- Data struct {
- AttestedHeader LightClientHeader `json:"attested_header"`
- SyncAggregate types.SyncAggregate `json:"sync_aggregate"`
- SignatureSlot common.Decimal `json:"signature_slot"`
- NextSyncCommittee *types.SerializedSyncCommittee `json:"next_sync_committee"`
- NextSyncCommitteeBranch *merkle.Values `json:"next_sync_committee_branch"`
- FinalizedHeader *LightClientHeader `json:"finalized_header"`
- FinalityBranch *merkle.Values `json:"finality_branch"`
- } `json:"data"`
-}
-
-// MarshalJSON marshals to JSON.
-func (u LightClientUpdate) MarshalJSON() ([]byte, error) {
- var enc lightClientUpdateMarshaling
- enc.Data.AttestedHeader = u.AttestedHeader
- enc.Data.SyncAggregate = u.SyncAggregate
- enc.Data.SignatureSlot = common.Decimal(u.SignatureSlot)
- enc.Data.NextSyncCommittee = u.NextSyncCommittee
- enc.Data.NextSyncCommitteeBranch = u.NextSyncCommitteeBranch
- enc.Data.FinalizedHeader = u.FinalizedHeader
- enc.Data.FinalityBranch = u.FinalityBranch
- return json.Marshal(&enc)
-}
-
-// UnmarshalJSON unmarshals from JSON.
-func (u *LightClientUpdate) UnmarshalJSON(input []byte) error {
- var dec lightClientUpdateMarshaling
- if err := json.Unmarshal(input, &dec); err != nil {
- return err
- }
- u.AttestedHeader = dec.Data.AttestedHeader
- u.SyncAggregate = dec.Data.SyncAggregate
- u.SignatureSlot = uint64(dec.Data.SignatureSlot)
- u.NextSyncCommittee = dec.Data.NextSyncCommittee
- u.NextSyncCommitteeBranch = dec.Data.NextSyncCommitteeBranch
- u.FinalizedHeader = dec.Data.FinalizedHeader
- u.FinalityBranch = dec.Data.FinalityBranch
- return nil
-}
-
-// Compare will compare two light client updates and determine the better one.
-// If next is not better than curr, it will error.
-func (curr *LightClientUpdate) Compare(next *LightClientUpdate) error {
- if curr == nil {
- // Nothing to compare.
- return nil
- }
- var (
- maxActiveParticipants = params.SyncCommitteeSize
- newNumActiveParticipants = next.SyncAggregate.SignerCount()
- oldNumActiveParticipants = curr.SyncAggregate.SignerCount()
- newHasSupermajority = newNumActiveParticipants*3 >= maxActiveParticipants*2
- oldHasSupermajority = oldNumActiveParticipants*3 >= maxActiveParticipants*2
- )
- if newHasSupermajority && !oldHasSupermajority {
- return nil
- } else if !newHasSupermajority && oldHasSupermajority {
- return fmt.Errorf("new update does not have supermajority while old does")
- }
- if !newHasSupermajority && newNumActiveParticipants > oldNumActiveParticipants {
- return nil
- } else if !newHasSupermajority && newNumActiveParticipants <= oldNumActiveParticipants {
- return fmt.Errorf("more active participants in old update")
- }
-
- // TODO: implement all tie breakers from spec
- // var (
- // sigPeriod = slotToSyncCommitteePeriod(next.SignatureSlot)
- // newHasRelevantSyncCommitteeUpdate = next.NextSyncCommittee != nil && (slotToSyncCommitteePeriod(next.AttestedHeader.Slot) == sigPeriod)
- // oldHasRelevantSyncCommitteeUpdate = curr.NextSyncCommittee != nil && (slotToSyncCommitteePeriod(curr.AttestedHeader.Slot) == sigPeriod)
- // )
- // if !newHasRelevantSyncCommitteeUpdate && oldHasRelevantSyncCommitteeUpdate {
- // return fmt.Errorf("old update also includes sync committee update")
- // }
-
- return nil
-}
-
-// versionedBlockToExecutableData parses versioned blocks and returns a generic
-// execution payload object.
-func versionedBlockToExecutableData(block *eth2spec.VersionedSignedBeaconBlock) *engine.ExecutableData {
- var ep *engine.ExecutableData
- switch block.Version {
- case eth2spec.DataVersionPhase0:
- panic("phase0 block has no execution payload to send")
- case eth2spec.DataVersionAltair:
- panic("altair block has no execution payload to send")
- case eth2spec.DataVersionBellatrix:
- p := block.Bellatrix.Message.Body.ExecutionPayload
- ep = &engine.ExecutableData{
- ParentHash: common.Hash(p.ParentHash),
- FeeRecipient: common.Address(p.FeeRecipient),
- StateRoot: p.StateRoot,
- ReceiptsRoot: p.ReceiptsRoot,
- LogsBloom: p.LogsBloom[:],
- Random: p.PrevRandao,
- Number: p.BlockNumber,
- GasLimit: p.GasLimit,
- GasUsed: p.GasUsed,
- Timestamp: p.Timestamp,
- ExtraData: p.ExtraData,
- BaseFeePerGas: new(big.Int).SetBytes(reverse(p.BaseFeePerGas[:])),
- BlockHash: common.Hash(p.BlockHash),
- Transactions: [][]byte{},
- Withdrawals: nil,
- BlobGasUsed: nil,
- ExcessBlobGas: nil,
- }
- for _, tx := range p.Transactions {
- ep.Transactions = append(ep.Transactions, tx)
- }
- case eth2spec.DataVersionCapella:
- p := block.Capella.Message.Body.ExecutionPayload
- ep = &engine.ExecutableData{
- ParentHash: common.Hash(p.ParentHash),
- FeeRecipient: common.Address(p.FeeRecipient),
- StateRoot: p.StateRoot,
- ReceiptsRoot: p.ReceiptsRoot,
- LogsBloom: p.LogsBloom[:],
- Random: p.PrevRandao,
- Number: p.BlockNumber,
- GasLimit: p.GasLimit,
- GasUsed: p.GasUsed,
- Timestamp: p.Timestamp,
- ExtraData: p.ExtraData,
- BaseFeePerGas: new(big.Int).SetBytes(reverse(p.BaseFeePerGas[:])),
- BlockHash: common.Hash(p.BlockHash),
- Transactions: [][]byte{},
- Withdrawals: nil,
- BlobGasUsed: nil,
- ExcessBlobGas: nil,
- }
- for _, tx := range p.Transactions {
- ep.Transactions = append(ep.Transactions, tx)
- }
- for _, wx := range p.Withdrawals {
- ep.Withdrawals = append(ep.Withdrawals, &ctypes.Withdrawal{
- Index: uint64(wx.Index),
- Validator: uint64(wx.ValidatorIndex),
- Address: common.Address(wx.Address),
- Amount: uint64(wx.Amount),
- })
- }
- case eth2spec.DataVersionDeneb:
- p := block.Deneb.Message.Body.ExecutionPayload
- ep = &engine.ExecutableData{
- ParentHash: common.Hash(p.ParentHash),
- FeeRecipient: common.Address(p.FeeRecipient),
- StateRoot: common.Hash(p.StateRoot),
- ReceiptsRoot: common.Hash(p.ReceiptsRoot),
- LogsBloom: p.LogsBloom[:],
- Random: p.PrevRandao,
- Number: p.BlockNumber,
- GasLimit: p.GasLimit,
- GasUsed: p.GasUsed,
- Timestamp: p.Timestamp,
- ExtraData: p.ExtraData,
- BaseFeePerGas: nil, // TODO: convert this []uint64 correctly to big.Int
- BlockHash: common.Hash(p.BlockHash),
- Transactions: [][]byte{},
- Withdrawals: nil,
- BlobGasUsed: &p.BlobGasUsed,
- ExcessBlobGas: &p.ExcessBlobGas,
- }
- for _, tx := range p.Transactions {
- ep.Transactions = append(ep.Transactions, tx)
- }
- for _, wx := range p.Withdrawals {
- ep.Withdrawals = append(ep.Withdrawals, &ctypes.Withdrawal{
- Index: uint64(wx.Index),
- Validator: uint64(wx.ValidatorIndex),
- Address: common.Address(wx.Address),
- Amount: uint64(wx.Amount),
- })
- }
- default:
- panic("unknown beacon block version")
- }
- return ep
-}
-
-func reverse(b []byte) []byte {
- for i := 0; i < len(b)/2; i++ {
- j := len(b) - i - 1
- b[i], b[j] = b[j], b[i]
- }
- return b
-}
diff --git a/go.mod b/go.mod
index ff02a3b1d9..57afd4dc92 100644
--- a/go.mod
+++ b/go.mod
@@ -72,7 +72,6 @@ require (
golang.org/x/time v0.3.0
golang.org/x/tools v0.15.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
- gopkg.in/yaml.v3 v3.0.1
)
require (
@@ -154,5 +153,6 @@ require (
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)