forked from cerc-io/plugeth
beacon/types: add beacon chain data types (#27292)
* beacon/types: add beacon chain data types * beacon/merkle: added comments * go.mod: cleanups --------- Co-authored-by: Péter Szilágyi <peterke@gmail.com>
This commit is contained in:
co-authored by
Péter Szilágyi
parent
41fafa47b6
commit
c08dc59aad
@@ -0,0 +1,214 @@
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
bls "github.com/protolambda/bls12-381-util"
|
||||
)
|
||||
|
||||
// SerializedSyncCommitteeSize is the size of the sync committee plus the
|
||||
// aggregate public key.
|
||||
const SerializedSyncCommitteeSize = (params.SyncCommitteeSize + 1) * params.BLSPubkeySize
|
||||
|
||||
// SerializedSyncCommittee is the serialized version of a sync committee
|
||||
// plus the aggregate public key.
|
||||
type SerializedSyncCommittee [SerializedSyncCommitteeSize]byte
|
||||
|
||||
// jsonSyncCommittee is the JSON representation of a sync committee.
|
||||
//
|
||||
// See data structure definition here:
|
||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate
|
||||
type jsonSyncCommittee struct {
|
||||
Pubkeys []hexutil.Bytes `json:"pubkeys"`
|
||||
Aggregate hexutil.Bytes `json:"aggregate_pubkey"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (s *SerializedSyncCommittee) MarshalJSON() ([]byte, error) {
|
||||
sc := jsonSyncCommittee{Pubkeys: make([]hexutil.Bytes, params.SyncCommitteeSize)}
|
||||
for i := range sc.Pubkeys {
|
||||
sc.Pubkeys[i] = make(hexutil.Bytes, params.BLSPubkeySize)
|
||||
copy(sc.Pubkeys[i][:], s[i*params.BLSPubkeySize:(i+1)*params.BLSPubkeySize])
|
||||
}
|
||||
sc.Aggregate = make(hexutil.Bytes, params.BLSPubkeySize)
|
||||
copy(sc.Aggregate[:], s[params.SyncCommitteeSize*params.BLSPubkeySize:])
|
||||
return json.Marshal(&sc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Marshaler.
|
||||
func (s *SerializedSyncCommittee) UnmarshalJSON(input []byte) error {
|
||||
var sc jsonSyncCommittee
|
||||
if err := json.Unmarshal(input, &sc); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(sc.Pubkeys) != params.SyncCommitteeSize {
|
||||
return fmt.Errorf("invalid number of pubkeys %d", len(sc.Pubkeys))
|
||||
}
|
||||
for i, key := range sc.Pubkeys {
|
||||
if len(key) != params.BLSPubkeySize {
|
||||
return fmt.Errorf("pubkey %d has invalid size %d", i, len(key))
|
||||
}
|
||||
copy(s[i*params.BLSPubkeySize:], key[:])
|
||||
}
|
||||
if len(sc.Aggregate) != params.BLSPubkeySize {
|
||||
return fmt.Errorf("invalid aggregate pubkey size %d", len(sc.Aggregate))
|
||||
}
|
||||
copy(s[params.SyncCommitteeSize*params.BLSPubkeySize:], sc.Aggregate[:])
|
||||
return nil
|
||||
}
|
||||
|
||||
// Root calculates the root hash of the binary tree representation of a sync
|
||||
// committee provided in serialized format.
|
||||
//
|
||||
// TODO(zsfelfoldi): Get rid of this when SSZ encoding lands.
|
||||
func (s *SerializedSyncCommittee) Root() common.Hash {
|
||||
var (
|
||||
hasher = sha256.New()
|
||||
padding [64 - params.BLSPubkeySize]byte
|
||||
data [params.SyncCommitteeSize]common.Hash
|
||||
l = params.SyncCommitteeSize
|
||||
)
|
||||
for i := range data {
|
||||
hasher.Reset()
|
||||
hasher.Write(s[i*params.BLSPubkeySize : (i+1)*params.BLSPubkeySize])
|
||||
hasher.Write(padding[:])
|
||||
hasher.Sum(data[i][:0])
|
||||
}
|
||||
for l > 1 {
|
||||
for i := 0; i < l/2; i++ {
|
||||
hasher.Reset()
|
||||
hasher.Write(data[i*2][:])
|
||||
hasher.Write(data[i*2+1][:])
|
||||
hasher.Sum(data[i][:0])
|
||||
}
|
||||
l /= 2
|
||||
}
|
||||
hasher.Reset()
|
||||
hasher.Write(s[SerializedSyncCommitteeSize-params.BLSPubkeySize : SerializedSyncCommitteeSize])
|
||||
hasher.Write(padding[:])
|
||||
hasher.Sum(data[1][:0])
|
||||
hasher.Reset()
|
||||
hasher.Write(data[0][:])
|
||||
hasher.Write(data[1][:])
|
||||
hasher.Sum(data[0][:0])
|
||||
return data[0]
|
||||
}
|
||||
|
||||
// Deserialize splits open the pubkeys into proper BLS key types.
|
||||
func (s *SerializedSyncCommittee) Deserialize() (*SyncCommittee, error) {
|
||||
sc := new(SyncCommittee)
|
||||
for i := 0; i <= params.SyncCommitteeSize; i++ {
|
||||
key := new(bls.Pubkey)
|
||||
|
||||
var bytes [params.BLSPubkeySize]byte
|
||||
copy(bytes[:], s[i*params.BLSPubkeySize:(i+1)*params.BLSPubkeySize])
|
||||
|
||||
if err := key.Deserialize(&bytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i < params.SyncCommitteeSize {
|
||||
sc.keys[i] = key
|
||||
} else {
|
||||
sc.aggregate = key
|
||||
}
|
||||
}
|
||||
return sc, nil
|
||||
}
|
||||
|
||||
// SyncCommittee is a set of sync committee signer pubkeys and the aggregate key.
|
||||
//
|
||||
// See data structure definition here:
|
||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate
|
||||
type SyncCommittee struct {
|
||||
keys [params.SyncCommitteeSize]*bls.Pubkey
|
||||
aggregate *bls.Pubkey
|
||||
}
|
||||
|
||||
// VerifySignature returns true if the given sync aggregate is a valid signature
|
||||
// or the given hash.
|
||||
func (sc *SyncCommittee) VerifySignature(signingRoot common.Hash, signature *SyncAggregate) bool {
|
||||
var (
|
||||
sig bls.Signature
|
||||
keys = make([]*bls.Pubkey, 0, params.SyncCommitteeSize)
|
||||
)
|
||||
if err := sig.Deserialize(&signature.Signature); err != nil {
|
||||
return false
|
||||
}
|
||||
for i, key := range sc.keys {
|
||||
if signature.Signers[i/8]&(byte(1)<<(i%8)) != 0 {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
return bls.FastAggregateVerify(keys, signingRoot[:], &sig)
|
||||
}
|
||||
|
||||
// SyncAggregate represents an aggregated BLS signature with Signers referring
|
||||
// to a subset of the corresponding sync committee.
|
||||
//
|
||||
// See data structure definition here:
|
||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate
|
||||
type SyncAggregate struct {
|
||||
Signers [params.SyncCommitteeBitmaskSize]byte
|
||||
Signature [params.BLSSignatureSize]byte
|
||||
}
|
||||
|
||||
type jsonSyncAggregate struct {
|
||||
Signers hexutil.Bytes `json:"sync_committee_bits"`
|
||||
Signature hexutil.Bytes `json:"sync_committee_signature"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (s *SyncAggregate) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&jsonSyncAggregate{
|
||||
Signers: s.Signers[:],
|
||||
Signature: s.Signature[:],
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Marshaler.
|
||||
func (s *SyncAggregate) UnmarshalJSON(input []byte) error {
|
||||
var sc jsonSyncAggregate
|
||||
if err := json.Unmarshal(input, &sc); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(sc.Signers) != params.SyncCommitteeBitmaskSize {
|
||||
return fmt.Errorf("invalid signature bitmask size %d", len(sc.Signers))
|
||||
}
|
||||
if len(sc.Signature) != params.BLSSignatureSize {
|
||||
return fmt.Errorf("invalid signature size %d", len(sc.Signature))
|
||||
}
|
||||
copy(s.Signers[:], sc.Signers)
|
||||
copy(s.Signature[:], sc.Signature)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignerCount returns the number of signers in the aggregate signature.
|
||||
func (s *SyncAggregate) SignerCount() int {
|
||||
var count int
|
||||
for _, v := range s.Signers {
|
||||
count += bits.OnesCount8(v)
|
||||
}
|
||||
return count
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2022 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 (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/go-yaml/yaml"
|
||||
)
|
||||
|
||||
// syncCommitteeDomain specifies the signatures specific use to avoid clashes
|
||||
// across signing different data structures.
|
||||
const syncCommitteeDomain = 7
|
||||
|
||||
// Fork describes a single beacon chain fork and also stores the calculated
|
||||
// signature domain used after this fork.
|
||||
type Fork struct {
|
||||
// Name of the fork in the chain config (config.yaml) file{
|
||||
Name string
|
||||
|
||||
// Epoch when given fork version is activated
|
||||
Epoch uint64
|
||||
|
||||
// Fork version, see https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types
|
||||
Version []byte
|
||||
|
||||
// calculated by computeDomain, based on fork version and genesis validators root
|
||||
domain merkle.Value
|
||||
}
|
||||
|
||||
// computeDomain returns the signature domain based on the given fork version
|
||||
// and genesis validator set root.
|
||||
func (f *Fork) computeDomain(genesisValidatorsRoot common.Hash) {
|
||||
var (
|
||||
hasher = sha256.New()
|
||||
forkVersion32 merkle.Value
|
||||
forkDataRoot merkle.Value
|
||||
)
|
||||
copy(forkVersion32[:], f.Version)
|
||||
hasher.Write(forkVersion32[:])
|
||||
hasher.Write(genesisValidatorsRoot[:])
|
||||
hasher.Sum(forkDataRoot[:0])
|
||||
|
||||
f.domain[0] = syncCommitteeDomain
|
||||
copy(f.domain[4:], forkDataRoot[:28])
|
||||
}
|
||||
|
||||
// Forks is the list of all beacon chain forks in the chain configuration.
|
||||
type Forks []*Fork
|
||||
|
||||
// domain returns the signature domain for the given epoch (assumes that domains
|
||||
// have already been calculated).
|
||||
func (f Forks) domain(epoch uint64) (merkle.Value, error) {
|
||||
for i := len(f) - 1; i >= 0; i-- {
|
||||
if epoch >= f[i].Epoch {
|
||||
return f[i].domain, nil
|
||||
}
|
||||
}
|
||||
return merkle.Value{}, fmt.Errorf("unknown fork for epoch %d", epoch)
|
||||
}
|
||||
|
||||
// SigningRoot calculates the signing root of the given header.
|
||||
func (f Forks) SigningRoot(header Header) (common.Hash, error) {
|
||||
domain, err := f.domain(header.Epoch())
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
var (
|
||||
signingRoot common.Hash
|
||||
headerHash = header.Hash()
|
||||
hasher = sha256.New()
|
||||
)
|
||||
hasher.Write(headerHash[:])
|
||||
hasher.Write(domain[:])
|
||||
hasher.Sum(signingRoot[:0])
|
||||
|
||||
return signingRoot, nil
|
||||
}
|
||||
|
||||
func (f Forks) Len() int { return len(f) }
|
||||
func (f Forks) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||
func (f Forks) Less(i, j int) bool { return f[i].Epoch < f[j].Epoch }
|
||||
|
||||
// ChainConfig contains the beacon chain configuration.
|
||||
type ChainConfig struct {
|
||||
GenesisTime uint64 // Unix timestamp of slot 0
|
||||
GenesisValidatorsRoot common.Hash // Root hash of the genesis validator set, used for signature domain calculation
|
||||
Forks Forks
|
||||
}
|
||||
|
||||
// AddFork adds a new item to the list of forks.
|
||||
func (c *ChainConfig) AddFork(name string, epoch uint64, version []byte) *ChainConfig {
|
||||
fork := &Fork{
|
||||
Name: name,
|
||||
Epoch: epoch,
|
||||
Version: version,
|
||||
}
|
||||
fork.computeDomain(c.GenesisValidatorsRoot)
|
||||
|
||||
c.Forks = append(c.Forks, fork)
|
||||
sort.Sort(c.Forks)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// LoadForks parses the beacon chain configuration file (config.yaml) and extracts
|
||||
// the list of forks.
|
||||
func (c *ChainConfig) LoadForks(path string) error {
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read beacon chain config file: %v", err)
|
||||
}
|
||||
config := make(map[string]string)
|
||||
if err := yaml.Unmarshal(file, &config); err != nil {
|
||||
return fmt.Errorf("failed to parse beacon chain config file: %v", err)
|
||||
}
|
||||
var (
|
||||
versions = make(map[string][]byte)
|
||||
epochs = make(map[string]uint64)
|
||||
)
|
||||
epochs["GENESIS"] = 0
|
||||
|
||||
for key, value := range config {
|
||||
if strings.HasSuffix(key, "_FORK_VERSION") {
|
||||
name := key[:len(key)-len("_FORK_VERSION")]
|
||||
if v, err := hexutil.Decode(value); err == nil {
|
||||
versions[name] = v
|
||||
} else {
|
||||
return fmt.Errorf("failed to decode hex fork id %q in beacon chain config file: %v", value, err)
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(key, "_FORK_EPOCH") {
|
||||
name := key[:len(key)-len("_FORK_EPOCH")]
|
||||
if v, err := strconv.ParseUint(value, 10, 64); err == nil {
|
||||
epochs[name] = v
|
||||
} else {
|
||||
return fmt.Errorf("failed to parse epoch number %q in beacon chain config file: %v", value, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for name, epoch := range epochs {
|
||||
if version, ok := versions[name]; ok {
|
||||
delete(versions, name)
|
||||
c.AddFork(name, epoch, version)
|
||||
} else {
|
||||
return fmt.Errorf("fork id missing for %q in beacon chain config file", name)
|
||||
}
|
||||
}
|
||||
for name := range versions {
|
||||
return fmt.Errorf("epoch number missing for fork %q in beacon chain config file", name)
|
||||
}
|
||||
sort.Sort(c.Forks)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var _ = (*headerMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (h Header) MarshalJSON() ([]byte, error) {
|
||||
type Header struct {
|
||||
Slot common.Decimal `gencodec:"required" json:"slot"`
|
||||
ProposerIndex common.Decimal `gencodec:"required" json:"proposer_index"`
|
||||
ParentRoot common.Hash `gencodec:"required" json:"parent_root"`
|
||||
StateRoot common.Hash `gencodec:"required" json:"state_root"`
|
||||
BodyRoot common.Hash `gencodec:"required" json:"body_root"`
|
||||
}
|
||||
var enc Header
|
||||
enc.Slot = common.Decimal(h.Slot)
|
||||
enc.ProposerIndex = common.Decimal(h.ProposerIndex)
|
||||
enc.ParentRoot = h.ParentRoot
|
||||
enc.StateRoot = h.StateRoot
|
||||
enc.BodyRoot = h.BodyRoot
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (h *Header) UnmarshalJSON(input []byte) error {
|
||||
type Header struct {
|
||||
Slot *common.Decimal `gencodec:"required" json:"slot"`
|
||||
ProposerIndex *common.Decimal `gencodec:"required" json:"proposer_index"`
|
||||
ParentRoot *common.Hash `gencodec:"required" json:"parent_root"`
|
||||
StateRoot *common.Hash `gencodec:"required" json:"state_root"`
|
||||
BodyRoot *common.Hash `gencodec:"required" json:"body_root"`
|
||||
}
|
||||
var dec Header
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.Slot == nil {
|
||||
return errors.New("missing required field 'slot' for Header")
|
||||
}
|
||||
h.Slot = uint64(*dec.Slot)
|
||||
if dec.ProposerIndex == nil {
|
||||
return errors.New("missing required field 'proposer_index' for Header")
|
||||
}
|
||||
h.ProposerIndex = uint64(*dec.ProposerIndex)
|
||||
if dec.ParentRoot == nil {
|
||||
return errors.New("missing required field 'parent_root' for Header")
|
||||
}
|
||||
h.ParentRoot = *dec.ParentRoot
|
||||
if dec.StateRoot == nil {
|
||||
return errors.New("missing required field 'state_root' for Header")
|
||||
}
|
||||
h.StateRoot = *dec.StateRoot
|
||||
if dec.BodyRoot == nil {
|
||||
return errors.New("missing required field 'body_root' for Header")
|
||||
}
|
||||
h.BodyRoot = *dec.BodyRoot
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2022 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 implements a few types of the beacon chain for light client usage.
|
||||
package types
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||
"github.com/ethereum/go-ethereum/beacon/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
|
||||
|
||||
const (
|
||||
headerIndexSlot = 8
|
||||
headerIndexProposerIndex = 9
|
||||
headerIndexParentRoot = 10
|
||||
headerIndexStateRoot = 11
|
||||
headerIndexBodyRoot = 12
|
||||
)
|
||||
|
||||
// Header defines a beacon header.
|
||||
//
|
||||
// See data structure definition here:
|
||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader
|
||||
type Header struct {
|
||||
// Monotonically increasing slot number for the beacon block (may be gapped)
|
||||
Slot uint64 `gencodec:"required" json:"slot"`
|
||||
|
||||
// Index into the validator table who created the beacon block
|
||||
ProposerIndex uint64 `gencodec:"required" json:"proposer_index"`
|
||||
|
||||
// SSZ hash of the parent beacon header
|
||||
ParentRoot common.Hash `gencodec:"required" json:"parent_root"`
|
||||
|
||||
// SSZ hash of the beacon state (https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#beacon-state)
|
||||
StateRoot common.Hash `gencodec:"required" json:"state_root"`
|
||||
|
||||
// SSZ hash of the beacon block body (https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#beaconblockbody)
|
||||
BodyRoot common.Hash `gencodec:"required" json:"body_root"`
|
||||
}
|
||||
|
||||
// headerMarshaling is a field type overrides for gencodec.
|
||||
type headerMarshaling struct {
|
||||
Slot common.Decimal
|
||||
ProposerIndex common.Decimal
|
||||
}
|
||||
|
||||
// Hash calculates the block root of the header.
|
||||
//
|
||||
// TODO(zsfelfoldi): Remove this when an SSZ encoder lands.
|
||||
func (h *Header) Hash() common.Hash {
|
||||
var values [16]merkle.Value // values corresponding to indices 8 to 15 of the beacon header tree
|
||||
binary.LittleEndian.PutUint64(values[headerIndexSlot][:8], h.Slot)
|
||||
binary.LittleEndian.PutUint64(values[headerIndexProposerIndex][:8], h.ProposerIndex)
|
||||
values[headerIndexParentRoot] = merkle.Value(h.ParentRoot)
|
||||
values[headerIndexStateRoot] = merkle.Value(h.StateRoot)
|
||||
values[headerIndexBodyRoot] = merkle.Value(h.BodyRoot)
|
||||
hasher := sha256.New()
|
||||
for i := 7; i > 0; i-- {
|
||||
hasher.Reset()
|
||||
hasher.Write(values[i*2][:])
|
||||
hasher.Write(values[i*2+1][:])
|
||||
hasher.Sum(values[i][:0])
|
||||
}
|
||||
return common.Hash(values[1])
|
||||
}
|
||||
|
||||
// Epoch returns the epoch the header belongs to.
|
||||
func (h *Header) Epoch() uint64 {
|
||||
return h.Slot / params.EpochLength
|
||||
}
|
||||
|
||||
// SyncPeriod returns the sync period the header belongs to.
|
||||
func (h *Header) SyncPeriod() uint64 {
|
||||
return SyncPeriod(h.Slot)
|
||||
}
|
||||
|
||||
// SyncPeriodStart returns the first slot of the given period.
|
||||
func SyncPeriodStart(period uint64) uint64 {
|
||||
return period * params.SyncPeriodLength
|
||||
}
|
||||
|
||||
// SyncPeriod returns the sync period that the given slot belongs to.
|
||||
func SyncPeriod(slot uint64) uint64 {
|
||||
return slot / params.SyncPeriodLength
|
||||
}
|
||||
|
||||
// SignedHeader represents a beacon header signed by a sync committee.
|
||||
//
|
||||
// This structure is created from either an optimistic update or an instant update:
|
||||
// - https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
|
||||
// - https://github.com/zsfelfoldi/beacon-APIs/blob/instant_update/apis/beacon/light_client/instant_update.yaml
|
||||
type SignedHeader struct {
|
||||
// Beacon header being signed
|
||||
Header Header
|
||||
|
||||
// Sync committee BLS signature aggregate
|
||||
Signature SyncAggregate
|
||||
|
||||
// Slot in which the signature has been created (newer than Header.Slot,
|
||||
// determines the signing sync committee)
|
||||
SignatureSlot uint64
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2022 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||
"github.com/ethereum/go-ethereum/beacon/params"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// LightClientUpdate is a proof of the next sync committee root based on a header
|
||||
// signed by the sync committee of the given period. Optionally, the update can
|
||||
// prove quasi-finality by the signed header referring to a previous, finalized
|
||||
// header from the same period, and the finalized header referring to the next
|
||||
// sync committee root.
|
||||
//
|
||||
// See data structure definition here:
|
||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientupdate
|
||||
type LightClientUpdate struct {
|
||||
AttestedHeader SignedHeader // Arbitrary header out of the period signed by the sync committee
|
||||
NextSyncCommitteeRoot common.Hash // Sync committee of the next period advertised in the current one
|
||||
NextSyncCommitteeBranch merkle.Values // Proof for the next period's sync committee
|
||||
|
||||
FinalizedHeader *Header `rlp:"nil"` // Optional header to announce a point of finality
|
||||
FinalityBranch merkle.Values // Proof for the announced finality
|
||||
|
||||
score *UpdateScore // Weight of the update to compare between competing ones
|
||||
}
|
||||
|
||||
// Validate verifies the validity of the update.
|
||||
func (update *LightClientUpdate) Validate() error {
|
||||
period := update.AttestedHeader.Header.SyncPeriod()
|
||||
if SyncPeriod(update.AttestedHeader.SignatureSlot) != period {
|
||||
return errors.New("signature slot and signed header are from different periods")
|
||||
}
|
||||
if update.FinalizedHeader != nil {
|
||||
if update.FinalizedHeader.SyncPeriod() != period {
|
||||
return errors.New("finalized header is from different period")
|
||||
}
|
||||
if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexFinalBlock, update.FinalityBranch, merkle.Value(update.FinalizedHeader.Hash())); err != nil {
|
||||
return fmt.Errorf("invalid finalized header proof: %w", err)
|
||||
}
|
||||
}
|
||||
if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexNextSyncCommittee, update.NextSyncCommitteeBranch, merkle.Value(update.NextSyncCommitteeRoot)); err != nil {
|
||||
return fmt.Errorf("invalid next sync committee proof: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Score returns the UpdateScore describing the proof strength of the update
|
||||
// Note: thread safety can be ensured by always calling Score on a newly received
|
||||
// or decoded update before making it potentially available for other threads
|
||||
func (update *LightClientUpdate) Score() UpdateScore {
|
||||
if update.score == nil {
|
||||
update.score = &UpdateScore{
|
||||
SignerCount: uint32(update.AttestedHeader.Signature.SignerCount()),
|
||||
SubPeriodIndex: uint32(update.AttestedHeader.Header.Slot & 0x1fff),
|
||||
FinalizedHeader: update.FinalizedHeader != nil,
|
||||
}
|
||||
}
|
||||
return *update.score
|
||||
}
|
||||
|
||||
// UpdateScore allows the comparison between updates at the same period in order
|
||||
// to find the best update chain that provides the strongest proof of being canonical.
|
||||
//
|
||||
// UpdateScores have a tightly packed binary encoding format for efficient p2p
|
||||
// protocol transmission. Each UpdateScore is encoded in 3 bytes.
|
||||
// When interpreted as a 24 bit little indian unsigned integer:
|
||||
// - the lowest 10 bits contain the number of signers in the header signature aggregate
|
||||
// - the next 13 bits contain the "sub-period index" which is he signed header's
|
||||
// slot modulo params.SyncPeriodLength (which is correlated with the risk of the chain being
|
||||
// re-orged before the previous period boundary in case of non-finalized updates)
|
||||
// - the highest bit is set when the update is finalized (meaning that the finality
|
||||
// header referenced by the signed header is in the same period as the signed
|
||||
// header, making reorgs before the period boundary impossible
|
||||
type UpdateScore struct {
|
||||
SignerCount uint32 // number of signers in the header signature aggregate
|
||||
SubPeriodIndex uint32 // signed header's slot modulo params.SyncPeriodLength
|
||||
FinalizedHeader bool // update is considered finalized if has finalized header from the same period and 2/3 signatures
|
||||
}
|
||||
|
||||
// finalized returns true if the update has a header signed by at least 2/3 of
|
||||
// the committee, referring to a finalized header that refers to the next sync
|
||||
// committee. This condition is a close approximation of the actual finality
|
||||
// condition that can only be verified by full beacon nodes.
|
||||
func (u *UpdateScore) finalized() bool {
|
||||
return u.FinalizedHeader && u.SignerCount >= params.SyncCommitteeSupermajority
|
||||
}
|
||||
|
||||
// BetterThan returns true if update u is considered better than w.
|
||||
func (u UpdateScore) BetterThan(w UpdateScore) bool {
|
||||
var (
|
||||
uFinalized = u.finalized()
|
||||
wFinalized = w.finalized()
|
||||
)
|
||||
if uFinalized != wFinalized {
|
||||
return uFinalized
|
||||
}
|
||||
return u.SignerCount > w.SignerCount
|
||||
}
|
||||
Reference in New Issue
Block a user