Skip to content

Commit 76f873f

Browse files
calberacursoragentabi87
authored
feat(node-api): Create proof endpoint for validator balance (#2840)
Signed-off-by: Cal Bera <[email protected]> Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Alberto Benegiamo <[email protected]>
1 parent eb1fbf6 commit 76f873f

14 files changed

+1097
-6
lines changed

node-api/handlers/proof/merkle/generalized_indexes.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ const (
7777
// GIndices. To get the GIndex of the withdrawal credentials of validator at index n, the formula is:
7878
// GIndex = ZeroValidatorCredentialsGIndexElectraBlock + (ValidatorGIndexOffset * n)
7979
ZeroValidatorCredentialsGIndexElectraBlock = 6350779162034177
80+
81+
// ZeroValidatorBalanceGIndexElectraState is the generalized index of the 0-3
82+
// validators' balances in the beacon state in the Electra forks. Balances are
83+
// packed 4 per leaf (uint64 values, 32 bytes per leaf). To get the GIndex of
84+
// the balance of validator at index n, the formula is:
85+
// GIndex = ZeroValidatorBalanceGIndexElectraState + (n / 4)
86+
ZeroValidatorBalanceGIndexElectraState = 23089744183296
87+
88+
// ZeroValidatorBalanceGIndexElectraBlock is the generalized index of the 0-3
89+
// validators' balances in the beacon block in the Electra forks. This is
90+
// calculated by concatenating the (ZeroValidatorBalanceGIndexElectraState, StateGIndexBlock)
91+
// GIndices. To get the GIndex of the balance of validator at index n, the formula is:
92+
// GIndex = ZeroValidatorBalanceGIndexElectraBlock + (n / 4)
93+
ZeroValidatorBalanceGIndexElectraBlock = 199011604627456
94+
95+
// BalancesPerLeaf is the number of validator balances packed into a single leaf.
96+
BalancesPerLeaf = 4
8097
)
8198

8299
// GetZeroValidatorPubkeyGIndexState determines the generalized index of the 0
@@ -120,3 +137,23 @@ func GetZeroValidatorCredentialsGIndexBlock(forkVersion common.Version) (uint64,
120137
}
121138
return 0, fmt.Errorf("unsupported fork version: %s", forkVersion)
122139
}
140+
141+
// GetZeroValidatorBalanceGIndexState determines the generalized
142+
// index of the 0-3 validators' balances in the beacon state
143+
// based on the fork version.
144+
func GetZeroValidatorBalanceGIndexState(forkVersion common.Version) (int, error) {
145+
if version.EqualsOrIsAfter(forkVersion, version.Electra()) {
146+
return ZeroValidatorBalanceGIndexElectraState, nil
147+
}
148+
return 0, fmt.Errorf("unsupported fork version: %s", forkVersion)
149+
}
150+
151+
// GetZeroValidatorBalanceGIndexBlock determines the generalized
152+
// index of the 0-3 validators' balances in the beacon block
153+
// based on the fork version.
154+
func GetZeroValidatorBalanceGIndexBlock(forkVersion common.Version) (uint64, error) {
155+
if version.EqualsOrIsAfter(forkVersion, version.Electra()) {
156+
return ZeroValidatorBalanceGIndexElectraBlock, nil
157+
}
158+
return 0, fmt.Errorf("unsupported fork version: %s", forkVersion)
159+
}

node-api/handlers/proof/merkle/generalized_indexes_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package merkle_test
2222

2323
import (
24+
"fmt"
2425
"testing"
2526

2627
"github.com/berachain/beacon-kit/node-api/handlers/proof/merkle"
@@ -310,3 +311,56 @@ func TestValidatorWithdrawalCredentialsGIndexElectra(t *testing.T) {
310311
int(oneValidatorWithdrawalCredentialsGIndexState-zeroValidatorWithdrawalCredentialsGIndexState),
311312
)
312313
}
314+
315+
func TestValidatorBalanceGIndexElectra(t *testing.T) {
316+
t.Parallel()
317+
318+
// GIndex of the 0 validator's balance in the state.
319+
_, zeroValidatorBalanceGIndexState, _, err := mlib.ObjectPath(
320+
"Balances/0",
321+
).GetGeneralizedIndex(beaconStateSchemaElectra)
322+
require.NoError(t, err)
323+
require.Equal(t,
324+
merkle.ZeroValidatorBalanceGIndexElectraState,
325+
int(zeroValidatorBalanceGIndexState),
326+
)
327+
328+
// GIndex of the 0 validator's balance in the block.
329+
_, zeroValidatorBalanceGIndexBlock, _, err := mlib.ObjectPath(
330+
"State/Balances/0",
331+
).GetGeneralizedIndex(beaconHeaderSchemaElectra)
332+
require.NoError(t, err)
333+
require.Equal(t,
334+
merkle.ZeroValidatorBalanceGIndexElectraBlock,
335+
int(zeroValidatorBalanceGIndexBlock),
336+
)
337+
338+
// Concatenation is consistent.
339+
concatValidatorBalanceStateToBlock := mlib.GeneralizedIndices{
340+
mlib.GeneralizedIndex(merkle.StateGIndexBlock),
341+
mlib.GeneralizedIndex(zeroValidatorBalanceGIndexState),
342+
}.Concat()
343+
require.Equal(t,
344+
zeroValidatorBalanceGIndexBlock,
345+
uint64(concatValidatorBalanceStateToBlock),
346+
)
347+
348+
// Verify that balances 0-3 share the same GIndex (packed in same leaf)
349+
for i := range merkle.BalancesPerLeaf {
350+
path := fmt.Sprintf("Balances/%d", i)
351+
_, balanceGIndex, _, gIndexErr := mlib.ObjectPath(path).GetGeneralizedIndex(beaconStateSchemaElectra)
352+
require.NoError(t, gIndexErr)
353+
require.Equal(t, zeroValidatorBalanceGIndexState, balanceGIndex)
354+
}
355+
356+
// GIndex offset of the next validator's balance.
357+
// Balances are packed 4 per leaf (uint64 values, 32 bytes per leaf)
358+
balance4Path := fmt.Sprintf("Balances/%d", merkle.BalancesPerLeaf)
359+
_, balanceGIndex4, _, err := mlib.ObjectPath(balance4Path).GetGeneralizedIndex(beaconStateSchemaElectra)
360+
require.NoError(t, err)
361+
require.Equal(t, 1, int(balanceGIndex4-zeroValidatorBalanceGIndexState))
362+
balance8Path := fmt.Sprintf("Balances/%d", merkle.BalancesPerLeaf*2)
363+
_, balanceGIndex8, _, err := mlib.ObjectPath(balance8Path).GetGeneralizedIndex(beaconStateSchemaElectra)
364+
require.NoError(t, err)
365+
require.Equal(t, 1, int(balanceGIndex8-balanceGIndex4))
366+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
[
2+
"0x0000000000000000000000000000000000000000000000000000000000000000",
3+
"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
4+
"0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
5+
"0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c",
6+
"0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c",
7+
"0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30",
8+
"0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1",
9+
"0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c",
10+
"0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193",
11+
"0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1",
12+
"0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b",
13+
"0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220",
14+
"0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f",
15+
"0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e",
16+
"0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784",
17+
"0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb",
18+
"0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
19+
"0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
20+
"0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
21+
"0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
22+
"0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
23+
"0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
24+
"0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
25+
"0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
26+
"0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
27+
"0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
28+
"0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
29+
"0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
30+
"0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
31+
"0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
32+
"0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
33+
"0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
34+
"0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
35+
"0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
36+
"0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
37+
"0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
38+
"0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
39+
"0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
40+
"0x6400000000000000000000000000000000000000000000000000000000000000",
41+
"0x6080a24df6cb76f31cacdf4419ac9bf0ac092087f40ec93f10c4608f967ca23a",
42+
"0xdb4261da1f55277e8d739af87ee3f2757b11de49e3021abcafe2d73ae3dd5ad1",
43+
"0x1b8afbf6f0034f939f0cfc6e3b03362631bdce35a43b65cbb8f732fa08373b69",
44+
"0x70ccdae9a06cda39d93eba92e2692bec147a29ef7e31ad9f4bebb347792d9204",
45+
"0x58f6c4a556e87b8de03a64800211685b11e4e6e05e001b65d5f0a588e4985be3",
46+
"0x0102030405060000000000000000000000000000000000000000000000000000",
47+
"0xe38c573641a369b49f1e77043562c3b6b3932c2cce7fcd4d71d494b4b8d08012",
48+
"0xa3df0acb0b3d50f9b7f569ffb440f3a5891a2723a35bd825d6cf271298e616b6"
49+
]
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
[
2+
"0x0000000000000000000000000000000000000000000000000000000000000000",
3+
"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
4+
"0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
5+
"0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c",
6+
"0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c",
7+
"0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30",
8+
"0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1",
9+
"0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c",
10+
"0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193",
11+
"0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1",
12+
"0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b",
13+
"0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220",
14+
"0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f",
15+
"0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e",
16+
"0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784",
17+
"0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb",
18+
"0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb",
19+
"0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab",
20+
"0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4",
21+
"0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f",
22+
"0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa",
23+
"0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c",
24+
"0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167",
25+
"0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7",
26+
"0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0",
27+
"0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544",
28+
"0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765",
29+
"0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4",
30+
"0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1",
31+
"0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636",
32+
"0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c",
33+
"0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7",
34+
"0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff",
35+
"0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5",
36+
"0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d",
37+
"0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c",
38+
"0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327",
39+
"0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74",
40+
"0x0100000000000000000000000000000000000000000000000000000000000000",
41+
"0x6080a24df6cb76f31cacdf4419ac9bf0ac092087f40ec93f10c4608f967ca23a",
42+
"0xd8fa0aadb22a3b0fbc808b72568e1ee6a4e189a09f5caf344277c96e6fa474fd",
43+
"0x1b8afbf6f0034f939f0cfc6e3b03362631bdce35a43b65cbb8f732fa08373b69",
44+
"0xda5a83fdae2974416e891f268f5d29d45f071bb414304bdff46aaaa07a7403cb",
45+
"0x58f6c4a556e87b8de03a64800211685b11e4e6e05e001b65d5f0a588e4985be3",
46+
"0x0102030000000000000000000000000000000000000000000000000000000000",
47+
"0xd6e497b816c27a31acd5d9f3ed670639fef7842fee51f044dfbfb6319c760a5f",
48+
"0x7b85fe2a9afab51dcca12b224e10bf25e6cb1cb99ac5d24be8a55fac862b6c90"
49+
]
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
//
3+
// Copyright (C) 2025, Berachain Foundation. All rights reserved.
4+
// Use of this software is governed by the Business Source License included
5+
// in the LICENSE file of this repository and at www.mariadb.com/bsl11.
6+
//
7+
// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY
8+
// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER
9+
// VERSIONS OF THE LICENSED WORK.
10+
//
11+
// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF
12+
// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF
13+
// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE).
14+
//
15+
// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
16+
// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
17+
// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
18+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
19+
// TITLE.
20+
21+
package merkle
22+
23+
import (
24+
"fmt"
25+
26+
ctypes "github.com/berachain/beacon-kit/consensus-types/types"
27+
"github.com/berachain/beacon-kit/node-api/handlers/proof/types"
28+
"github.com/berachain/beacon-kit/primitives/common"
29+
"github.com/berachain/beacon-kit/primitives/math"
30+
"github.com/berachain/beacon-kit/primitives/merkle"
31+
"github.com/pkg/errors"
32+
)
33+
34+
// bytesPerBalance is the number of bytes in a single balance (uint64).
35+
const bytesPerBalance uint64 = 8
36+
37+
// ProveBalanceInState generates a proof for a validator's balance in the beacon state.
38+
func ProveBalanceInState(
39+
forkVersion common.Version,
40+
bsm types.BeaconStateMarshallable,
41+
validatorIndex math.U64,
42+
) ([]common.Root, common.Root, error) {
43+
stateProofTree, err := bsm.GetTree()
44+
if err != nil {
45+
return nil, common.Root{}, err
46+
}
47+
48+
// Determine the starting generalized index for the 0-th validator's
49+
// balance for this fork.
50+
zeroBalanceGIndexState, err := GetZeroValidatorBalanceGIndexState(forkVersion)
51+
if err != nil {
52+
return nil, common.Root{}, err
53+
}
54+
55+
// Since balances are packed 4 per leaf, calculate the leaf offset
56+
leafOffset := validatorIndex / BalancesPerLeaf
57+
58+
// Calculate the generalized index for the target validator's balance leaf.
59+
// The offset multiplication is bounded by the number of validators, so
60+
// converting to int is safe on 64-bit architectures.
61+
gIndex := zeroBalanceGIndexState + int(leafOffset) // #nosec G115
62+
63+
balanceProof, err := stateProofTree.Prove(gIndex)
64+
if err != nil {
65+
return nil, common.Root{}, err
66+
}
67+
68+
proof := make([]common.Root, len(balanceProof.Hashes))
69+
for i, hash := range balanceProof.Hashes {
70+
proof[i] = common.NewRootFromBytes(hash)
71+
}
72+
73+
// The leaf contains 4 packed uint64 balances
74+
return proof, common.NewRootFromBytes(balanceProof.Leaf), nil
75+
}
76+
77+
// ProveBalanceInBlock generates a proof for a validator's balance in the beacon block.
78+
// Returns the proof, the leaf containing the packed balances, and the beacon block root.
79+
func ProveBalanceInBlock(
80+
validatorIndex math.U64,
81+
bbh *ctypes.BeaconBlockHeader,
82+
bsm types.BeaconStateMarshallable,
83+
allBalances []uint64,
84+
) ([]common.Root, common.Root, common.Root, error) {
85+
forkVersion := bsm.GetForkVersion()
86+
87+
// 1. Proof inside the state.
88+
balanceInStateProof, leaf, err := ProveBalanceInState(
89+
forkVersion, bsm, validatorIndex,
90+
)
91+
if err != nil {
92+
return nil, common.Root{}, common.Root{}, err
93+
}
94+
95+
// 2. Build the balance leaf and assert that it matches the proof's leaf.
96+
builtLeaf := buildBalanceLeaf(allBalances, validatorIndex)
97+
if !leaf.Equals(builtLeaf) {
98+
return nil, common.Root{}, common.Root{}, fmt.Errorf(
99+
"balance leaf mismatch -- proof tree leaf: 0x%s, built leaf: 0x%s", leaf, builtLeaf,
100+
)
101+
}
102+
103+
// 3. Proof of the state inside the block.
104+
stateInBlockProof, err := ProveBeaconStateInBlock(bbh, false)
105+
if err != nil {
106+
return nil, common.Root{}, common.Root{}, err
107+
}
108+
109+
// 4. Combine proofs: state-level hashes come first, followed by block-level
110+
// hashes (same order as ProveProposerPubkeyInBlock).
111+
//
112+
//nolint:gocritic // ok.
113+
combinedProof := append(balanceInStateProof, stateInBlockProof...)
114+
115+
// 5. Verify the combined proof against the beacon block root.
116+
// Since balances are packed 4 per leaf, calculate the leaf offset.
117+
leafOffset := validatorIndex / BalancesPerLeaf
118+
beaconRoot, err := verifyBalanceInBlock(
119+
forkVersion, bbh, leafOffset.Unwrap(), combinedProof, leaf,
120+
)
121+
if err != nil {
122+
return nil, common.Root{}, common.Root{}, err
123+
}
124+
125+
return combinedProof, leaf, beaconRoot, nil
126+
}
127+
128+
// buildBalanceLeaf constructs the 32-byte leaf containing the packed balances
129+
// for the group of validators that includes `validatorIndex`. Balances are
130+
// packed 4 per leaf (little-endian uint64s).
131+
func buildBalanceLeaf(allBalances []uint64, validatorIndex math.U64) common.Root {
132+
var leafBytes common.Root
133+
134+
// Determine which leaf the validator belongs to and the starting index in
135+
// the balances slice.
136+
leafIndex := validatorIndex / BalancesPerLeaf
137+
startIdx := leafIndex * BalancesPerLeaf
138+
139+
// Pack up to 4 balances (little-endian) into the 32-byte array.
140+
for i := range uint64(BalancesPerLeaf) {
141+
idx := startIdx.Unwrap() + i
142+
if idx >= uint64(len(allBalances)) {
143+
break
144+
}
145+
bal := allBalances[idx]
146+
for j := range bytesPerBalance {
147+
leafBytes[i*bytesPerBalance+j] = byte(bal >> (j * bytesPerBalance))
148+
}
149+
}
150+
151+
return leafBytes
152+
}
153+
154+
// verifyBalanceInBlock verifies the provided Merkle proof of a
155+
// validator's balance inside the beacon block and returns the
156+
// beacon block root that the proof was verified against.
157+
//
158+
// NOTE: Proof verification is not strictly necessary for operation, but we do
159+
// it as a sanity check to avoid propagating malformed proofs downstream.
160+
func verifyBalanceInBlock(
161+
forkVersion common.Version,
162+
bbh *ctypes.BeaconBlockHeader,
163+
balanceOffset uint64,
164+
proof []common.Root,
165+
leaf common.Root,
166+
) (common.Root, error) {
167+
zeroBalanceGIndexBlock, err := GetZeroValidatorBalanceGIndexBlock(forkVersion)
168+
if err != nil {
169+
return common.Root{}, err
170+
}
171+
172+
beaconRoot := bbh.HashTreeRoot()
173+
if !merkle.VerifyProof(
174+
beaconRoot,
175+
leaf,
176+
zeroBalanceGIndexBlock+balanceOffset,
177+
proof,
178+
) {
179+
return common.Root{}, errors.Wrapf(
180+
errors.New("balance proof failed to verify against beacon root"),
181+
"beacon root: 0x%s", beaconRoot,
182+
)
183+
}
184+
185+
return beaconRoot, nil
186+
}

0 commit comments

Comments
 (0)