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