From 80eff4823cb3061b1840b42a54d627eb8d76a53d Mon Sep 17 00:00:00 2001 From: Giulio Rebuffo Date: Fri, 9 Dec 2022 13:57:48 +0100 Subject: [PATCH 1/3] added dirty leaves skeleton + basic methods --- cmd/erigon-cl/core/state/encoding.go | 14 +++ cmd/erigon-cl/core/state/leaf_indexes.go | 52 ++++---- cmd/erigon-cl/core/state/root.go | 151 +++++++++++++++++++++++ cmd/erigon-cl/core/state/state.go | 12 +- 4 files changed, 198 insertions(+), 31 deletions(-) create mode 100644 cmd/erigon-cl/core/state/encoding.go create mode 100644 cmd/erigon-cl/core/state/root.go diff --git a/cmd/erigon-cl/core/state/encoding.go b/cmd/erigon-cl/core/state/encoding.go new file mode 100644 index 00000000000..043039f8fd7 --- /dev/null +++ b/cmd/erigon-cl/core/state/encoding.go @@ -0,0 +1,14 @@ +package state + +import ( + "encoding/binary" + + "github.com/ledgerwatch/erigon/common" +) + +// Uint64Root retrieve thr root of uint64 fields +func Uint64Root(val uint64) common.Hash { + var root common.Hash + binary.LittleEndian.PutUint64(root[:], val) + return root +} diff --git a/cmd/erigon-cl/core/state/leaf_indexes.go b/cmd/erigon-cl/core/state/leaf_indexes.go index 374d3c48e79..5ca17d0402f 100644 --- a/cmd/erigon-cl/core/state/leaf_indexes.go +++ b/cmd/erigon-cl/core/state/leaf_indexes.go @@ -1,32 +1,34 @@ package state +type StateLeafIndex uint + // All position of all the leaves of the state merkle tree. const ( - GenesisTimeLeafIndex = 0 - GenesisValidatorsRootLeafIndex = 1 - SlotLeafIndex = 2 - ForkLeafIndex = 3 - LatestBlockHeaderLeafIndex = 4 - BlockRootsLeafIndex = 5 - StateRootsLeafIndex = 6 - HistoricalRootsLeafIndex = 7 - Eth1DataLeafIndex = 8 - Eth1DataVotesLeafIndex = 9 - Eth1DepositIndexLeafIndex = 10 - ValidatorsLeafIndex = 11 - BalancesLeafIndex = 12 - RandaoMixesLeafIndex = 13 - SlashingsLeafIndex = 14 - PreviousEpochParticipationLeafIndex = 15 - CurrentEpochParticipationLeafIndex = 16 - JustificationBitsLeafIndex = 17 - PreviousJustifiedCheckpointLeafIndex = 18 - CurrentJustifiedCheckpointLeafIndex = 19 - FinalizedCheckpointLeafIndex = 20 - InactivityScoresLeafIndex = 21 - CurrentSyncCommitteeLeafIndex = 22 - NextSyncCommitteeLeafIndex = 23 - LatestExecutionPayloadHeaderLeafIndex = 24 + GenesisTimeLeafIndex StateLeafIndex = 0 + GenesisValidatorsRootLeafIndex StateLeafIndex = 1 + SlotLeafIndex StateLeafIndex = 2 + ForkLeafIndex StateLeafIndex = 3 + LatestBlockHeaderLeafIndex StateLeafIndex = 4 + BlockRootsLeafIndex StateLeafIndex = 5 + StateRootsLeafIndex StateLeafIndex = 6 + HistoricalRootsLeafIndex StateLeafIndex = 7 + Eth1DataLeafIndex StateLeafIndex = 8 + Eth1DataVotesLeafIndex StateLeafIndex = 9 + Eth1DepositIndexLeafIndex StateLeafIndex = 10 + ValidatorsLeafIndex StateLeafIndex = 11 + BalancesLeafIndex StateLeafIndex = 12 + RandaoMixesLeafIndex StateLeafIndex = 13 + SlashingsLeafIndex StateLeafIndex = 14 + PreviousEpochParticipationLeafIndex StateLeafIndex = 15 + CurrentEpochParticipationLeafIndex StateLeafIndex = 16 + JustificationBitsLeafIndex StateLeafIndex = 17 + PreviousJustifiedCheckpointLeafIndex StateLeafIndex = 18 + CurrentJustifiedCheckpointLeafIndex StateLeafIndex = 19 + FinalizedCheckpointLeafIndex StateLeafIndex = 20 + InactivityScoresLeafIndex StateLeafIndex = 21 + CurrentSyncCommitteeLeafIndex StateLeafIndex = 22 + NextSyncCommitteeLeafIndex StateLeafIndex = 23 + LatestExecutionPayloadHeaderLeafIndex StateLeafIndex = 24 // Leaves sizes BellatrixLeavesSize = 25 diff --git a/cmd/erigon-cl/core/state/root.go b/cmd/erigon-cl/core/state/root.go new file mode 100644 index 00000000000..23e4b5e09d6 --- /dev/null +++ b/cmd/erigon-cl/core/state/root.go @@ -0,0 +1,151 @@ +package state + +import ( + "github.com/ledgerwatch/erigon/common" +) + +func (b *BeaconState) HashTreeRoot() ([]byte, error) { +} + +func (b *BeaconState) computeDirtyLeaves() error { + // Update all dirty leafs + // ---- + + // Field(0): GenesisTime + if b.isLeafDirty(GenesisTimeLeafIndex) { + b.updateLeaf(GenesisTimeLeafIndex, Uint64Root(b.genesisTime)) + } + + // Field(1): GenesisValidatorsRoot + if b.isLeafDirty(GenesisValidatorsRootLeafIndex) { + b.updateLeaf(GenesisValidatorsRootLeafIndex, b.genesisValidatorsRoot) + } + + // Field(2): Slot + if b.isLeafDirty(SlotLeafIndex) { + b.updateLeaf(SlotLeafIndex, Uint64Root(b.slot)) + } + + // Field(3): Fork + if b.isLeafDirty(LatestBlockHeaderLeafIndex) { + headerRoot, err := b.fork.HashTreeRoot() + if err != nil { + return err + } + b.updateLeaf(LatestBlockHeaderLeafIndex, headerRoot) + } + + // Field(4): LatestBlockHeader + if b.isLeafDirty(LatestBlockHeaderLeafIndex) { + headerRoot, err := b.latestBlockHeader.HashTreeRoot() + if err != nil { + return err + } + b.updateLeaf(LatestBlockHeaderLeafIndex, headerRoot) + } + + // Field(5): BlockRoots + + // Field(6): StateRoots + + // Field(7): HistoricalRoots + + // Field(8): Eth1Data + if b.isLeafDirty(Eth1DataLeafIndex) { + dataRoot, err := b.eth1Data.HashTreeRoot() + if err != nil { + return err + } + b.updateLeaf(Eth1DataLeafIndex, dataRoot) + } + // Field(9): Eth1DataVotes + + // Field(10): Eth1DepositIndex + if b.isLeafDirty(Eth1DepositIndexLeafIndex) { + b.updateLeaf(Eth1DepositIndexLeafIndex, Uint64Root(b.eth1DepositIndex)) + } + + // Field(11): Validators + + // Field(12): Balances + + // Field(13): RandaoMixes + + // Field(14): Slashings + + // Field(15): PreviousEpochParticipation + + // Field(16): CurrentEpochParticipation + + // Field(17): JustificationBits + + // Field(18): PreviousJustifiedCheckpoint + if b.isLeafDirty(PreviousJustifiedCheckpointLeafIndex) { + checkpointRoot, err := b.previousJustifiedCheckpoint.HashTreeRoot() + if err != nil { + return err + } + b.updateLeaf(PreviousJustifiedCheckpointLeafIndex, checkpointRoot) + } + + // Field(19): CurrentJustifiedCheckpoint + if b.isLeafDirty(CurrentJustifiedCheckpointLeafIndex) { + checkpointRoot, err := b.currentJustifiedCheckpoint.HashTreeRoot() + if err != nil { + return err + } + b.updateLeaf(CurrentJustifiedCheckpointLeafIndex, checkpointRoot) + } + + // Field(20): FinalizedCheckpoint + if b.isLeafDirty(FinalizedCheckpointLeafIndex) { + checkpointRoot, err := b.finalizedCheckpoint.HashTreeRoot() + if err != nil { + return err + } + b.updateLeaf(FinalizedCheckpointLeafIndex, checkpointRoot) + } + + // Field(21): Inactivity Scores + + // Field(22): CurrentSyncCommitte + if b.isLeafDirty(CurrentSyncCommitteeLeafIndex) { + committeeRoot, err := b.currentSyncCommittee.HashTreeRoot() + if err != nil { + return err + } + b.updateLeaf(CurrentSyncCommitteeLeafIndex, committeeRoot) + } + + // Field(23): NextSyncCommitte + if b.isLeafDirty(NextSyncCommitteeLeafIndex) { + committeeRoot, err := b.nextSyncCommittee.HashTreeRoot() + if err != nil { + return err + } + b.updateLeaf(NextSyncCommitteeLeafIndex, committeeRoot) + } + + // Field(24): LatestExecutionPayloadHeader + if b.isLeafDirty(LatestBlockHeaderLeafIndex) { + headerRoot, err := b.latestBlockHeader.HashTreeRoot() + if err != nil { + return err + } + b.updateLeaf(LatestBlockHeaderLeafIndex, headerRoot) + } + return nil +} + +func (b *BeaconState) updateLeaf(idx StateLeafIndex, leaf common.Hash) { + // Update leaf with new value. + b.leaves[idx] = leaf + // Now leaf is clean :). + b.touchedLeaves[idx] = false +} + +func (b *BeaconState) isLeafDirty(idx StateLeafIndex) bool { + // If leaf is non-initialized or if it was touched then we change it. + touched, isInitialized := b.touchedLeaves[idx] + return !isInitialized || touched // change only if the leaf was touched or root is non-initialized. +} diff --git a/cmd/erigon-cl/core/state/state.go b/cmd/erigon-cl/core/state/state.go index f3c4ef00dc8..1bbda24174c 100644 --- a/cmd/erigon-cl/core/state/state.go +++ b/cmd/erigon-cl/core/state/state.go @@ -43,11 +43,11 @@ type BeaconState struct { nextSyncCommittee *cltypes.SyncCommittee latestExecutionPayloadHeader *cltypes.ExecutionHeader // Internals - version StateVersion // State version - leaves []common.Hash // Pre-computed leaves. - touchedLeaves map[int]bool // Maps each leaf to whether they were touched or not. - /*root common.Hash // Cached state root. - hasher HashFunc // Merkle root hasher.*/ + version StateVersion // State version + leaves []common.Hash // Pre-computed leaves. + touchedLeaves map[StateLeafIndex]bool // Maps each leaf to whether they were touched or not. + root common.Hash // Cached state root. + //hasher HashFunc // Merkle root hasher. } // FromBellatrixState initialize the beacon state as a bellatrix state. @@ -81,7 +81,7 @@ func FromBellatrixState(state *cltypes.BeaconStateBellatrix) *BeaconState { // Internals version: BellatrixVersion, leaves: make([]common.Hash, BellatrixLeavesSize), - touchedLeaves: map[int]bool{}, + touchedLeaves: map[StateLeafIndex]bool{}, // TODO: Make proper hasher } } From d0febebed9d00f7f9b52e940eb77cf4450b8e903 Mon Sep 17 00:00:00 2001 From: Giulio Rebuffo Date: Fri, 9 Dec 2022 16:23:04 +0100 Subject: [PATCH 2/3] added a bunch more stuff --- cl/utils/crypto.go | 5 +- cl/utils/math.go | 12 ++ cl/utils/merkle.go | 14 ++ cmd/erigon-cl/core/state/encoding.go | 149 +++++++++++++++++- .../core/state/{leaf_indexes.go => params.go} | 9 ++ cmd/erigon-cl/core/state/root.go | 49 +++++- cmd/erigon-cl/core/state/state.go | 4 +- cmd/lightclient/lightclient/store.go | 3 +- cmd/lightclient/lightclient/utils.go | 26 --- cmd/lightclient/lightclient/verify.go | 4 +- 10 files changed, 240 insertions(+), 35 deletions(-) create mode 100644 cl/utils/math.go create mode 100644 cl/utils/merkle.go rename cmd/erigon-cl/core/state/{leaf_indexes.go => params.go} (88%) delete mode 100644 cmd/lightclient/lightclient/utils.go diff --git a/cl/utils/crypto.go b/cl/utils/crypto.go index 99e586131db..a103dba36ed 100644 --- a/cl/utils/crypto.go +++ b/cl/utils/crypto.go @@ -25,7 +25,7 @@ var hasherPool = sync.Pool{ }, } -func Keccak256(data []byte) [32]byte { +func Keccak256(data []byte, extras ...[]byte) [32]byte { h, ok := hasherPool.Get().(hash.Hash) if !ok { h = sha256.New() @@ -36,6 +36,9 @@ func Keccak256(data []byte) [32]byte { var b [32]byte h.Write(data) + for _, extra := range extras { + h.Write(extra) + } h.Sum(b[:0]) return b diff --git a/cl/utils/math.go b/cl/utils/math.go new file mode 100644 index 00000000000..a99f5236aa4 --- /dev/null +++ b/cl/utils/math.go @@ -0,0 +1,12 @@ +package utils + +func IsPowerOf2(n uint64) bool { + return n != 0 && (n&(n-1)) == 0 +} + +func PowerOf2(n uint64) uint64 { + if n >= 64 { + panic("integer overflow") + } + return 1 << n +} diff --git a/cl/utils/merkle.go b/cl/utils/merkle.go new file mode 100644 index 00000000000..60a5dec29e1 --- /dev/null +++ b/cl/utils/merkle.go @@ -0,0 +1,14 @@ +package utils + +// Check if leaf at index verifies against the Merkle root and branch +func IsValidMerkleBranch(leaf [32]byte, branch [][]byte, depth uint64, index uint64, root [32]byte) bool { + value := leaf + for i := uint64(0); i < depth; i++ { + if (index / PowerOf2(i) % 2) == 1 { + value = Keccak256(append(branch[i], value[:]...)) + } else { + value = Keccak256(append(value[:], branch[i]...)) + } + } + return value == root +} diff --git a/cmd/erigon-cl/core/state/encoding.go b/cmd/erigon-cl/core/state/encoding.go index 043039f8fd7..c88b3a63ddb 100644 --- a/cmd/erigon-cl/core/state/encoding.go +++ b/cmd/erigon-cl/core/state/encoding.go @@ -1,14 +1,161 @@ package state import ( + "bytes" "encoding/binary" + "errors" + "fmt" + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/utils" "github.com/ledgerwatch/erigon/common" + "github.com/prysmaticlabs/gohashtree" ) -// Uint64Root retrieve thr root of uint64 fields +// Bits and masks are used for depth calculation. +const ( + mask0 = ^uint64((1 << (1 << iota)) - 1) + mask1 + mask2 + mask3 + mask4 + mask5 +) + +const ( + bit0 = uint8(1 << iota) + bit1 + bit2 + bit3 + bit4 + bit5 +) + +// Uint64Root retrieve the root of uint64 fields func Uint64Root(val uint64) common.Hash { var root common.Hash binary.LittleEndian.PutUint64(root[:], val) return root } + +func ArraysRoot(input [][32]byte, length uint64) ([32]byte, error) { + res, err := merkleRootFromLeaves(input, length) + if err != nil { + return [32]byte{}, err + } + + return res, nil +} + +func ArraysRootWithLength(input [][32]byte, length uint64) ([32]byte, error) { + base, err := merkleRootFromLeaves(input, length) + if err != nil { + return [32]byte{}, err + } + + lengthRoot := Uint64Root(uint64(len(input))) + return utils.Keccak256(base[:], lengthRoot[:]), nil +} + +func Eth1DataVectorRoot(votes []*cltypes.Eth1Data, length uint64) ([32]byte, error) { + var err error + + vectorizedVotesRoot := make([][32]byte, len(votes)) + // Vectorize ETH1 Data first of all + for i, vote := range votes { + vectorizedVotesRoot[i], err = vote.HashTreeRoot() + if err != nil { + return [32]byte{}, err + } + } + + return ArraysRootWithLength(vectorizedVotesRoot, length) +} + +func ValidatorsVectorRoot(validators []*cltypes.Validator, length uint64) ([32]byte, error) { + var err error + + vectorizedValidatorsRoot := make([][32]byte, len(validators)) + // Vectorize ETH1 Data first of all + for i, validator := range validators { + vectorizedValidatorsRoot[i], err = validator.HashTreeRoot() + if err != nil { + return [32]byte{}, err + } + } + + return ArraysRootWithLength(vectorizedValidatorsRoot, length) +} + +func merkleRootFromLeaves(leaves [][32]byte, length uint64) ([32]byte, error) { + if len(leaves) == 0 { + return [32]byte{}, errors.New("zero leaves provided") + } + if len(leaves) == 1 { + return leaves[0], nil + } + hashLayer := leaves + layers := make([][][32]byte, depth(length)+1) + layers[0] = hashLayer + var err error + hashLayer, err = merkleizeTrieLeaves(layers, hashLayer) + if err != nil { + return [32]byte{}, err + } + root := hashLayer[0] + return root, nil +} + +// depth retrieves the appropriate depth for the provided trie size. +func depth(v uint64) (out uint8) { + if v <= 1 { + return 0 + } + v-- + if v&mask5 != 0 { + v >>= bit5 + out |= bit5 + } + if v&mask4 != 0 { + v >>= bit4 + out |= bit4 + } + if v&mask3 != 0 { + v >>= bit3 + out |= bit3 + } + if v&mask2 != 0 { + v >>= bit2 + out |= bit2 + } + if v&mask1 != 0 { + v >>= bit1 + out |= bit1 + } + if v&mask0 != 0 { + out |= bit0 + } + out++ + return +} + +// merkleizeTrieLeaves returns intermediate roots of given leaves. +func merkleizeTrieLeaves(layers [][][32]byte, hashLayer [][32]byte) ([][32]byte, error) { + i := 1 + chunkBuffer := bytes.NewBuffer([]byte{}) + chunkBuffer.Grow(64) + for len(hashLayer) > 1 && i < len(layers) { + if !utils.IsPowerOf2(uint64(len(hashLayer))) { + return nil, fmt.Errorf("hash layer is a non power of 2: %d", len(hashLayer)) + } + newLayer := make([][32]byte, len(hashLayer)/2) + err := gohashtree.Hash(hashLayer, newLayer) + if err != nil { + return nil, err + } + hashLayer = newLayer + layers[i] = hashLayer + i++ + } + return hashLayer, nil +} diff --git a/cmd/erigon-cl/core/state/leaf_indexes.go b/cmd/erigon-cl/core/state/params.go similarity index 88% rename from cmd/erigon-cl/core/state/leaf_indexes.go rename to cmd/erigon-cl/core/state/params.go index 5ca17d0402f..f9362a886ce 100644 --- a/cmd/erigon-cl/core/state/leaf_indexes.go +++ b/cmd/erigon-cl/core/state/params.go @@ -33,3 +33,12 @@ const ( // Leaves sizes BellatrixLeavesSize = 25 ) + +const ( + BlockRootsLength = 8192 + StateRootsLength = 8192 + HistoricalRootsLength = 16777216 + Eth1DataVotesRootsLimit = 2048 + ValidatorRegistryLimit = 1099511627776 + RandaoMixesLength = 65536 +) diff --git a/cmd/erigon-cl/core/state/root.go b/cmd/erigon-cl/core/state/root.go index 23e4b5e09d6..b8de409ae67 100644 --- a/cmd/erigon-cl/core/state/root.go +++ b/cmd/erigon-cl/core/state/root.go @@ -4,7 +4,11 @@ import ( "github.com/ledgerwatch/erigon/common" ) -func (b *BeaconState) HashTreeRoot() ([]byte, error) { +func (b *BeaconState) HashTreeRoot() ([32]byte, error) { + if err := b.computeDirtyLeaves(); err != nil { + return [32]byte{}, err + } + return merkleRootFromLeaves(b.leaves, BellatrixLeavesSize) } func (b *BeaconState) computeDirtyLeaves() error { @@ -45,10 +49,31 @@ func (b *BeaconState) computeDirtyLeaves() error { } // Field(5): BlockRoots + if b.isLeafDirty(BlockRootsLeafIndex) { + blockRootsRoot, err := ArraysRoot(b.blockRoots, BlockRootsLength) + if err != nil { + return err + } + b.updateLeaf(BlockRootsLeafIndex, blockRootsRoot) + } // Field(6): StateRoots + if b.isLeafDirty(StateRootsLeafIndex) { + stateRootsRoot, err := ArraysRoot(b.stateRoots, StateRootsLength) + if err != nil { + return err + } + b.updateLeaf(StateRootsLeafIndex, stateRootsRoot) + } // Field(7): HistoricalRoots + if b.isLeafDirty(HistoricalRootsLeafIndex) { + historicalRootsRoot, err := ArraysRootWithLength(b.historicalRoots, HistoricalRootsLength) + if err != nil { + return err + } + b.updateLeaf(HistoricalRootsLeafIndex, historicalRootsRoot) + } // Field(8): Eth1Data if b.isLeafDirty(Eth1DataLeafIndex) { @@ -58,7 +83,15 @@ func (b *BeaconState) computeDirtyLeaves() error { } b.updateLeaf(Eth1DataLeafIndex, dataRoot) } + // Field(9): Eth1DataVotes + if b.isLeafDirty(Eth1DataVotesLeafIndex) { + votesRoot, err := Eth1DataVectorRoot(b.eth1DataVotes, Eth1DataVotesRootsLimit) + if err != nil { + return err + } + b.updateLeaf(Eth1DataLeafIndex, votesRoot) + } // Field(10): Eth1DepositIndex if b.isLeafDirty(Eth1DepositIndexLeafIndex) { @@ -66,10 +99,24 @@ func (b *BeaconState) computeDirtyLeaves() error { } // Field(11): Validators + if b.isLeafDirty(ValidatorsLeafIndex) { + vRoot, err := ValidatorsVectorRoot(b.validators, ValidatorRegistryLimit) + if err != nil { + return err + } + b.updateLeaf(ValidatorsLeafIndex, vRoot) + } // Field(12): Balances // Field(13): RandaoMixes + if b.isLeafDirty(RandaoMixesLeafIndex) { + randaoRootsRoot, err := ArraysRoot(b.randaoMixes, RandaoMixesLength) + if err != nil { + return err + } + b.updateLeaf(RandaoMixesLeafIndex, randaoRootsRoot) + } // Field(14): Slashings diff --git a/cmd/erigon-cl/core/state/state.go b/cmd/erigon-cl/core/state/state.go index 1bbda24174c..65f88e5248a 100644 --- a/cmd/erigon-cl/core/state/state.go +++ b/cmd/erigon-cl/core/state/state.go @@ -44,10 +44,8 @@ type BeaconState struct { latestExecutionPayloadHeader *cltypes.ExecutionHeader // Internals version StateVersion // State version - leaves []common.Hash // Pre-computed leaves. + leaves [][32]byte // Pre-computed leaves. touchedLeaves map[StateLeafIndex]bool // Maps each leaf to whether they were touched or not. - root common.Hash // Cached state root. - //hasher HashFunc // Merkle root hasher. } // FromBellatrixState initialize the beacon state as a bellatrix state. diff --git a/cmd/lightclient/lightclient/store.go b/cmd/lightclient/lightclient/store.go index fd51f2618b5..d87d42ba4fd 100644 --- a/cmd/lightclient/lightclient/store.go +++ b/cmd/lightclient/lightclient/store.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/utils" ) type LightClientStore struct { @@ -43,7 +44,7 @@ func NewLightClientStore(trustedRoot [32]byte, bootstrap *cltypes.LightClientBoo if err != nil { return nil, err } - if !isValidMerkleBranch( + if !utils.IsValidMerkleBranch( syncCommitteeRoot, bootstrap.CurrentSyncCommitteeBranch, 5, // floorlog2(CURRENT_SYNC_COMMITTEE_INDEX) diff --git a/cmd/lightclient/lightclient/utils.go b/cmd/lightclient/lightclient/utils.go deleted file mode 100644 index 19476a650b2..00000000000 --- a/cmd/lightclient/lightclient/utils.go +++ /dev/null @@ -1,26 +0,0 @@ -package lightclient - -import "github.com/ledgerwatch/erigon/cl/utils" - -// PowerOf2 returns an integer that is the provided -// exponent of 2. Can only return powers of 2 till 63, -// after that it overflows -func powerOf2(n uint64) uint64 { - if n >= 64 { - panic("integer overflow") - } - return 1 << n -} - -// Check if leaf at index verifies against the Merkle root and branch -func isValidMerkleBranch(leaf [32]byte, branch [][]byte, depth uint64, index uint64, root [32]byte) bool { - value := leaf - for i := uint64(0); i < depth; i++ { - if (index / powerOf2(i) % 2) == 1 { - value = utils.Keccak256(append(branch[i], value[:]...)) - } else { - value = utils.Keccak256(append(value[:], branch[i]...)) - } - } - return value == root -} diff --git a/cmd/lightclient/lightclient/verify.go b/cmd/lightclient/lightclient/verify.go index a67432ad62d..cd96f55e9be 100644 --- a/cmd/lightclient/lightclient/verify.go +++ b/cmd/lightclient/lightclient/verify.go @@ -48,7 +48,7 @@ func (l *LightClient) validateUpdate(update *cltypes.LightClientUpdate) (bool, e if err != nil { return false, err } - if !isValidMerkleBranch( + if !utils.IsValidMerkleBranch( finalizedRoot, update.FinalityBranch, 6, // floorlog2(FINALIZED_ROOT_INDEX) @@ -67,7 +67,7 @@ func (l *LightClient) validateUpdate(update *cltypes.LightClientUpdate) (bool, e if err != nil { return false, err } - if !isValidMerkleBranch( + if !utils.IsValidMerkleBranch( syncRoot, update.NextSyncCommitteeBranch, 5, // floorlog2(NEXT_SYNC_COMMITTEE_INDEX) From ddacae8b088ab2c1863826db273e48ef6a3a1ce3 Mon Sep 17 00:00:00 2001 From: Giulio Rebuffo Date: Fri, 9 Dec 2022 18:03:20 +0100 Subject: [PATCH 3/3] added all fields root --- cmd/erigon-cl/core/state/encoding.go | 152 +++++++++++++++++++++++++++ cmd/erigon-cl/core/state/params.go | 1 + cmd/erigon-cl/core/state/root.go | 41 +++++++- cmd/erigon-cl/core/state/state.go | 2 +- 4 files changed, 194 insertions(+), 2 deletions(-) diff --git a/cmd/erigon-cl/core/state/encoding.go b/cmd/erigon-cl/core/state/encoding.go index c88b3a63ddb..653058c8b1f 100644 --- a/cmd/erigon-cl/core/state/encoding.go +++ b/cmd/erigon-cl/core/state/encoding.go @@ -72,6 +72,16 @@ func Eth1DataVectorRoot(votes []*cltypes.Eth1Data, length uint64) ([32]byte, err return ArraysRootWithLength(vectorizedVotesRoot, length) } +func Uint64ListRootWithLength(list []uint64, length uint64) ([32]byte, error) { + var err error + roots, err := PackUint64IntoChunks(list) + if err != nil { + return [32]byte{}, err + } + + return ArraysRootWithLength(roots, length) +} + func ValidatorsVectorRoot(validators []*cltypes.Validator, length uint64) ([32]byte, error) { var err error @@ -159,3 +169,145 @@ func merkleizeTrieLeaves(layers [][][32]byte, hashLayer [][32]byte) ([][32]byte, } return hashLayer, nil } + +// PackUint64IntoChunks packs a list of uint64 values into 32 byte roots. +func PackUint64IntoChunks(vals []uint64) ([][32]byte, error) { + // Initialize how many uint64 values we can pack + // into a single chunk(32 bytes). Each uint64 value + // would take up 8 bytes. + numOfElems := 4 + sizeOfElem := 32 / numOfElems + // Determine total number of chunks to be + // allocated to provided list of unsigned + // 64-bit integers. + numOfChunks := len(vals) / numOfElems + // Add an extra chunk if the list size + // is not a perfect multiple of the number + // of elements. + if len(vals)%numOfElems != 0 { + numOfChunks++ + } + chunkList := make([][32]byte, numOfChunks) + for idx, b := range vals { + // In order to determine how to pack in the uint64 value by index into + // our chunk list we need to determine a few things. + // 1) The chunk which the particular uint64 value corresponds to. + // 2) The position of the value in the chunk itself. + // + // Once we have determined these 2 values we can simply find the correct + // section of contiguous bytes to insert the value in the chunk. + chunkIdx := idx / numOfElems + idxInChunk := idx % numOfElems + chunkPos := idxInChunk * sizeOfElem + binary.LittleEndian.PutUint64(chunkList[chunkIdx][chunkPos:chunkPos+sizeOfElem], b) + } + return chunkList, nil +} + +func ValidatorLimitForBalancesChunks() uint64 { + maxValidatorLimit := uint64(ValidatorRegistryLimit) + bytesInUint64 := uint64(8) + return (maxValidatorLimit*bytesInUint64 + 31) / 32 // round to nearest chunk +} + +func SlashingsRoot(slashings []uint64) ([32]byte, error) { + slashingMarshaling := make([][]byte, SlashingsLength) + for i := 0; i < len(slashings) && i < len(slashingMarshaling); i++ { + slashBuf := make([]byte, 8) + binary.LittleEndian.PutUint64(slashBuf, slashings[i]) + slashingMarshaling[i] = slashBuf + } + slashingChunks, err := PackByChunk(slashingMarshaling) + if err != nil { + return [32]byte{}, err + } + return ArraysRoot(slashingChunks, uint64(len(slashingChunks))) +} + +// PackByChunk a given byte array's final chunk with zeroes if needed. +func PackByChunk(serializedItems [][]byte) ([][32]byte, error) { + emptyChunk := [32]byte{} + // If there are no items, we return an empty chunk. + if len(serializedItems) == 0 { + return [][32]byte{emptyChunk}, nil + } else if len(serializedItems[0]) == 32 { + // If each item has exactly BYTES_PER_CHUNK length, we return the list of serialized items. + chunks := make([][32]byte, 0, len(serializedItems)) + for _, c := range serializedItems { + var chunk [32]byte + copy(chunk[:], c) + chunks = append(chunks, chunk) + } + return chunks, nil + } + // We flatten the list in order to pack its items into byte chunks correctly. + orderedItems := make([]byte, 0, len(serializedItems)*len(serializedItems[0])) + for _, item := range serializedItems { + orderedItems = append(orderedItems, item...) + } + // If all our serialized item slices are length zero, we + // exit early. + if len(orderedItems) == 0 { + return [][32]byte{emptyChunk}, nil + } + numItems := len(orderedItems) + var chunks [][32]byte + for i := 0; i < numItems; i += 32 { + j := i + 32 + // We create our upper bound index of the chunk, if it is greater than numItems, + // we set it as numItems itself. + if j > numItems { + j = numItems + } + // We create chunks from the list of items based on the + // indices determined above. + // Right-pad the last chunk with zero bytes if it does not + // have length bytesPerChunk from the helper. + // The ToBytes32 helper allocates a 32-byte array, before + // copying the ordered items in. This ensures that even if + // the last chunk is != 32 in length, we will right-pad it with + // zero bytes. + var chunk [32]byte + copy(chunk[:], orderedItems[i:j]) + chunks = append(chunks, chunk) + } + + return chunks, nil +} + +// ParticipationBitsRoot computes the HashTreeRoot merkleization of +// participation roots. +func ParticipationBitsRoot(bits []byte) ([32]byte, error) { + chunkedRoots, err := packParticipationBits(bits) + if err != nil { + return [32]byte{}, err + } + + return ArraysRootWithLength(chunkedRoots, uint64(ValidatorRegistryLimit+31)/32) +} + +// packParticipationBits into chunks. It'll pad the last chunk with zero bytes if +// it does not have length bytes per chunk. +func packParticipationBits(bytes []byte) ([][32]byte, error) { + numItems := len(bytes) + chunks := make([][32]byte, 0, numItems/32) + for i := 0; i < numItems; i += 32 { + j := i + 32 + // We create our upper bound index of the chunk, if it is greater than numItems, + // we set it as numItems itself. + if j > numItems { + j = numItems + } + // We create chunks from the list of items based on the + // indices determined above. + chunk := [32]byte{} + copy(chunk[:], bytes[i:j]) + chunks = append(chunks, chunk) + } + + if len(chunks) == 0 { + return chunks, nil + } + + return chunks, nil +} diff --git a/cmd/erigon-cl/core/state/params.go b/cmd/erigon-cl/core/state/params.go index f9362a886ce..e0c6720221a 100644 --- a/cmd/erigon-cl/core/state/params.go +++ b/cmd/erigon-cl/core/state/params.go @@ -41,4 +41,5 @@ const ( Eth1DataVotesRootsLimit = 2048 ValidatorRegistryLimit = 1099511627776 RandaoMixesLength = 65536 + SlashingsLength = 8192 ) diff --git a/cmd/erigon-cl/core/state/root.go b/cmd/erigon-cl/core/state/root.go index b8de409ae67..2d06402a40f 100644 --- a/cmd/erigon-cl/core/state/root.go +++ b/cmd/erigon-cl/core/state/root.go @@ -108,6 +108,13 @@ func (b *BeaconState) computeDirtyLeaves() error { } // Field(12): Balances + if b.isLeafDirty(BalancesLeafIndex) { + balancesRoot, err := Uint64ListRootWithLength(b.balances, ValidatorLimitForBalancesChunks()) + if err != nil { + return err + } + b.updateLeaf(BalancesLeafIndex, balancesRoot) + } // Field(13): RandaoMixes if b.isLeafDirty(RandaoMixesLeafIndex) { @@ -119,12 +126,37 @@ func (b *BeaconState) computeDirtyLeaves() error { } // Field(14): Slashings - + if b.isLeafDirty(SlashingsLeafIndex) { + slashingsRoot, err := SlashingsRoot(b.slashings) + if err != nil { + return err + } + b.updateLeaf(SlashingsLeafIndex, slashingsRoot) + } // Field(15): PreviousEpochParticipation + if b.isLeafDirty(PreviousEpochParticipationLeafIndex) { + participationRoot, err := ParticipationBitsRoot(b.previousEpochParticipation) + if err != nil { + return err + } + b.updateLeaf(PreviousEpochParticipationLeafIndex, participationRoot) + } // Field(16): CurrentEpochParticipation + if b.isLeafDirty(CurrentEpochParticipationLeafIndex) { + participationRoot, err := ParticipationBitsRoot(b.currentEpochParticipation) + if err != nil { + return err + } + b.updateLeaf(CurrentEpochParticipationLeafIndex, participationRoot) + } // Field(17): JustificationBits + if b.isLeafDirty(JustificationBitsLeafIndex) { + var root [32]byte + copy(root[:], b.justificationBits) + b.updateLeaf(JustificationBitsLeafIndex, root) + } // Field(18): PreviousJustifiedCheckpoint if b.isLeafDirty(PreviousJustifiedCheckpointLeafIndex) { @@ -154,6 +186,13 @@ func (b *BeaconState) computeDirtyLeaves() error { } // Field(21): Inactivity Scores + if b.isLeafDirty(InactivityScoresLeafIndex) { + scoresRoot, err := Uint64ListRootWithLength(b.inactivityScores, ValidatorLimitForBalancesChunks()) + if err != nil { + return err + } + b.updateLeaf(InactivityScoresLeafIndex, scoresRoot) + } // Field(22): CurrentSyncCommitte if b.isLeafDirty(CurrentSyncCommitteeLeafIndex) { diff --git a/cmd/erigon-cl/core/state/state.go b/cmd/erigon-cl/core/state/state.go index 65f88e5248a..f55373020c1 100644 --- a/cmd/erigon-cl/core/state/state.go +++ b/cmd/erigon-cl/core/state/state.go @@ -78,7 +78,7 @@ func FromBellatrixState(state *cltypes.BeaconStateBellatrix) *BeaconState { latestExecutionPayloadHeader: state.LatestExecutionPayloadHeader, // Internals version: BellatrixVersion, - leaves: make([]common.Hash, BellatrixLeavesSize), + leaves: make([][32]byte, BellatrixLeavesSize), touchedLeaves: map[StateLeafIndex]bool{}, // TODO: Make proper hasher }