cmd/blsync,beacon: move blsync code into beacon

This commit is contained in:
lightclient 2024-01-04 14:26:07 -07:00
parent e151f4a93d
commit ca0feab9d3
No known key found for this signature in database
GPG key ID: 75C916AFEE20183E
12 changed files with 398 additions and 426 deletions

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package main package beaclient
import ( import (
"context" "context"
@ -27,6 +27,7 @@ import (
eth2api "github.com/attestantio/go-eth2-client/api" eth2api "github.com/attestantio/go-eth2-client/api"
eth2http "github.com/attestantio/go-eth2-client/http" eth2http "github.com/attestantio/go-eth2-client/http"
eth2spec "github.com/attestantio/go-eth2-client/spec" eth2spec "github.com/attestantio/go-eth2-client/spec"
"github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
@ -38,13 +39,13 @@ var (
beaconLightClientFinalityUpdate = "eth/v1/beacon/light_client/finality_update" beaconLightClientFinalityUpdate = "eth/v1/beacon/light_client/finality_update"
) )
type BeaconClient struct { type Client struct {
ctx context.Context ctx context.Context
url string url string
client eth2client.Service 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( client, err := eth2http.New(
ctx, ctx,
eth2http.WithAddress(server), eth2http.WithAddress(server),
@ -54,14 +55,14 @@ func NewBeaconClient(ctx context.Context, server string) (*BeaconClient, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &BeaconClient{ return &Client{
ctx: ctx, ctx: ctx,
url: server, url: server,
client: client, client: client,
}, nil }, 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())) resp, err := http.Get(fmt.Sprintf("%s/%s/%s", c.url, beaconLightClientBootstrap, root.String()))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed http request: %w", err) 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 { if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err) 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 { if err := json.Unmarshal(b, &bs); err != nil {
return nil, fmt.Errorf("failed to unmarshal data: %v", err) return nil, fmt.Errorf("failed to unmarshal data: %v", err)
} }
return &bs, nil 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)) resp, err := http.Get(fmt.Sprintf("%s/%s?start_period=%d&count=%d", c.url, beaconLightClientUpdate, start, count))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed http request: %w", err) 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 { if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err) 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 { if err := json.Unmarshal(b, &u); err != nil {
return nil, fmt.Errorf("failed to unmarshal data: %v", err) 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)) resp, err := http.Get(fmt.Sprintf("%s/%s", c.url, beaconLightClientOptimisticUpdate))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed http request: %w", err) return nil, fmt.Errorf("failed http request: %w", err)
@ -103,14 +104,14 @@ func (c *BeaconClient) GetOptimisticUpdate() (*LightClientUpdate, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err) 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 { if err := json.Unmarshal(b, &u); err != nil {
return nil, fmt.Errorf("failed to unmarshal data: %v", err) return nil, fmt.Errorf("failed to unmarshal data: %v", err)
} }
return &u, nil 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)) resp, err := http.Get(fmt.Sprintf("%s/%s", c.url, beaconLightClientFinalityUpdate))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed http request: %w", err) return nil, fmt.Errorf("failed http request: %w", err)
@ -119,7 +120,7 @@ func (c *BeaconClient) GetFinalityUpdate() (*LightClientUpdate, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err) 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 { if err := json.Unmarshal(b, &u); err != nil {
return nil, fmt.Errorf("failed to unmarshal data: %v", err) 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) provider, ok := c.client.(eth2client.SignedBeaconBlockProvider)
if !ok { if !ok {
return nil, fmt.Errorf("beacon server does not support retrieving blocks") return nil, fmt.Errorf("beacon server does not support retrieving blocks")

View file

@ -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. // This file is part of the go-ethereum library.
// //
// The go-ethereum library is free software: you can redistribute it and/or modify // 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 // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package main package light
import ( import (
"context" "context"
"fmt" "fmt"
"math/big"
"time" "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/engine"
"github.com/ethereum/go-ethereum/beacon/params"
"github.com/ethereum/go-ethereum/beacon/types" "github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common" "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/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
@ -31,16 +36,16 @@ import (
// LightClient tracks the head of the chain using the light client protocol, // LightClient tracks the head of the chain using the light client protocol,
// which assumes the majority of beacon chain sync committe is honest. // which assumes the majority of beacon chain sync committe is honest.
type LightClient struct { type LightClient struct {
beacon *BeaconClient beacon *beaclient.Client
store *store store *store
chainHeadFeed event.Feed 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. // provided trusted root.
func bootstrap(ctx context.Context, server string, root common.Hash) (*LightClient, error) { func Bootstrap(ctx context.Context, server string, root common.Hash) (*LightClient, error) {
api, err := NewBeaconClient(ctx, server) api, err := beaclient.NewClient(ctx, server)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to connect to beacon server: %w", err) 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{ return &LightClient{
beacon: api, beacon: api,
store: &store{ store: &store{
config: SepoliaChainConfig, config: params.SepoliaChainConfig,
current: current, current: current,
optimistic: &bs.Header.Header, optimistic: &bs.Header.Header,
finalized: &bs.Header.Header, finalized: &bs.Header.Header,
@ -88,7 +93,7 @@ func (c *LightClient) Finalized() *types.Header {
func (c *LightClient) Start() { func (c *LightClient) Start() {
log.Info("beacon light client starting") log.Info("beacon light client starting")
var ( var (
ticker = time.NewTicker(SecondsPerSlot * time.Second) ticker = time.NewTicker(params.SlotLength * time.Second)
lastFinality = time.Now() lastFinality = time.Now()
) )
for ; ; <-ticker.C { for ; ; <-ticker.C {
@ -108,7 +113,7 @@ func (c *LightClient) Start() {
} }
var ( var (
update *LightClientUpdate update *types.LightClientUpdate
err error err error
) )
if time.Since(lastFinality) > time.Minute*5 { if time.Since(lastFinality) > time.Minute*5 {
@ -159,3 +164,114 @@ func (c *LightClient) getExecutableData(head common.Hash) (*engine.ExecutableDat
} }
return versionedBlockToExecutableData(block), nil 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
}

View file

@ -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. // This file is part of the go-ethereum library.
// //
// The go-ethereum library is free software: you can redistribute it and/or modify // 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 // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package main package light
import ( import (
"bytes" "bytes"

View file

@ -14,23 +14,35 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package main package light
import ( import (
"crypto/sha256"
"errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/beacon/merkle" "github.com/ethereum/go-ethereum/beacon/merkle"
"github.com/ethereum/go-ethereum/beacon/params" "github.com/ethereum/go-ethereum/beacon/params"
"github.com/ethereum/go-ethereum/beacon/types" "github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "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 // store implements the light client state machine LightClientStore from the
// light client specification. // light client specification.
// //
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientstore // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientstore
type store struct { type store struct {
config *ChainConfig config *params.ChainConfig
finalized *types.Header finalized *types.Header
optimistic *types.Header optimistic *types.Header
@ -38,7 +50,7 @@ type store struct {
current *types.SyncCommittee current *types.SyncCommittee
next *types.SyncCommittee next *types.SyncCommittee
best *LightClientUpdate best *types.LightClientUpdate
prevActive uint64 prevActive uint64
currActive uint64 currActive uint64
@ -49,8 +61,8 @@ func (s *store) copy() *store {
return shallow return shallow
} }
func (s *store) validate(update *LightClientUpdate) error { func (s *store) validate(update *types.LightClientUpdate) error {
if update.SyncAggregate.SignerCount() <= MinSyncCommitteeParticipants { if update.SyncAggregate.SignerCount() <= params.SyncCommitteeMinParticipants {
return errNotEnoughParticipants return errNotEnoughParticipants
} }
var ( var (
@ -93,7 +105,7 @@ func (s *store) validate(update *LightClientUpdate) error {
// Validate sync committee signature. // Validate sync committee signature.
var ( var (
domain = s.config.Domain(SyncCommitteeDomain, update.SignatureSlot) domain = s.config.Domain(params.SyncCommitteeDomain, update.SignatureSlot)
signingRoot = computeSigningRoot(update.AttestedHeader.Hash(), domain) signingRoot = computeSigningRoot(update.AttestedHeader.Hash(), domain)
) )
committee := s.current committee := s.current
@ -115,7 +127,7 @@ func (s *store) finalizedPeriod() int {
return int(types.SyncPeriod(s.finalized.Slot)) 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 { if err := s.validate(update); err != nil {
return err return err
} }
@ -181,3 +193,18 @@ func max(x, y uint64) uint64 {
} }
return x 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
}

View file

@ -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. // This file is part of the go-ethereum library.
// //
// The go-ethereum library is free software: you can redistribute it and/or modify // 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 // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package main package params
import ( import (
"crypto/sha256"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
@ -51,7 +53,7 @@ type ChainConfig struct {
// Version returns the active version for a given slot. // Version returns the active version for a given slot.
func (c *ChainConfig) Version(slot uint64) []byte { func (c *ChainConfig) Version(slot uint64) []byte {
epoch := slot / SlotsPerEpoch epoch := slot / SlotLength
switch { switch {
case c.Capella.Epoch <= epoch: case c.Capella.Epoch <= epoch:
return c.Capella.Version return c.Capella.Version
@ -74,3 +76,21 @@ func (c *ChainConfig) Domain(typ []byte, slot uint64) common.Hash {
copy(domain[4:], forkData[0:28]) copy(domain[4:], forkData[0:28])
return domain 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
}

View file

@ -17,6 +17,7 @@
package params package params
const ( const (
SlotLength = 12
EpochLength = 32 EpochLength = 32
SyncPeriodLength = 8192 SyncPeriodLength = 8192
@ -26,6 +27,8 @@ const (
SyncCommitteeSize = 512 SyncCommitteeSize = 512
SyncCommitteeBitmaskSize = SyncCommitteeSize / 8 SyncCommitteeBitmaskSize = SyncCommitteeSize / 8
SyncCommitteeSupermajority = (SyncCommitteeSize*2 + 2) / 3 SyncCommitteeSupermajority = (SyncCommitteeSize*2 + 2) / 3
SyncCommitteeMinParticipants = 1
SyncCommitteeEpochsPerPeriod = 256
) )
const ( const (

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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")
)

View file

@ -24,6 +24,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/beacon/engine" "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/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
@ -99,13 +100,13 @@ func run(ctx *cli.Context) error {
engine = makeRPCClient(ctx) engine = makeRPCClient(ctx)
server = ctx.String(LightClientServerFlag.Name) 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 { if err != nil {
return fmt.Errorf("failed to bootstrap: %v", err) return fmt.Errorf("failed to bootstrap: %v", err)
} }
go chain.Start() go chain.Start()
headCh := make(chan ChainHeadEvent) headCh := make(chan light.ChainHeadEvent)
chain.SubscribeChainHeadEvent(headCh) chain.SubscribeChainHeadEvent(headCh)
// Send new head events to engine api. // Send new head events to engine api.

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

2
go.mod
View file

@ -72,7 +72,6 @@ require (
golang.org/x/time v0.3.0 golang.org/x/time v0.3.0
golang.org/x/tools v0.15.0 golang.org/x/tools v0.15.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
@ -154,5 +153,6 @@ require (
google.golang.org/protobuf v1.30.0 // indirect google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/yaml.v2 v2.4.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 rsc.io/tmplfunc v0.0.3 // indirect
) )