mirror of https://github.com/ethereum/go-ethereum
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>pull/27295/head
parent
41fafa47b6
commit
c08dc59aad
@ -0,0 +1,67 @@ |
|||||||
|
// 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 merkle implements proof verifications in binary merkle trees.
|
||||||
|
package merkle |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/sha256" |
||||||
|
"errors" |
||||||
|
"reflect" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil" |
||||||
|
) |
||||||
|
|
||||||
|
// Value represents either a 32 byte leaf value or hash node in a binary merkle tree/partial proof.
|
||||||
|
type Value [32]byte |
||||||
|
|
||||||
|
// Values represent a series of merkle tree leaves/nodes.
|
||||||
|
type Values []Value |
||||||
|
|
||||||
|
var valueT = reflect.TypeOf(Value{}) |
||||||
|
|
||||||
|
// UnmarshalJSON parses a merkle value in hex syntax.
|
||||||
|
func (m *Value) UnmarshalJSON(input []byte) error { |
||||||
|
return hexutil.UnmarshalFixedJSON(valueT, input, m[:]) |
||||||
|
} |
||||||
|
|
||||||
|
// VerifyProof verifies a Merkle proof branch for a single value in a
|
||||||
|
// binary Merkle tree (index is a generalized tree index).
|
||||||
|
func VerifyProof(root common.Hash, index uint64, branch Values, value Value) error { |
||||||
|
hasher := sha256.New() |
||||||
|
for _, sibling := range branch { |
||||||
|
hasher.Reset() |
||||||
|
if index&1 == 0 { |
||||||
|
hasher.Write(value[:]) |
||||||
|
hasher.Write(sibling[:]) |
||||||
|
} else { |
||||||
|
hasher.Write(sibling[:]) |
||||||
|
hasher.Write(value[:]) |
||||||
|
} |
||||||
|
hasher.Sum(value[:0]) |
||||||
|
if index >>= 1; index == 0 { |
||||||
|
return errors.New("branch has extra items") |
||||||
|
} |
||||||
|
} |
||||||
|
if index != 1 { |
||||||
|
return errors.New("branch is missing items") |
||||||
|
} |
||||||
|
if common.Hash(value) != root { |
||||||
|
return errors.New("root mismatch") |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
// 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 params |
||||||
|
|
||||||
|
const ( |
||||||
|
EpochLength = 32 |
||||||
|
SyncPeriodLength = 8192 |
||||||
|
|
||||||
|
BLSSignatureSize = 96 |
||||||
|
BLSPubkeySize = 48 |
||||||
|
|
||||||
|
SyncCommitteeSize = 512 |
||||||
|
SyncCommitteeBitmaskSize = SyncCommitteeSize / 8 |
||||||
|
SyncCommitteeSupermajority = (SyncCommitteeSize*2 + 2) / 3 |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
StateIndexGenesisTime = 32 |
||||||
|
StateIndexGenesisValidators = 33 |
||||||
|
StateIndexForkVersion = 141 |
||||||
|
StateIndexLatestHeader = 36 |
||||||
|
StateIndexBlockRoots = 37 |
||||||
|
StateIndexStateRoots = 38 |
||||||
|
StateIndexHistoricRoots = 39 |
||||||
|
StateIndexFinalBlock = 105 |
||||||
|
StateIndexSyncCommittee = 54 |
||||||
|
StateIndexNextSyncCommittee = 55 |
||||||
|
StateIndexExecPayload = 56 |
||||||
|
StateIndexExecHead = 908 |
||||||
|
) |
@ -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 |
||||||
|
} |
Loading…
Reference in new issue