prysm-pulse/beacon-chain/cache/depositsnapshot/merkle_tree.go
Sammy Rosso d55757500f
Integrate EIP-4881 Deposit Tree Into Prysm via a Feature Flag (#11942)
* Initial spec rewrite

* Finish adding merkle tree implementation

* Last bits

* Move reverse function

* Add comments

* Add deposit tree snapshot

* Add deposit tree

* Add comments + cleanup

* Fixes

* Add missing errors

* Small fixes

* Add unhandled error

* Cleanup

* Fix unsafe file.Close

* Add missing comments

* Small fixes

* Address some of deepSource' compaints

* Add depositCount check

* Add finalizedDeposit check

* Replace pointer magic with copy()

* Add test for slice reversal

* add back bytes method

* Add package level description

* Remove zerohash gen and add additional checks

* Add additional comments

* Small lint fixes

* Forgot an error

* Small fixes

* Move Uint64ToBytesLittleEndian32 + test

* Fix uint subtraction issue

* Move mixInLength below error handling

* Fix

* Fix deposit root

* integrate 4881

* edits

* added in deposit tree fetcher

* add file

* Add remaining fetcher functions

* Add new file for inserter functions

* Fixes and additional funcs

* Cleanup

* Add

* Graph

* pushed up edits

* fix up

* Updates

* Add EIP4881 toggle flag

* Add interfaces

* Fix tests

* More changes

* Fix

* Remove generated graph

* Fix spacing

* Changes

* Fixes

* Changes

* Test Fix

* gaz

* Fix a couple tests

* Fix last tests

* define protos

* proto methods

* pushed

* regen

* Add proto funcs

* builds

* pushin up

* Fix and cleanup

* Fix spectest

* General cleanup

* add 4881 to e2e

* Remove debug statements + remove test skip

* Implement first set of  missing methods

* Replace Zerohashes + cleanup

* gazelle

* fmt

* Put back defensive check

* Add error logs

* InsertFinalizedDeposits: return an error

* Remove logging

* Radek' Review

* Lint fixes

* build

* Remove cancel

* Update beacon-chain/deterministic-genesis/service.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Update beacon-chain/cache/depositsnapshot/deposit_inserter.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Cleanup

* Fix panic when DepositSnapshot is nil on init

* Gofmt

* Fix RootEquivalence test

* Gofmt

* Add missing comments

* Nishant' review

* Add Insert benchmarks

* fix up copy method

* Fix deep copy

* Fix conflicts

* Return error

* Fix linter issues

* add in migration logic

* Cleanup + tests

* fix

* Fix incorrect index in test

* Fix linter

* Gofmt

* fix it

* fixes for off by 1

* gaz

* fix cast

* fix it

* remove ErrZeroIndex

* Fix merkle_tree_test

* add fallback

* add fix for insertion bug

* add many fixes

* fix empty snapshot

* clean up

* use feature

* remove check

* fix failing tests

* skip it

* fix test

* fix test again

* fix for the last time

* Apply suggestions from code review

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* fix it

* remove cancel

* fix for voting

* addressing more comments

* fix err

* potuz's review

* one more test

* fix bad test

* make 4881 part of dev mode

* add workaround for new trie

* comment

* preston's review

* james's review

* add comment

* james review

* preston's review

* remove skipped test

* gaz

---------

Co-authored-by: rauljordan <raul@prysmaticlabs.com>
Co-authored-by: nisdas <nishdas93@gmail.com>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-07 03:19:32 +00:00

324 lines
9.7 KiB
Go

package depositsnapshot
import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/container/slice"
"github.com/prysmaticlabs/prysm/v4/container/trie"
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
"github.com/prysmaticlabs/prysm/v4/math"
)
const (
// DepositContractDepth is the maximum tree depth as defined by EIP-4881.
DepositContractDepth = 32
)
var (
// ErrFinalizedNodeCannotPushLeaf may occur when attempting to push a leaf to a finalized node. When a node is finalized, it cannot be modified or changed.
ErrFinalizedNodeCannotPushLeaf = errors.New("can't push a leaf to a finalized node")
// ErrLeafNodeCannotPushLeaf may occur when attempting to push a leaf to a leaf node.
ErrLeafNodeCannotPushLeaf = errors.New("can't push a leaf to a leaf node")
// ErrZeroLevel occurs when the value of level is 0.
ErrZeroLevel = errors.New("level should be greater than 0")
// ErrZeroDepth occurs when the value of depth is 0.
ErrZeroDepth = errors.New("depth should be greater than 0")
)
// MerkleTreeNode is the interface for a Merkle tree.
type MerkleTreeNode interface {
// GetRoot returns the root of the Merkle tree.
GetRoot() [32]byte
// IsFull returns whether there is space left for deposits.
IsFull() bool
// Finalize marks deposits of the Merkle tree as finalized.
Finalize(depositsToFinalize uint64, depth uint64) (MerkleTreeNode, error)
// GetFinalized returns the number of deposits and a list of hashes of all the finalized nodes.
GetFinalized(result [][32]byte) (uint64, [][32]byte)
// PushLeaf adds a new leaf node at the next available Zero node.
PushLeaf(leaf [32]byte, depth uint64) (MerkleTreeNode, error)
// Right represents the right child of a node.
Right() MerkleTreeNode
// Left represents the left child of a node.
Left() MerkleTreeNode
}
// create builds a new merkle tree
func create(leaves [][32]byte, depth uint64) MerkleTreeNode {
length := uint64(len(leaves))
if length == 0 {
return &ZeroNode{depth: depth}
}
if depth == 0 {
return &LeafNode{hash: leaves[0]}
}
split := math.Min(math.PowerOf2(depth-1), length)
left := create(leaves[0:split], depth-1)
right := create(leaves[split:], depth-1)
return &InnerNode{left: left, right: right}
}
// fromSnapshotParts creates a new Merkle tree from a list of finalized leaves, number of deposits and specified depth.
func fromSnapshotParts(finalized [][32]byte, deposits uint64, level uint64) (MerkleTreeNode, error) {
var err error
if len(finalized) < 1 || deposits == 0 {
return &ZeroNode{
depth: level,
}, nil
}
if deposits == math.PowerOf2(level) {
return &FinalizedNode{
depositCount: deposits,
hash: finalized[0],
}, nil
}
if level == 0 {
return &ZeroNode{}, ErrZeroLevel
}
node := InnerNode{}
if leftSubtree := math.PowerOf2(level - 1); deposits <= leftSubtree {
node.left, err = fromSnapshotParts(finalized, deposits, level-1)
if err != nil {
return &ZeroNode{}, err
}
node.right = &ZeroNode{depth: level - 1}
} else {
node.left = &FinalizedNode{
depositCount: leftSubtree,
hash: finalized[0],
}
node.right, err = fromSnapshotParts(finalized[1:], deposits-leftSubtree, level-1)
if err != nil {
return &ZeroNode{}, err
}
}
return &node, nil
}
// generateProof returns a merkle proof and root
func generateProof(tree MerkleTreeNode, index uint64, depth uint64) ([32]byte, [][32]byte) {
var proof [][32]byte
node := tree
for depth > 0 {
ithBit := (index >> (depth - 1)) & 0x1
if ithBit == 1 {
proof = append(proof, node.Left().GetRoot())
node = node.Right()
} else {
proof = append(proof, node.Right().GetRoot())
node = node.Left()
}
depth--
}
proof = slice.Reverse(proof)
return node.GetRoot(), proof
}
// FinalizedNode represents a finalized node and satisfies the MerkleTreeNode interface.
type FinalizedNode struct {
depositCount uint64
hash [32]byte
}
// GetRoot returns the root of the Merkle tree.
func (f *FinalizedNode) GetRoot() [32]byte {
return f.hash
}
// IsFull returns whether there is space left for deposits.
// A FinalizedNode will always return true as by definition it
// is full and deposits can't be added to it.
func (_ *FinalizedNode) IsFull() bool {
return true
}
// Finalize marks deposits of the Merkle tree as finalized.
func (f *FinalizedNode) Finalize(depositsToFinalize uint64, depth uint64) (MerkleTreeNode, error) {
return f, nil
}
// 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) {
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) (MerkleTreeNode, error) {
return nil, ErrFinalizedNodeCannotPushLeaf
}
// Right returns nil as a finalized node can't have any children.
func (_ *FinalizedNode) Right() MerkleTreeNode {
return nil
}
// Left returns nil as a finalized node can't have any children.
func (_ *FinalizedNode) Left() MerkleTreeNode {
return nil
}
// LeafNode represents a leaf node holding a deposit and satisfies the MerkleTreeNode interface.
type LeafNode struct {
hash [32]byte
}
// GetRoot returns the root of the Merkle tree.
func (l *LeafNode) GetRoot() [32]byte {
return l.hash
}
// IsFull returns whether there is space left for deposits.
// A LeafNode will always return true as it is the last node
// in the tree and therefore can't have any deposits added to it.
func (_ *LeafNode) IsFull() bool {
return true
}
// Finalize marks deposits of the Merkle tree as finalized.
func (l *LeafNode) Finalize(depositsToFinalize uint64, depth uint64) (MerkleTreeNode, error) {
return &FinalizedNode{1, l.hash}, nil
}
// 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) {
return 0, result
}
// PushLeaf adds a new leaf node at the next available zero node.
func (_ *LeafNode) PushLeaf(_ [32]byte, _ uint64) (MerkleTreeNode, error) {
return nil, ErrLeafNodeCannotPushLeaf
}
// Right returns nil as a leaf node is the last node and can't have any children.
func (_ *LeafNode) Right() MerkleTreeNode {
return nil
}
// Left returns nil as a leaf node is the last node and can't have any children.
func (_ *LeafNode) Left() MerkleTreeNode {
return nil
}
// InnerNode represents an inner node with two children and satisfies the MerkleTreeNode interface.
type InnerNode struct {
left, right MerkleTreeNode
}
// GetRoot returns the root of the Merkle tree.
func (n *InnerNode) GetRoot() [32]byte {
left := n.left.GetRoot()
right := n.right.GetRoot()
return hash.Hash(append(left[:], right[:]...))
}
// IsFull returns whether there is space left for deposits.
func (n *InnerNode) IsFull() bool {
return n.right.IsFull()
}
// Finalize marks deposits of the Merkle tree as finalized.
func (n *InnerNode) Finalize(depositsToFinalize uint64, depth uint64) (MerkleTreeNode, error) {
var err error
deposits := math.PowerOf2(depth)
if deposits <= depositsToFinalize {
return &FinalizedNode{deposits, n.GetRoot()}, nil
}
if depth == 0 {
return &ZeroNode{}, ErrZeroDepth
}
n.left, err = n.left.Finalize(depositsToFinalize, depth-1)
if err != nil {
return &ZeroNode{}, err
}
if depositsToFinalize > deposits/2 {
remaining := depositsToFinalize - deposits/2
n.right, err = n.right.Finalize(remaining, depth-1)
if err != nil {
return &ZeroNode{}, err
}
}
return n, nil
}
// 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) {
leftDeposits, result := n.left.GetFinalized(result)
rightDeposits, result := n.right.GetFinalized(result)
return leftDeposits + rightDeposits, result
}
// PushLeaf adds a new leaf node at the next available zero node.
func (n *InnerNode) PushLeaf(leaf [32]byte, depth uint64) (MerkleTreeNode, error) {
if !n.left.IsFull() {
left, err := n.left.PushLeaf(leaf, depth-1)
if err == nil {
n.left = left
} else {
return n, err
}
} else {
right, err := n.right.PushLeaf(leaf, depth-1)
if err == nil {
n.right = right
} else {
return n, err
}
}
return n, nil
}
// Right returns the child node on the right.
func (n *InnerNode) Right() MerkleTreeNode {
return n.right
}
// Left returns the child node on the left.
func (n *InnerNode) Left() MerkleTreeNode {
return n.left
}
// ZeroNode represents an empty node without a deposit and satisfies the MerkleTreeNode interface.
type ZeroNode struct {
depth uint64
}
// GetRoot returns the root of the Merkle tree.
func (z *ZeroNode) GetRoot() [32]byte {
if z.depth == DepositContractDepth {
return hash.Hash(append(trie.ZeroHashes[z.depth-1][:], trie.ZeroHashes[z.depth-1][:]...))
}
return trie.ZeroHashes[z.depth]
}
// IsFull returns wh ether there is space left for deposits.
// A ZeroNode will always return false as a ZeroNode is an empty node
// that gets replaced by a deposit.
func (_ *ZeroNode) IsFull() bool {
return false
}
// Finalize marks deposits of the Merkle tree as finalized.
func (_ *ZeroNode) Finalize(depositsToFinalize uint64, depth uint64) (MerkleTreeNode, error) {
return &ZeroNode{}, nil
}
// 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) {
return 0, result
}
// PushLeaf adds a new leaf node at the next available zero node.
func (_ *ZeroNode) PushLeaf(leaf [32]byte, depth uint64) (MerkleTreeNode, error) {
return create([][32]byte{leaf}, depth), nil
}
// Right returns nil as a zero node can't have any children.
func (_ *ZeroNode) Right() MerkleTreeNode {
return nil
}
// Left returns nil as a zero node can't have any children.
func (_ *ZeroNode) Left() MerkleTreeNode {
return nil
}