// Package depositsnapshot implements the EIP-4881 standard for minimal sparse Merkle tree. // The format proposed by the EIP allows for the pruning of deposits that are no longer needed to participate fully in consensus. // Full EIP-4881 specification can be found here: https://eips.ethereum.org/EIPS/eip-4881 package depositsnapshot import ( "crypto/sha256" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v4/math" eth "github.com/prysmaticlabs/prysm/v4/proto/eth/v1" ) var ( // ErrEmptyExecutionBlock occurs when the execution block is nil. ErrEmptyExecutionBlock = errors.New("empty execution block") // ErrInvalidSnapshotRoot occurs when the snapshot root does not match the calculated root. ErrInvalidSnapshotRoot = errors.New("snapshot root is invalid") // ErrInvalidMixInLength occurs when the value for mix in length is 0. ErrInvalidMixInLength = errors.New("mixInLength should be greater than 0") // ErrInvalidIndex occurs when the index is less than the number of finalized deposits. ErrInvalidIndex = errors.New("index should be greater than finalizedDeposits - 1") // ErrNoDeposits occurs when the number of deposits is 0. ErrNoDeposits = errors.New("number of deposits should be greater than 0") // ErrTooManyDeposits occurs when the number of deposits exceeds the capacity of the tree. ErrTooManyDeposits = errors.New("number of deposits should not be greater than the capacity of the tree") ) // DepositTree is the Merkle tree representation of deposits. type DepositTree struct { tree MerkleTreeNode mixInLength uint64 // number of deposits in the tree, reference implementation calls this mix_in_length. finalizedExecutionBlock executionBlock } type executionBlock struct { Hash [32]byte Depth uint64 } // New creates an empty deposit tree. // //nolint:unused func New() *DepositTree { var leaves [][32]byte merkle := create(leaves, DepositContractDepth) return &DepositTree{ tree: merkle, mixInLength: 0, finalizedExecutionBlock: executionBlock{}, } } // getSnapshot returns a deposit tree snapshot. // //nolint:unused func (d *DepositTree) getSnapshot() (DepositTreeSnapshot, error) { if d.finalizedExecutionBlock == (executionBlock{}) { return DepositTreeSnapshot{}, ErrEmptyExecutionBlock } var finalized [][32]byte depositCount, finalized := d.tree.GetFinalized(finalized) return fromTreeParts(finalized, depositCount, d.finalizedExecutionBlock) } // fromSnapshot returns a deposit tree from a deposit tree snapshot. // //nolint:unused func fromSnapshot(snapshot DepositTreeSnapshot) (DepositTree, error) { root, err := snapshot.CalculateRoot() if err != nil { return DepositTree{}, err } if snapshot.depositRoot != root { return DepositTree{}, ErrInvalidSnapshotRoot } if snapshot.depositCount >= math.PowerOf2(uint64(DepositContractDepth)) { return DepositTree{}, ErrTooManyDeposits } tree, err := fromSnapshotParts(snapshot.finalized, snapshot.depositCount, DepositContractDepth) if err != nil { return DepositTree{}, err } if snapshot.depositCount == 0 { return DepositTree{}, ErrNoDeposits } return DepositTree{ tree: tree, mixInLength: snapshot.depositCount, finalizedExecutionBlock: snapshot.executionBlock, }, nil } // finalize marks a deposit as finalized. // //nolint:unused func (d *DepositTree) finalize(eth1data *eth.Eth1Data, executionBlockHeight uint64) error { var blockHash [32]byte copy(blockHash[:], eth1data.BlockHash) d.finalizedExecutionBlock = executionBlock{ Hash: blockHash, Depth: executionBlockHeight, } _, err := d.tree.Finalize(eth1data.DepositCount, DepositContractDepth) if err != nil { return err } return nil } // getProof returns the Deposit tree proof. // //nolint:unused func (d *DepositTree) getProof(index uint64) ([32]byte, [][32]byte, error) { if d.mixInLength <= 0 { return [32]byte{}, nil, ErrInvalidMixInLength } finalizedDeposits, _ := d.tree.GetFinalized([][32]byte{}) if finalizedDeposits != 0 { finalizedDeposits = finalizedDeposits - 1 } if index <= finalizedDeposits { return [32]byte{}, nil, ErrInvalidIndex } leaf, proof := generateProof(d.tree, index, DepositContractDepth) var mixInLength [32]byte copy(mixInLength[:], bytesutil.Uint64ToBytesLittleEndian32(d.mixInLength)) proof = append(proof, mixInLength) return leaf, proof, nil } // getRoot returns the root of the deposit tree. // //nolint:unused func (d *DepositTree) getRoot() [32]byte { root := d.tree.GetRoot() return sha256.Sum256(append(root[:], bytesutil.Uint64ToBytesLittleEndian32(d.mixInLength)...)) } // pushLeaf adds a new leaf to the tree. // //nolint:unused func (d *DepositTree) pushLeaf(leaf [32]byte) error { var err error d.tree, err = d.tree.PushLeaf(leaf, DepositContractDepth) if err != nil { return err } d.mixInLength++ return nil }