From ca0feab9d3578055b852a5e4032ca9a28dc49e9c Mon Sep 17 00:00:00 2001 From: lightclient Date: Thu, 4 Jan 2024 14:26:07 -0700 Subject: [PATCH] cmd/blsync,beacon: move blsync code into beacon --- .../api.go => beacon/beaclient/client.go | 27 +- {cmd/blsync => beacon/light}/chain.go | 134 +++++++- {cmd/blsync => beacon/light}/chain_test.go | 4 +- {cmd/blsync => beacon/light}/store.go | 41 ++- {cmd/blsync => beacon/params}/config.go | 26 +- beacon/params/params.go | 9 +- beacon/types/light_client.go | 190 +++++++++++ cmd/blsync/beacon.go | 52 --- cmd/blsync/errors.go | 28 -- cmd/blsync/main.go | 5 +- cmd/blsync/types.go | 306 ------------------ go.mod | 2 +- 12 files changed, 398 insertions(+), 426 deletions(-) rename cmd/blsync/api.go => beacon/beaclient/client.go (85%) rename {cmd/blsync => beacon/light}/chain.go (54%) rename {cmd/blsync => beacon/light}/chain_test.go (99%) rename {cmd/blsync => beacon/light}/store.go (81%) rename {cmd/blsync => beacon/params}/config.go (80%) create mode 100644 beacon/types/light_client.go delete mode 100644 cmd/blsync/beacon.go delete mode 100644 cmd/blsync/errors.go delete mode 100644 cmd/blsync/types.go 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 )