mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-22 06:14:38 +00:00
## Summary The deposit cache now has the ability to finalize deposits. This will cause it to drop unneeded deposit logs and hashes in the deposit Merkle tree that are no longer required to construct deposit proofs. The cache is finalized whenever the latest finalized checkpoint has a new `Eth1Data` with all deposits imported. This has three benefits: 1. Improves the speed of constructing Merkle proofs for deposits as we can just replay deposits since the last finalized checkpoint instead of all historical deposits when re-constructing the Merkle tree. 2. Significantly faster weak subjectivity sync as the deposit cache can be transferred to the newly syncing node in compressed form. The Merkle tree that stores `N` finalized deposits requires a maximum of `log2(N)` hashes. The newly syncing node then only needs to download deposits since the last finalized checkpoint to have a full tree. 3. Future proofing in preparation for [EIP-4444](https://eips.ethereum.org/EIPS/eip-4444) as execution nodes will no longer be required to store logs permanently so we won't always have all historical logs available to us. ## More Details Image to illustrate how the deposit contract merkle tree evolves and finalizes along with the resulting `DepositTreeSnapshot`  ## Other Considerations I've changed the structure of the `SszDepositCache` so once you load & save your database from this version of lighthouse, you will no longer be able to load it from older versions. Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
103 lines
3.7 KiB
Rust
103 lines
3.7 KiB
Rust
use eth2_hashing::hash;
|
|
use int_to_bytes::int_to_bytes32;
|
|
use merkle_proof::{MerkleTree, MerkleTreeError};
|
|
use safe_arith::SafeArith;
|
|
use types::{DepositTreeSnapshot, FinalizedExecutionBlock, Hash256};
|
|
|
|
/// Emulates the eth1 deposit contract merkle tree.
|
|
#[derive(PartialEq)]
|
|
pub struct DepositDataTree {
|
|
tree: MerkleTree,
|
|
mix_in_length: usize,
|
|
finalized_execution_block: Option<FinalizedExecutionBlock>,
|
|
depth: usize,
|
|
}
|
|
|
|
impl DepositDataTree {
|
|
/// Create a new Merkle tree from a list of leaves (`DepositData::tree_hash_root`) and a fixed depth.
|
|
pub fn create(leaves: &[Hash256], mix_in_length: usize, depth: usize) -> Self {
|
|
Self {
|
|
tree: MerkleTree::create(leaves, depth),
|
|
mix_in_length,
|
|
finalized_execution_block: None,
|
|
depth,
|
|
}
|
|
}
|
|
|
|
/// Returns 32 bytes representing the "mix in length" for the merkle root of this tree.
|
|
fn length_bytes(&self) -> Vec<u8> {
|
|
int_to_bytes32(self.mix_in_length as u64)
|
|
}
|
|
|
|
/// Retrieve the root hash of this Merkle tree with the length mixed in.
|
|
pub fn root(&self) -> Hash256 {
|
|
let mut preimage = [0; 64];
|
|
preimage[0..32].copy_from_slice(&self.tree.hash()[..]);
|
|
preimage[32..64].copy_from_slice(&self.length_bytes());
|
|
Hash256::from_slice(&hash(&preimage))
|
|
}
|
|
|
|
/// Return the leaf at `index` and a Merkle proof of its inclusion.
|
|
///
|
|
/// The Merkle proof is in "bottom-up" order, starting with a leaf node
|
|
/// and moving up the tree. Its length will be exactly equal to `depth + 1`.
|
|
pub fn generate_proof(&self, index: usize) -> Result<(Hash256, Vec<Hash256>), MerkleTreeError> {
|
|
let (root, mut proof) = self.tree.generate_proof(index, self.depth)?;
|
|
proof.push(Hash256::from_slice(&self.length_bytes()));
|
|
Ok((root, proof))
|
|
}
|
|
|
|
/// Add a deposit to the merkle tree.
|
|
pub fn push_leaf(&mut self, leaf: Hash256) -> Result<(), MerkleTreeError> {
|
|
self.tree.push_leaf(leaf, self.depth)?;
|
|
self.mix_in_length.safe_add_assign(1)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Finalize deposits up to `finalized_execution_block.deposit_count`
|
|
pub fn finalize(
|
|
&mut self,
|
|
finalized_execution_block: FinalizedExecutionBlock,
|
|
) -> Result<(), MerkleTreeError> {
|
|
self.tree
|
|
.finalize_deposits(finalized_execution_block.deposit_count as usize, self.depth)?;
|
|
self.finalized_execution_block = Some(finalized_execution_block);
|
|
Ok(())
|
|
}
|
|
|
|
/// Get snapshot of finalized deposit tree (if tree is finalized)
|
|
pub fn get_snapshot(&self) -> Option<DepositTreeSnapshot> {
|
|
let finalized_execution_block = self.finalized_execution_block.as_ref()?;
|
|
Some(DepositTreeSnapshot {
|
|
finalized: self.tree.get_finalized_hashes(),
|
|
deposit_root: finalized_execution_block.deposit_root,
|
|
deposit_count: finalized_execution_block.deposit_count,
|
|
execution_block_hash: finalized_execution_block.block_hash,
|
|
execution_block_height: finalized_execution_block.block_height,
|
|
})
|
|
}
|
|
|
|
/// Create a new Merkle tree from a snapshot
|
|
pub fn from_snapshot(
|
|
snapshot: &DepositTreeSnapshot,
|
|
depth: usize,
|
|
) -> Result<Self, MerkleTreeError> {
|
|
Ok(Self {
|
|
tree: MerkleTree::from_finalized_snapshot(
|
|
&snapshot.finalized,
|
|
snapshot.deposit_count as usize,
|
|
depth,
|
|
)?,
|
|
mix_in_length: snapshot.deposit_count as usize,
|
|
finalized_execution_block: Some(snapshot.into()),
|
|
depth,
|
|
})
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn print_tree(&self) {
|
|
self.tree.print_node(0);
|
|
println!("========================================================");
|
|
}
|
|
}
|