mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
cmd/blsync,beacon: move blsync code into beacon
This commit is contained in:
parent
e151f4a93d
commit
ca0feab9d3
12 changed files with 398 additions and 426 deletions
|
|
@ -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")
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
190
beacon/types/light_client.go
Normal file
190
beacon/types/light_client.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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")
|
|
||||||
)
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
2
go.mod
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue