Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2/n] test(deposits): EIP-4881 spec tests #2277

Open
wants to merge 9 commits into
base: fix-deps-htr2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,6 @@ coverage-test-unit-cover.txt

# server dev env for Air
.air.toml

# test data
storage/deposit/merkle/testdata/eip4881_test_cases.yaml
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ require (
go.uber.org/nilaway v0.0.0-20241010202415-ba14292918d8
golang.org/x/crypto v0.29.0
golang.org/x/sync v0.9.0
gopkg.in/yaml.v3 v3.0.1
sigs.k8s.io/yaml v1.4.0
)

Expand Down Expand Up @@ -494,7 +495,6 @@ require (
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
honnef.co/go/tools v0.5.1 // indirect
mvdan.cc/gofumpt v0.7.0 // indirect
Expand Down
2 changes: 1 addition & 1 deletion storage/deposit/merkle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ This package implements the [EIP-4881 spec](https://eips.ethereum.org/EIPS/eip-4

The format proposed by the EIP allows for the pruning of deposits that are no longer needed to participate fully in consensus.

Thanks to [Prysm](https://github.com/prysmaticlabs/prysm/blob/develop/beacon-chain/cache/depositsnapshot/deposit_tree.go) and Mark Mackey ([@ethDreamer](https://github.com/ethDreamer)) for the reference implementation.
Thanks to [Prysm](https://github.com/prysmaticlabs/prysm/blob/develop/beacon-chain/cache/depositsnapshot) and Mark Mackey ([@ethDreamer](https://github.com/ethDreamer)) for the reference implementation and tests.
13 changes: 7 additions & 6 deletions storage/deposit/merkle/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ package merkle
import (
"slices"

"github.com/berachain/beacon-kit/primitives/common"
"github.com/berachain/beacon-kit/primitives/math/pow"
"github.com/berachain/beacon-kit/primitives/merkle"
)

// create builds a new merkle tree.
func create(
hasher merkle.Hasher[[32]byte],
leaves [][32]byte,
hasher merkle.Hasher[common.Root],
leaves []common.Root,
depth uint64,
) TreeNode {
length := uint64(len(leaves))
Expand Down Expand Up @@ -58,8 +59,8 @@ func create(
// fromSnapshotParts creates a new Merkle tree from a list of finalized leaves,
// number of deposits and specified depth.
func fromSnapshotParts(
hasher merkle.Hasher[[32]byte],
finalized [][32]byte,
hasher merkle.Hasher[common.Root],
finalized []common.Root,
deposits uint64,
level uint64,
) (TreeNode, error) {
Expand Down Expand Up @@ -110,8 +111,8 @@ func generateProof(
tree TreeNode,
index uint64,
depth uint64,
) ([32]byte, [][32]byte) {
var proof [][32]byte
) (common.Root, []common.Root) {
var proof []common.Root
node := tree
for depth > 0 {
ithBit := (index >> (depth - 1)) & 0x1 //nolint:mnd // spec.
Expand Down
35 changes: 18 additions & 17 deletions storage/deposit/merkle/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package merkle

import (
"github.com/berachain/beacon-kit/primitives/common"
"github.com/berachain/beacon-kit/primitives/constants"
"github.com/berachain/beacon-kit/primitives/math/pow"
"github.com/berachain/beacon-kit/primitives/merkle"
Expand All @@ -31,11 +32,11 @@ import (
// interface.
type FinalizedNode struct {
depositCount uint64
hash [32]byte
hash common.Root
}

// GetRoot returns the root of the Merkle tree.
func (f *FinalizedNode) GetRoot() [32]byte {
func (f *FinalizedNode) GetRoot() common.Root {
return f.hash
}

Expand All @@ -53,12 +54,12 @@ func (f *FinalizedNode) Finalize(_, _ uint64) (TreeNode, error) {

// GetFinalized returns a list of hashes of all the finalized nodes and the
// number of deposits.
func (f *FinalizedNode) GetFinalized(result [][32]byte) (uint64, [][32]byte) {
func (f *FinalizedNode) GetFinalized(result []common.Root) (uint64, []common.Root) {
return f.depositCount, append(result, f.hash)
}

// PushLeaf adds a new leaf node at the next available zero node.
func (*FinalizedNode) PushLeaf([32]byte, uint64) (TreeNode, error) {
func (*FinalizedNode) PushLeaf(common.Root, uint64) (TreeNode, error) {
return nil, ErrFinalizedNodeCannotPushLeaf
}

Expand Down Expand Up @@ -90,11 +91,11 @@ func (f *FinalizedNode) Equals(other TreeNode) bool {
// LeafNode represents a leaf node holding a deposit and satisfies the TreeNode
// interface.
type LeafNode struct {
hash [32]byte
hash common.Root
}

// GetRoot returns the root of the Merkle tree.
func (l *LeafNode) GetRoot() [32]byte {
func (l *LeafNode) GetRoot() common.Root {
return l.hash
}

Expand All @@ -112,12 +113,12 @@ func (l *LeafNode) Finalize(_, _ uint64) (TreeNode, error) {

// GetFinalized returns a list of hashes of all the finalized nodes and the
// number of deposits.
func (*LeafNode) GetFinalized(result [][32]byte) (uint64, [][32]byte) {
func (*LeafNode) GetFinalized(result []common.Root) (uint64, []common.Root) {
return 0, result
}

// PushLeaf adds a new leaf node at the next available zero node.
func (*LeafNode) PushLeaf([32]byte, uint64) (TreeNode, error) {
func (*LeafNode) PushLeaf(common.Root, uint64) (TreeNode, error) {
return nil, ErrLeafNodeCannotPushLeaf
}

Expand Down Expand Up @@ -151,11 +152,11 @@ func (l *LeafNode) Equals(other TreeNode) bool {
// TreeNode interface.
type InnerNode struct {
left, right TreeNode
hasher merkle.Hasher[[32]byte]
hasher merkle.Hasher[common.Root]
}

// GetRoot returns the root of the Merkle tree.
func (n *InnerNode) GetRoot() [32]byte {
func (n *InnerNode) GetRoot() common.Root {
left := n.left.GetRoot()
right := n.right.GetRoot()
return n.hasher.Combi(left, right)
Expand Down Expand Up @@ -197,7 +198,7 @@ func (n *InnerNode) Finalize(

// GetFinalized returns a list of hashes of all the finalized nodes and the
// number of deposits.
func (n *InnerNode) GetFinalized(result [][32]byte) (uint64, [][32]byte) {
func (n *InnerNode) GetFinalized(result []common.Root) (uint64, []common.Root) {
leftDeposits, result := n.left.GetFinalized(result)
rightDeposits, result := n.right.GetFinalized(result)
return leftDeposits + rightDeposits, result
Expand All @@ -206,7 +207,7 @@ func (n *InnerNode) GetFinalized(result [][32]byte) (uint64, [][32]byte) {
// PushLeaf adds a new leaf node at the next available zero node.
//
//nolint:nestif // recursion.
func (n *InnerNode) PushLeaf(leaf [32]byte, depth uint64) (TreeNode, error) {
func (n *InnerNode) PushLeaf(leaf common.Root, depth uint64) (TreeNode, error) {
if !n.left.IsFull() {
left, err := n.left.PushLeaf(leaf, depth-1)
if err == nil {
Expand Down Expand Up @@ -254,11 +255,11 @@ func (n *InnerNode) Equals(other TreeNode) bool {
// TreeNode interface.
type ZeroNode struct {
depth uint64
hasher merkle.Hasher[[32]byte]
hasher merkle.Hasher[common.Root]
}

// GetRoot returns the root of the Merkle tree.
func (z *ZeroNode) GetRoot() [32]byte {
func (z *ZeroNode) GetRoot() common.Root {
if z.depth == constants.DepositContractDepth {
return z.hasher.Combi(zero.Hashes[z.depth-1], zero.Hashes[z.depth-1])
}
Expand All @@ -279,13 +280,13 @@ func (*ZeroNode) Finalize(_, _ uint64) (TreeNode, error) {

// GetFinalized returns a list of hashes of all the finalized nodes and the
// number of deposits.
func (*ZeroNode) GetFinalized(result [][32]byte) (uint64, [][32]byte) {
func (*ZeroNode) GetFinalized(result []common.Root) (uint64, []common.Root) {
return 0, result
}

// PushLeaf adds a new leaf node at the next available zero node.
func (z *ZeroNode) PushLeaf(leaf [32]byte, depth uint64) (TreeNode, error) {
return create(z.hasher, [][32]byte{leaf}, depth), nil
func (z *ZeroNode) PushLeaf(leaf common.Root, depth uint64) (TreeNode, error) {
return create(z.hasher, []common.Root{leaf}, depth), nil
}

// Right returns nil as a zero node can't have any children.
Expand Down
20 changes: 9 additions & 11 deletions storage/deposit/merkle/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ import (
)

func Test_create(t *testing.T) {
hasher := merkle.NewHasher[[32]byte](sha256.Hash)
hasher := merkle.NewHasher[common.Root](sha256.Hash)

tests := []struct {
name string
leaves [][32]byte
leaves []common.Root
depth uint64
want TreeNode
}{
Expand All @@ -51,13 +51,13 @@ func Test_create(t *testing.T) {
},
{
name: "zero depth",
leaves: [][32]byte{hexString(t, fmt.Sprintf("%064d", 0))},
leaves: []common.Root{hexString(t, fmt.Sprintf("%064d", 0))},
depth: 0,
want: &LeafNode{},
},
{
name: "depth of 1",
leaves: [][32]byte{hexString(t, fmt.Sprintf("%064d", 0))},
leaves: []common.Root{hexString(t, fmt.Sprintf("%064d", 0))},
depth: 1,
want: &InnerNode{
left: &LeafNode{},
Expand All @@ -80,15 +80,13 @@ func Test_create(t *testing.T) {
}

func Test_fromSnapshotParts(t *testing.T) {
hasher := merkle.NewHasher[[32]byte](sha256.Hash)

tests := []struct {
name string
finalized [][32]byte
finalized []common.Root
}{
{
name: "multiple deposits and multiple Finalized",
finalized: [][32]byte{
finalized: []common.Root{
hexString(t, fmt.Sprintf("%064d", 1)),
hexString(t, fmt.Sprintf("%064d", 2)),
},
Expand Down Expand Up @@ -124,7 +122,7 @@ func Test_fromSnapshotParts(t *testing.T) {
require.True(t, want.Equals(common.NewRootFromBytes(got[:])))

// Build from the snapshot once more
recovered, err := fromSnapshot(hasher, sShot)
recovered, err := fromSnapshot(sShot)
require.NoError(t, err)
got = recovered.HashTreeRoot()
require.True(t, want.Equals(common.NewRootFromBytes(got[:])))
Expand All @@ -134,13 +132,13 @@ func Test_fromSnapshotParts(t *testing.T) {

// TODO: add tests against spec for generateProof.

func hexString(t *testing.T, hexStr string) [32]byte {
func hexString(t *testing.T, hexStr string) common.Root {
t.Helper()
b, err := hex.DecodeString(hexStr)
require.NoError(t, err)
if len(b) != 32 {
require.Len(t, b, 32, "bad hash length, expected 32")
}
x := (*[32]byte)(b)
x := (*common.Root)(b)
return *x
}
44 changes: 32 additions & 12 deletions storage/deposit/merkle/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
package merkle

import (
"bytes"

"github.com/berachain/beacon-kit/primitives/common"
"github.com/berachain/beacon-kit/primitives/constants"
"github.com/berachain/beacon-kit/primitives/merkle"
"github.com/berachain/beacon-kit/primitives/merkle/zero"
Expand All @@ -29,15 +32,15 @@ import (
// DepositTreeSnapshot represents the data used to create a deposit tree given a
// snapshot.
type DepositTreeSnapshot struct {
finalized [][32]byte
depositRoot [32]byte
finalized []common.Root
depositRoot common.Root
depositCount uint64
executionBlock executionBlock
hasher merkle.Hasher[[32]byte]
hasher merkle.Hasher[common.Root]
}

// CalculateRoot returns the root of a deposit tree snapshot.
func (ds *DepositTreeSnapshot) CalculateRoot() [32]byte {
func (ds *DepositTreeSnapshot) CalculateRoot() common.Root {
size := ds.depositCount
index := len(ds.finalized)
root := zero.Hashes[0]
Expand All @@ -56,11 +59,28 @@ func (ds *DepositTreeSnapshot) CalculateRoot() [32]byte {
return ds.hasher.MixIn(root, ds.depositCount)
}

// Equals returns true if two deposit tree snapshots are equal.
func (ds *DepositTreeSnapshot) Equals(other *DepositTreeSnapshot) bool {
if ds == nil && other == nil {
return true
}
if ds == nil || other == nil {
return false
}

for i := range ds.finalized {
if !bytes.Equal(ds.finalized[i][:], other.finalized[i][:]) {
return false
}
}
return bytes.Equal(ds.depositRoot[:], other.depositRoot[:]) &&
ds.depositCount == other.depositCount &&
bytes.Equal(ds.executionBlock.Hash[:], other.executionBlock.Hash[:]) &&
ds.executionBlock.Depth == other.executionBlock.Depth
}

// fromSnapshot returns a deposit tree from a deposit tree snapshot.
func fromSnapshot(
hasher merkle.Hasher[[32]byte],
snapshot DepositTreeSnapshot,
) (*DepositTree, error) {
func fromSnapshot(snapshot DepositTreeSnapshot) (*DepositTree, error) {
root := snapshot.CalculateRoot()
if snapshot.depositRoot != root {
return nil, ErrInvalidSnapshotRoot
Expand All @@ -69,7 +89,7 @@ func fromSnapshot(
return nil, ErrTooManyDeposits
}
tree, err := fromSnapshotParts(
hasher,
snapshot.hasher,
snapshot.finalized,
snapshot.depositCount,
constants.DepositContractDepth,
Expand All @@ -81,14 +101,14 @@ func fromSnapshot(
tree: tree,
mixInLength: snapshot.depositCount,
finalizedExecutionBlock: snapshot.executionBlock,
hasher: hasher,
hasher: snapshot.hasher,
}, nil
}

// fromTreeParts constructs the deposit tree from pre-existing data.
func fromTreeParts(
hasher merkle.Hasher[[32]byte],
finalised [][32]byte,
hasher merkle.Hasher[common.Root],
finalised []common.Root,
depositCount uint64,
executionBlock executionBlock,
) DepositTreeSnapshot {
Expand Down
Loading
Loading