e8604757a2
## 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` ![image](https://user-images.githubusercontent.com/37123614/151465302-5fc56284-8a69-4998-b20e-45db3934ac70.png) ## 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>
1094 lines
44 KiB
Rust
1094 lines
44 KiB
Rust
use crate::{DepositLog, Eth1Block};
|
|
use ssz_derive::{Decode, Encode};
|
|
use state_processing::common::DepositDataTree;
|
|
use std::cmp::Ordering;
|
|
use superstruct::superstruct;
|
|
use tree_hash::TreeHash;
|
|
use types::{Deposit, DepositTreeSnapshot, Hash256, DEPOSIT_TREE_DEPTH};
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum Error {
|
|
/// A deposit log was added when a prior deposit was not already in the cache.
|
|
///
|
|
/// Logs have to be added with monotonically-increasing block numbers.
|
|
NonConsecutive { log_index: u64, expected: usize },
|
|
/// The eth1 event log data was unable to be parsed.
|
|
LogParse(String),
|
|
/// There are insufficient deposits in the cache to fulfil the request.
|
|
InsufficientDeposits {
|
|
known_deposits: usize,
|
|
requested: u64,
|
|
},
|
|
/// A log with the given index is already present in the cache and it does not match the one
|
|
/// provided.
|
|
DuplicateDistinctLog(u64),
|
|
/// Attempted to insert log with given index after the log had been finalized
|
|
FinalizedLogInsert {
|
|
log_index: u64,
|
|
finalized_index: u64,
|
|
},
|
|
/// The deposit count must always be large enough to account for the requested deposit range.
|
|
///
|
|
/// E.g., you cannot request deposit 10 when the deposit count is 9.
|
|
DepositCountInvalid { deposit_count: u64, range_end: u64 },
|
|
/// You can't request deposits on or before the finalized deposit
|
|
DepositRangeInvalid {
|
|
range_start: u64,
|
|
finalized_count: u64,
|
|
},
|
|
/// You can't finalize what's already been finalized and the cache must have the logs
|
|
/// that you wish to finalize
|
|
InvalidFinalizeIndex {
|
|
requested_count: u64,
|
|
currently_finalized: u64,
|
|
deposit_count: u64,
|
|
},
|
|
/// Error with the merkle tree for deposits.
|
|
DepositTree(merkle_proof::MerkleTreeError),
|
|
/// An unexpected condition was encountered.
|
|
Internal(String),
|
|
/// This is for errors that should never occur
|
|
PleaseNotifyTheDevs,
|
|
}
|
|
|
|
pub type SszDepositCache = SszDepositCacheV13;
|
|
|
|
#[superstruct(
|
|
variants(V1, V13),
|
|
variant_attributes(derive(Encode, Decode, Clone)),
|
|
no_enum
|
|
)]
|
|
pub struct SszDepositCache {
|
|
pub logs: Vec<DepositLog>,
|
|
pub leaves: Vec<Hash256>,
|
|
pub deposit_contract_deploy_block: u64,
|
|
#[superstruct(only(V13))]
|
|
pub finalized_deposit_count: u64,
|
|
#[superstruct(only(V13))]
|
|
pub finalized_block_height: u64,
|
|
#[superstruct(only(V13))]
|
|
pub deposit_tree_snapshot: Option<DepositTreeSnapshot>,
|
|
pub deposit_roots: Vec<Hash256>,
|
|
}
|
|
|
|
impl SszDepositCache {
|
|
pub fn from_deposit_cache(cache: &DepositCache) -> Self {
|
|
Self {
|
|
logs: cache.logs.clone(),
|
|
leaves: cache.leaves.clone(),
|
|
deposit_contract_deploy_block: cache.deposit_contract_deploy_block,
|
|
finalized_deposit_count: cache.finalized_deposit_count,
|
|
finalized_block_height: cache.finalized_block_height,
|
|
deposit_tree_snapshot: cache.deposit_tree.get_snapshot(),
|
|
deposit_roots: cache.deposit_roots.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn to_deposit_cache(&self) -> Result<DepositCache, String> {
|
|
let deposit_tree = self
|
|
.deposit_tree_snapshot
|
|
.as_ref()
|
|
.map(|snapshot| {
|
|
let mut tree = DepositDataTree::from_snapshot(snapshot, DEPOSIT_TREE_DEPTH)
|
|
.map_err(|e| format!("Invalid SszDepositCache: {:?}", e))?;
|
|
for leaf in &self.leaves {
|
|
tree.push_leaf(*leaf).map_err(|e| {
|
|
format!("Invalid SszDepositCache: unable to push leaf: {:?}", e)
|
|
})?;
|
|
}
|
|
Ok::<_, String>(tree)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
// deposit_tree_snapshot = None (tree was never finalized)
|
|
// Create DepositDataTree from leaves
|
|
Ok(DepositDataTree::create(
|
|
&self.leaves,
|
|
self.leaves.len(),
|
|
DEPOSIT_TREE_DEPTH,
|
|
))
|
|
})?;
|
|
|
|
// Check for invalid SszDepositCache conditions
|
|
if self.leaves.len() != self.logs.len() {
|
|
return Err("Invalid SszDepositCache: logs and leaves should have equal length".into());
|
|
}
|
|
// `deposit_roots` also includes the zero root
|
|
if self.leaves.len() + 1 != self.deposit_roots.len() {
|
|
return Err(
|
|
"Invalid SszDepositCache: deposit_roots length must be only one more than leaves"
|
|
.into(),
|
|
);
|
|
}
|
|
Ok(DepositCache {
|
|
logs: self.logs.clone(),
|
|
leaves: self.leaves.clone(),
|
|
deposit_contract_deploy_block: self.deposit_contract_deploy_block,
|
|
finalized_deposit_count: self.finalized_deposit_count,
|
|
finalized_block_height: self.finalized_block_height,
|
|
deposit_tree,
|
|
deposit_roots: self.deposit_roots.clone(),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Mirrors the merkle tree of deposits in the eth1 deposit contract.
|
|
///
|
|
/// Provides `Deposit` objects with merkle proofs included.
|
|
#[cfg_attr(test, derive(PartialEq))]
|
|
pub struct DepositCache {
|
|
logs: Vec<DepositLog>,
|
|
leaves: Vec<Hash256>,
|
|
deposit_contract_deploy_block: u64,
|
|
finalized_deposit_count: u64,
|
|
finalized_block_height: u64,
|
|
/// An incremental merkle tree which represents the current state of the
|
|
/// deposit contract tree.
|
|
deposit_tree: DepositDataTree,
|
|
/// Vector of deposit roots. `deposit_roots[i]` denotes `deposit_root` at
|
|
/// `deposit_index` `i`.
|
|
deposit_roots: Vec<Hash256>,
|
|
}
|
|
|
|
impl Default for DepositCache {
|
|
fn default() -> Self {
|
|
let deposit_tree = DepositDataTree::create(&[], 0, DEPOSIT_TREE_DEPTH);
|
|
let deposit_roots = vec![deposit_tree.root()];
|
|
DepositCache {
|
|
logs: Vec::new(),
|
|
leaves: Vec::new(),
|
|
deposit_contract_deploy_block: 1,
|
|
finalized_deposit_count: 0,
|
|
finalized_block_height: 0,
|
|
deposit_tree,
|
|
deposit_roots,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum DepositCacheInsertOutcome {
|
|
Inserted,
|
|
Duplicate,
|
|
}
|
|
|
|
impl DepositCache {
|
|
/// Create new `DepositCache` given block number at which deposit
|
|
/// contract was deployed.
|
|
pub fn new(deposit_contract_deploy_block: u64) -> Self {
|
|
DepositCache {
|
|
deposit_contract_deploy_block,
|
|
finalized_block_height: deposit_contract_deploy_block.saturating_sub(1),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
pub fn from_deposit_snapshot(
|
|
deposit_contract_deploy_block: u64,
|
|
snapshot: &DepositTreeSnapshot,
|
|
) -> Result<Self, String> {
|
|
let deposit_tree = DepositDataTree::from_snapshot(snapshot, DEPOSIT_TREE_DEPTH)
|
|
.map_err(|e| format!("Invalid DepositSnapshot: {:?}", e))?;
|
|
Ok(DepositCache {
|
|
logs: Vec::new(),
|
|
leaves: Vec::new(),
|
|
deposit_contract_deploy_block,
|
|
finalized_deposit_count: snapshot.deposit_count,
|
|
finalized_block_height: snapshot.execution_block_height,
|
|
deposit_tree,
|
|
deposit_roots: vec![snapshot.deposit_root],
|
|
})
|
|
}
|
|
|
|
/// Returns the number of deposits the cache stores
|
|
pub fn len(&self) -> usize {
|
|
self.finalized_deposit_count as usize + self.logs.len()
|
|
}
|
|
|
|
/// True if the cache does not store any blocks.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.finalized_deposit_count != 0 && self.logs.is_empty()
|
|
}
|
|
|
|
/// Returns the block number for the most recent deposit in the cache.
|
|
pub fn latest_block_number(&self) -> u64 {
|
|
self.logs
|
|
.last()
|
|
.map(|log| log.block_number)
|
|
.unwrap_or(self.finalized_block_height)
|
|
}
|
|
|
|
/// Returns an iterator over all the logs in `self` that aren't finalized.
|
|
pub fn iter(&self) -> impl Iterator<Item = &DepositLog> {
|
|
self.logs.iter()
|
|
}
|
|
|
|
/// Returns the deposit log with INDEX i.
|
|
pub fn get_log(&self, i: usize) -> Option<&DepositLog> {
|
|
let finalized_deposit_count = self.finalized_deposit_count as usize;
|
|
if i < finalized_deposit_count {
|
|
None
|
|
} else {
|
|
self.logs.get(i - finalized_deposit_count)
|
|
}
|
|
}
|
|
|
|
/// Returns the deposit root with DEPOSIT COUNT (not index) i
|
|
pub fn get_root(&self, i: usize) -> Option<&Hash256> {
|
|
let finalized_deposit_count = self.finalized_deposit_count as usize;
|
|
if i < finalized_deposit_count {
|
|
None
|
|
} else {
|
|
self.deposit_roots.get(i - finalized_deposit_count)
|
|
}
|
|
}
|
|
|
|
/// Returns the finalized deposit count
|
|
pub fn finalized_deposit_count(&self) -> u64 {
|
|
self.finalized_deposit_count
|
|
}
|
|
|
|
/// Finalizes the cache up to `eth1_block.deposit_count`.
|
|
pub fn finalize(&mut self, eth1_block: Eth1Block) -> Result<(), Error> {
|
|
let deposits_to_finalize = eth1_block.deposit_count.ok_or_else(|| {
|
|
Error::Internal("Eth1Block did not contain deposit_count".to_string())
|
|
})?;
|
|
|
|
let currently_finalized = self.finalized_deposit_count;
|
|
if deposits_to_finalize > self.len() as u64 || deposits_to_finalize <= currently_finalized {
|
|
Err(Error::InvalidFinalizeIndex {
|
|
requested_count: deposits_to_finalize,
|
|
currently_finalized,
|
|
deposit_count: self.len() as u64,
|
|
})
|
|
} else {
|
|
let finalized_log = self
|
|
.get_log((deposits_to_finalize - 1) as usize)
|
|
.cloned()
|
|
.ok_or(Error::PleaseNotifyTheDevs)?;
|
|
let drop = (deposits_to_finalize - currently_finalized) as usize;
|
|
self.deposit_tree
|
|
.finalize(eth1_block.into())
|
|
.map_err(Error::DepositTree)?;
|
|
self.logs.drain(0..drop);
|
|
self.leaves.drain(0..drop);
|
|
self.deposit_roots.drain(0..drop);
|
|
self.finalized_deposit_count = deposits_to_finalize;
|
|
self.finalized_block_height = finalized_log.block_number;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Returns the deposit tree snapshot (if tree is finalized)
|
|
pub fn get_deposit_snapshot(&self) -> Option<DepositTreeSnapshot> {
|
|
self.deposit_tree.get_snapshot()
|
|
}
|
|
|
|
/// Adds `log` to self.
|
|
///
|
|
/// This function enforces that `logs` are imported one-by-one with no gaps between
|
|
/// `log.index`, starting at `log.index == 0`.
|
|
///
|
|
/// ## Errors
|
|
///
|
|
/// - If a log with index `log.index - 1` is not already present in `self` (ignored when empty).
|
|
/// - If a log with `log.index` is already known, but the given `log` is distinct to it.
|
|
pub fn insert_log(&mut self, log: DepositLog) -> Result<DepositCacheInsertOutcome, Error> {
|
|
match log.index.cmp(&(self.len() as u64)) {
|
|
Ordering::Equal => {
|
|
let deposit = log.deposit_data.tree_hash_root();
|
|
// should push to deposit_tree first because it's fallible
|
|
self.deposit_tree
|
|
.push_leaf(deposit)
|
|
.map_err(Error::DepositTree)?;
|
|
self.leaves.push(deposit);
|
|
self.logs.push(log);
|
|
self.deposit_roots.push(self.deposit_tree.root());
|
|
Ok(DepositCacheInsertOutcome::Inserted)
|
|
}
|
|
Ordering::Less => {
|
|
let mut compare_index = log.index as usize;
|
|
if log.index < self.finalized_deposit_count {
|
|
return Err(Error::FinalizedLogInsert {
|
|
log_index: log.index,
|
|
finalized_index: self.finalized_deposit_count - 1,
|
|
});
|
|
} else {
|
|
compare_index -= self.finalized_deposit_count as usize;
|
|
}
|
|
if self.logs[compare_index] == log {
|
|
Ok(DepositCacheInsertOutcome::Duplicate)
|
|
} else {
|
|
Err(Error::DuplicateDistinctLog(log.index))
|
|
}
|
|
}
|
|
Ordering::Greater => Err(Error::NonConsecutive {
|
|
log_index: log.index,
|
|
expected: self.logs.len(),
|
|
}),
|
|
}
|
|
}
|
|
|
|
/// Returns a list of `Deposit` objects, within the given deposit index `range`.
|
|
///
|
|
/// The `deposit_count` is used to generate the proofs for the `Deposits`. For example, if we
|
|
/// have 100 proofs, but the eth2 chain only acknowledges 50 of them, we must produce our
|
|
/// proofs with respect to a tree size of 50.
|
|
///
|
|
///
|
|
/// ## Errors
|
|
///
|
|
/// - If `deposit_count` is less than `end`.
|
|
/// - There are not sufficient deposits in the tree to generate the proof.
|
|
pub fn get_deposits(
|
|
&self,
|
|
start: u64,
|
|
end: u64,
|
|
deposit_count: u64,
|
|
) -> Result<(Hash256, Vec<Deposit>), Error> {
|
|
if deposit_count < end {
|
|
// It's invalid to ask for more deposits than should exist.
|
|
Err(Error::DepositCountInvalid {
|
|
deposit_count,
|
|
range_end: end,
|
|
})
|
|
} else if end > self.len() as u64 {
|
|
// The range of requested deposits exceeds the deposits stored locally.
|
|
Err(Error::InsufficientDeposits {
|
|
requested: end,
|
|
known_deposits: self.logs.len(),
|
|
})
|
|
} else if self.finalized_deposit_count > start {
|
|
// Can't ask for deposits before or on the finalized deposit
|
|
Err(Error::DepositRangeInvalid {
|
|
range_start: start,
|
|
finalized_count: self.finalized_deposit_count,
|
|
})
|
|
} else {
|
|
let (start, end, deposit_count) = (
|
|
start - self.finalized_deposit_count,
|
|
end - self.finalized_deposit_count,
|
|
deposit_count - self.finalized_deposit_count,
|
|
);
|
|
let leaves = self
|
|
.leaves
|
|
.get(0..deposit_count as usize)
|
|
.ok_or_else(|| Error::Internal("Unable to get known leaves".into()))?;
|
|
|
|
let tree = self
|
|
.deposit_tree
|
|
.get_snapshot()
|
|
.map(|snapshot| {
|
|
// The tree has already been finalized. So we can just start from the snapshot
|
|
// and replay the deposits up to `deposit_count`
|
|
let mut tree = DepositDataTree::from_snapshot(&snapshot, DEPOSIT_TREE_DEPTH)
|
|
.map_err(Error::DepositTree)?;
|
|
for leaf in leaves {
|
|
tree.push_leaf(*leaf).map_err(Error::DepositTree)?;
|
|
}
|
|
Ok(tree)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
// Deposit tree hasn't been finalized yet, will have to re-create the whole tree
|
|
Ok(DepositDataTree::create(
|
|
leaves,
|
|
leaves.len(),
|
|
DEPOSIT_TREE_DEPTH,
|
|
))
|
|
})?;
|
|
|
|
let mut deposits = vec![];
|
|
self.logs
|
|
.get(start as usize..end as usize)
|
|
.ok_or_else(|| Error::Internal("Unable to get known log".into()))?
|
|
.iter()
|
|
.try_for_each(|deposit_log| {
|
|
let (_leaf, proof) = tree
|
|
.generate_proof(deposit_log.index as usize)
|
|
.map_err(Error::DepositTree)?;
|
|
deposits.push(Deposit {
|
|
proof: proof.into(),
|
|
data: deposit_log.deposit_data.clone(),
|
|
});
|
|
Ok(())
|
|
})?;
|
|
|
|
Ok((tree.root(), deposits))
|
|
}
|
|
}
|
|
|
|
/// Returns the number of deposits with valid signatures that have been observed up to and
|
|
/// including the block at `block_number`.
|
|
///
|
|
/// Returns `None` if the `block_number` is zero or prior to contract deployment.
|
|
pub fn get_valid_signature_count(&self, block_number: u64) -> Option<usize> {
|
|
if block_number == 0 || block_number < self.deposit_contract_deploy_block {
|
|
None
|
|
} else {
|
|
Some(
|
|
self.logs
|
|
.iter()
|
|
.take_while(|deposit| deposit.block_number <= block_number)
|
|
.filter(|deposit| deposit.signature_is_valid)
|
|
.count(),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Returns the number of deposits that have been observed up to and
|
|
/// including the block at `block_number`.
|
|
///
|
|
/// Returns `None` if the `block_number` is zero or prior to contract deployment
|
|
/// or prior to last finalized deposit.
|
|
pub fn get_deposit_count_from_cache(&self, block_number: u64) -> Option<u64> {
|
|
if block_number == 0
|
|
|| block_number < self.deposit_contract_deploy_block
|
|
|| block_number < self.finalized_block_height
|
|
{
|
|
None
|
|
} else if block_number == self.finalized_block_height {
|
|
Some(self.finalized_deposit_count)
|
|
} else {
|
|
Some(
|
|
self.finalized_deposit_count
|
|
+ self
|
|
.logs
|
|
.iter()
|
|
.take_while(|deposit| deposit.block_number <= block_number)
|
|
.count() as u64,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Gets the deposit root at block height = block_number.
|
|
///
|
|
/// Fetches the `deposit_count` on or just before the queried `block_number`
|
|
/// and queries the `deposit_roots` map to get the corresponding `deposit_root`.
|
|
pub fn get_deposit_root_from_cache(&self, block_number: u64) -> Option<Hash256> {
|
|
let count = self.get_deposit_count_from_cache(block_number)?;
|
|
self.get_root(count as usize).cloned()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod tests {
|
|
use super::*;
|
|
use execution_layer::http::deposit_log::Log;
|
|
use types::{EthSpec, MainnetEthSpec};
|
|
|
|
/// The data from a deposit event, using the v0.8.3 version of the deposit contract.
|
|
pub const EXAMPLE_LOG: &[u8] = &[
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 1, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 167, 108, 6, 69, 88, 17, 3, 51, 6, 4, 158, 232, 82,
|
|
248, 218, 2, 71, 219, 55, 102, 86, 125, 136, 203, 36, 77, 64, 213, 43, 52, 175, 154, 239,
|
|
50, 142, 52, 201, 77, 54, 239, 0, 229, 22, 46, 139, 120, 62, 240, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 64, 89, 115, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 140, 74, 175, 158, 209, 20, 206,
|
|
30, 63, 215, 238, 113, 60, 132, 216, 211, 100, 186, 202, 71, 34, 200, 160, 225, 212, 213,
|
|
119, 88, 51, 80, 101, 74, 2, 45, 78, 153, 12, 192, 44, 51, 77, 40, 10, 72, 246, 34, 193,
|
|
187, 22, 95, 4, 211, 245, 224, 13, 162, 21, 163, 54, 225, 22, 124, 3, 56, 14, 81, 122, 189,
|
|
149, 250, 251, 159, 22, 77, 94, 157, 197, 196, 253, 110, 201, 88, 193, 246, 136, 226, 221,
|
|
18, 113, 232, 105, 100, 114, 103, 237, 189, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
];
|
|
|
|
fn example_log() -> DepositLog {
|
|
let spec = MainnetEthSpec::default_spec();
|
|
|
|
let log = Log {
|
|
block_number: 42,
|
|
data: EXAMPLE_LOG.to_vec(),
|
|
};
|
|
log.to_deposit_log(&spec).expect("should decode log")
|
|
}
|
|
|
|
fn get_cache_with_deposits(n: u64) -> DepositCache {
|
|
let mut deposit_cache = DepositCache::default();
|
|
for i in 0..n {
|
|
let mut log = example_log();
|
|
log.index = i;
|
|
log.block_number = i;
|
|
log.deposit_data.withdrawal_credentials = Hash256::from_low_u64_be(i);
|
|
deposit_cache
|
|
.insert_log(log)
|
|
.expect("should add consecutive logs");
|
|
}
|
|
assert_eq!(deposit_cache.len() as u64, n, "should have {} deposits", n);
|
|
|
|
deposit_cache
|
|
}
|
|
|
|
#[test]
|
|
fn insert_log_valid() {
|
|
let mut deposit_cache = DepositCache::default();
|
|
|
|
for i in 0..16 {
|
|
let mut log = example_log();
|
|
log.index = i;
|
|
deposit_cache
|
|
.insert_log(log)
|
|
.expect("should add consecutive logs");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn insert_log_invalid() {
|
|
let mut deposit_cache = DepositCache::default();
|
|
|
|
for i in 0..4 {
|
|
let mut log = example_log();
|
|
log.index = i;
|
|
deposit_cache
|
|
.insert_log(log)
|
|
.expect("should add consecutive logs");
|
|
}
|
|
|
|
// Add duplicate, when given is the same as the one known.
|
|
let mut log = example_log();
|
|
log.index = 3;
|
|
assert_eq!(
|
|
deposit_cache.insert_log(log).unwrap(),
|
|
DepositCacheInsertOutcome::Duplicate
|
|
);
|
|
|
|
// Add duplicate, when given is different to the one known.
|
|
let mut log = example_log();
|
|
log.index = 3;
|
|
log.block_number = 99;
|
|
assert!(deposit_cache.insert_log(log).is_err());
|
|
|
|
// Skip inserting a log.
|
|
let mut log = example_log();
|
|
log.index = 5;
|
|
assert!(deposit_cache.insert_log(log).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn get_deposit_valid() {
|
|
let n = 1_024;
|
|
let deposit_cache = get_cache_with_deposits(n);
|
|
|
|
// Get 0 deposits, with max deposit count.
|
|
let (_, deposits) = deposit_cache
|
|
.get_deposits(0, 0, n)
|
|
.expect("should get the full tree");
|
|
assert_eq!(deposits.len(), 0, "should return no deposits");
|
|
|
|
// Get 0 deposits, with 0 deposit count.
|
|
let (_, deposits) = deposit_cache
|
|
.get_deposits(0, 0, 0)
|
|
.expect("should get the full tree");
|
|
assert_eq!(deposits.len(), 0, "should return no deposits");
|
|
|
|
// Get all deposits, with max deposit count.
|
|
let (full_root, deposits) = deposit_cache
|
|
.get_deposits(0, n, n)
|
|
.expect("should get the full tree");
|
|
assert_eq!(deposits.len(), n as usize, "should return all deposits");
|
|
|
|
// Get 4 deposits, with max deposit count.
|
|
let (root, deposits) = deposit_cache
|
|
.get_deposits(0, 4, n)
|
|
.expect("should get the four from the full tree");
|
|
assert_eq!(
|
|
deposits.len(),
|
|
4_usize,
|
|
"should get 4 deposits from full tree"
|
|
);
|
|
assert_eq!(
|
|
root, full_root,
|
|
"should still return full root when getting deposit subset"
|
|
);
|
|
|
|
// Get half of the deposits, with half deposit count.
|
|
let half = n / 2;
|
|
let (half_root, deposits) = deposit_cache
|
|
.get_deposits(0, half, half)
|
|
.expect("should get the half tree");
|
|
assert_eq!(deposits.len(), half as usize, "should return half deposits");
|
|
|
|
// Get 4 deposits, with half deposit count.
|
|
let (root, deposits) = deposit_cache
|
|
.get_deposits(0, 4, n / 2)
|
|
.expect("should get the half tree");
|
|
assert_eq!(
|
|
deposits.len(),
|
|
4_usize,
|
|
"should get 4 deposits from half tree"
|
|
);
|
|
assert_eq!(
|
|
root, half_root,
|
|
"should still return half root when getting deposit subset"
|
|
);
|
|
assert_ne!(
|
|
full_root, half_root,
|
|
"should get different root when pinning deposit count"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn get_deposit_invalid() {
|
|
let n = 16;
|
|
let mut tree = get_cache_with_deposits(n);
|
|
|
|
// Range too high.
|
|
assert!(tree.get_deposits(0, n + 1, n).is_err());
|
|
|
|
// Count too high.
|
|
assert!(tree.get_deposits(0, n, n + 1).is_err());
|
|
|
|
// Range higher than count.
|
|
assert!(tree.get_deposits(0, 4, 2).is_err());
|
|
|
|
let block7 = fake_eth1_block(&tree, 7).expect("should create fake eth1 block");
|
|
tree.finalize(block7).expect("should finalize");
|
|
// Range starts <= finalized deposit
|
|
assert!(tree.get_deposits(6, 9, 11).is_err());
|
|
assert!(tree.get_deposits(7, 9, 11).is_err());
|
|
// Range start > finalized deposit should be OK
|
|
assert!(tree.get_deposits(8, 9, 11).is_ok());
|
|
}
|
|
|
|
// returns an eth1 block that can be used to finalize the cache at `deposit_index`
|
|
// this will ensure the `deposit_root` on the `Eth1Block` is correct
|
|
fn fake_eth1_block(deposit_cache: &DepositCache, deposit_index: usize) -> Option<Eth1Block> {
|
|
let deposit_log = deposit_cache.get_log(deposit_index)?;
|
|
Some(Eth1Block {
|
|
hash: Hash256::from_low_u64_be(deposit_log.block_number),
|
|
timestamp: 0,
|
|
number: deposit_log.block_number,
|
|
deposit_root: deposit_cache.get_root(deposit_index + 1).cloned(),
|
|
deposit_count: Some(deposit_log.index + 1),
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn test_finalization_boundaries() {
|
|
let n = 8;
|
|
let half = (n / 2) as usize;
|
|
|
|
let mut deposit_cache = get_cache_with_deposits(n as u64);
|
|
|
|
let full_root_before_finalization = deposit_cache.deposit_tree.root();
|
|
let half_log_plus1_before_finalization = deposit_cache
|
|
.get_log(half + 1)
|
|
.expect("log should exist")
|
|
.clone();
|
|
let half_root_plus1_before_finalization =
|
|
*deposit_cache.get_root(half + 1).expect("root should exist");
|
|
|
|
let (root_before_finalization, proof_before_finalization) = deposit_cache
|
|
.get_deposits((half + 1) as u64, (half + 2) as u64, (half + 2) as u64)
|
|
.expect("should return 1 deposit with proof");
|
|
|
|
// finalize on the tree at half
|
|
let half_block =
|
|
fake_eth1_block(&deposit_cache, half).expect("fake block should be created");
|
|
assert!(
|
|
deposit_cache.get_deposit_snapshot().is_none(),
|
|
"snapshot should not exist as tree has not been finalized"
|
|
);
|
|
deposit_cache
|
|
.finalize(half_block)
|
|
.expect("tree should_finalize");
|
|
|
|
// check boundary conditions for get_log
|
|
assert!(
|
|
deposit_cache.get_log(half).is_none(),
|
|
"log at finalized deposit should NOT exist"
|
|
);
|
|
assert_eq!(
|
|
*deposit_cache.get_log(half + 1).expect("log should exist"),
|
|
half_log_plus1_before_finalization,
|
|
"log after finalized deposit should match before finalization"
|
|
);
|
|
// check boundary conditions for get_root
|
|
assert!(
|
|
deposit_cache.get_root(half).is_none(),
|
|
"root at finalized deposit should NOT exist"
|
|
);
|
|
assert_eq!(
|
|
*deposit_cache.get_root(half + 1).expect("root should exist"),
|
|
half_root_plus1_before_finalization,
|
|
"root after finalized deposit should match before finalization"
|
|
);
|
|
// full root should match before and after finalization
|
|
assert_eq!(
|
|
deposit_cache.deposit_tree.root(),
|
|
full_root_before_finalization,
|
|
"full root should match before and after finalization"
|
|
);
|
|
// check boundary conditions for get_deposits (proof)
|
|
assert!(
|
|
deposit_cache
|
|
.get_deposits(half as u64, (half + 1) as u64, (half + 1) as u64)
|
|
.is_err(),
|
|
"cannot prove the finalized deposit"
|
|
);
|
|
let (root_after_finalization, proof_after_finalization) = deposit_cache
|
|
.get_deposits((half + 1) as u64, (half + 2) as u64, (half + 2) as u64)
|
|
.expect("should return 1 deposit with proof");
|
|
assert_eq!(
|
|
root_before_finalization, root_after_finalization,
|
|
"roots before and after finalization should match"
|
|
);
|
|
assert_eq!(
|
|
proof_before_finalization, proof_after_finalization,
|
|
"proof before and after finalization should match"
|
|
);
|
|
|
|
// recover tree from snapshot by replaying deposits
|
|
let snapshot = deposit_cache
|
|
.get_deposit_snapshot()
|
|
.expect("snapshot should exist");
|
|
let mut recovered = DepositCache::from_deposit_snapshot(1, &snapshot)
|
|
.expect("should recover finalized tree");
|
|
for i in half + 1..n {
|
|
let mut log = example_log();
|
|
log.index = i as u64;
|
|
log.block_number = i as u64;
|
|
log.deposit_data.withdrawal_credentials = Hash256::from_low_u64_be(i as u64);
|
|
recovered
|
|
.insert_log(log)
|
|
.expect("should add consecutive logs");
|
|
}
|
|
|
|
// check the same boundary conditions above for the recovered tree
|
|
assert!(
|
|
recovered.get_log(half).is_none(),
|
|
"log at finalized deposit should NOT exist"
|
|
);
|
|
assert_eq!(
|
|
*recovered.get_log(half + 1).expect("log should exist"),
|
|
half_log_plus1_before_finalization,
|
|
"log after finalized deposit should match before finalization in recovered tree"
|
|
);
|
|
// check boundary conditions for get_root
|
|
assert!(
|
|
recovered.get_root(half).is_none(),
|
|
"root at finalized deposit should NOT exist"
|
|
);
|
|
assert_eq!(
|
|
*recovered.get_root(half + 1).expect("root should exist"),
|
|
half_root_plus1_before_finalization,
|
|
"root after finalized deposit should match before finalization in recovered tree"
|
|
);
|
|
// full root should match before and after finalization
|
|
assert_eq!(
|
|
recovered.deposit_tree.root(),
|
|
full_root_before_finalization,
|
|
"full root should match before and after finalization"
|
|
);
|
|
// check boundary conditions for get_deposits (proof)
|
|
assert!(
|
|
recovered
|
|
.get_deposits(half as u64, (half + 1) as u64, (half + 1) as u64)
|
|
.is_err(),
|
|
"cannot prove the finalized deposit"
|
|
);
|
|
let (recovered_root_after_finalization, recovered_proof_after_finalization) = recovered
|
|
.get_deposits((half + 1) as u64, (half + 2) as u64, (half + 2) as u64)
|
|
.expect("should return 1 deposit with proof");
|
|
assert_eq!(
|
|
root_before_finalization, recovered_root_after_finalization,
|
|
"recovered roots before and after finalization should match"
|
|
);
|
|
assert_eq!(
|
|
proof_before_finalization, recovered_proof_after_finalization,
|
|
"recovered proof before and after finalization should match"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_finalization() {
|
|
let n = 1024;
|
|
let half = n / 2;
|
|
let quarter = half / 2;
|
|
let mut deposit_cache = get_cache_with_deposits(n);
|
|
|
|
let full_root_before_finalization = deposit_cache.deposit_tree.root();
|
|
let q3_root_before_finalization = deposit_cache
|
|
.get_root((half + quarter) as usize)
|
|
.cloned()
|
|
.expect("root should exist");
|
|
let q3_log_before_finalization = deposit_cache
|
|
.get_log((half + quarter) as usize)
|
|
.cloned()
|
|
.expect("log should exist");
|
|
// get_log(half+quarter) should return log with index `half+quarter`
|
|
assert_eq!(
|
|
q3_log_before_finalization.index,
|
|
(half + quarter) as u64,
|
|
"log index should be {}",
|
|
(half + quarter),
|
|
);
|
|
|
|
// get lower quarter of deposits with max deposit count
|
|
let (lower_quarter_root_before_finalization, lower_quarter_deposits_before_finalization) =
|
|
deposit_cache
|
|
.get_deposits(quarter, half, n)
|
|
.expect("should get lower quarter");
|
|
assert_eq!(
|
|
lower_quarter_deposits_before_finalization.len(),
|
|
quarter as usize,
|
|
"should get {} deposits from lower quarter",
|
|
quarter,
|
|
);
|
|
// since the lower quarter was done with full deposits, root should be the same as full_root_before_finalization
|
|
assert_eq!(
|
|
lower_quarter_root_before_finalization, full_root_before_finalization,
|
|
"should still get full root with deposit subset",
|
|
);
|
|
|
|
// get upper quarter of deposits with slightly reduced deposit count
|
|
let (upper_quarter_root_before_finalization, upper_quarter_deposits_before_finalization) =
|
|
deposit_cache
|
|
.get_deposits(half, half + quarter, n - 2)
|
|
.expect("should get upper quarter");
|
|
assert_eq!(
|
|
upper_quarter_deposits_before_finalization.len(),
|
|
quarter as usize,
|
|
"should get {} deposits from upper quarter",
|
|
quarter,
|
|
);
|
|
// since upper quarter was with subset of nodes, it should differ from full root
|
|
assert_ne!(
|
|
full_root_before_finalization, upper_quarter_root_before_finalization,
|
|
"subtree root should differ from full root",
|
|
);
|
|
|
|
let f0_log = deposit_cache
|
|
.get_log((quarter - 1) as usize)
|
|
.cloned()
|
|
.expect("should return log");
|
|
let f0_block = fake_eth1_block(&deposit_cache, (quarter - 1) as usize)
|
|
.expect("fake eth1 block should be created");
|
|
|
|
// finalize first quarter
|
|
deposit_cache
|
|
.finalize(f0_block)
|
|
.expect("should finalize first quarter");
|
|
// finalized count and block number should match log
|
|
assert_eq!(
|
|
deposit_cache.finalized_deposit_count,
|
|
f0_log.index + 1,
|
|
"after calling finalize(eth1block) finalized_deposit_count should equal eth1_block.deposit_count",
|
|
);
|
|
assert_eq!(
|
|
deposit_cache.finalized_block_height,
|
|
f0_log.block_number,
|
|
"after calling finalize(eth1block) finalized_block_number should equal eth1block.block_number"
|
|
);
|
|
// check get_log boundaries
|
|
assert!(
|
|
deposit_cache.get_log((quarter - 1) as usize).is_none(),
|
|
"get_log() should return None for index <= finalized log index",
|
|
);
|
|
assert!(
|
|
deposit_cache.get_log(quarter as usize).is_some(),
|
|
"get_log() should return Some(log) for index >= finalized_deposit_count",
|
|
);
|
|
|
|
// full root should remain the same after finalization
|
|
assert_eq!(
|
|
full_root_before_finalization,
|
|
deposit_cache.deposit_tree.root(),
|
|
"root should be the same before and after finalization",
|
|
);
|
|
// get_root should return the same root before and after finalization
|
|
assert_eq!(
|
|
q3_root_before_finalization,
|
|
deposit_cache
|
|
.get_root((half + quarter) as usize)
|
|
.cloned()
|
|
.expect("root should exist"),
|
|
"get_root should return the same root before and after finalization",
|
|
);
|
|
// get_log should return the same log before and after finalization
|
|
assert_eq!(
|
|
q3_log_before_finalization,
|
|
deposit_cache
|
|
.get_log((half + quarter) as usize)
|
|
.cloned()
|
|
.expect("log should exist"),
|
|
"get_log should return the same log before and after finalization",
|
|
);
|
|
|
|
// again get lower quarter of deposits with max deposit count after finalization
|
|
let (f0_lower_quarter_root, f0_lower_quarter_deposits) = deposit_cache
|
|
.get_deposits(quarter, half, n)
|
|
.expect("should get lower quarter");
|
|
assert_eq!(
|
|
f0_lower_quarter_deposits.len(),
|
|
quarter as usize,
|
|
"should get {} deposits from lower quarter",
|
|
quarter,
|
|
);
|
|
// again get upper quarter of deposits with slightly reduced deposit count after finalization
|
|
let (f0_upper_quarter_root, f0_upper_quarter_deposits) = deposit_cache
|
|
.get_deposits(half, half + quarter, n - 2)
|
|
.expect("should get upper quarter");
|
|
assert_eq!(
|
|
f0_upper_quarter_deposits.len(),
|
|
quarter as usize,
|
|
"should get {} deposits from upper quarter",
|
|
quarter,
|
|
);
|
|
|
|
// lower quarter root and deposits should be the same
|
|
assert_eq!(
|
|
lower_quarter_root_before_finalization, f0_lower_quarter_root,
|
|
"root should be the same before and after finalization",
|
|
);
|
|
for i in 0..lower_quarter_deposits_before_finalization.len() {
|
|
assert_eq!(
|
|
lower_quarter_deposits_before_finalization[i], f0_lower_quarter_deposits[i],
|
|
"get_deposits() should be the same before and after finalization",
|
|
);
|
|
}
|
|
// upper quarter root and deposits should be the same
|
|
assert_eq!(
|
|
upper_quarter_root_before_finalization, f0_upper_quarter_root,
|
|
"subtree root should be the same before and after finalization",
|
|
);
|
|
for i in 0..upper_quarter_deposits_before_finalization.len() {
|
|
assert_eq!(
|
|
upper_quarter_deposits_before_finalization[i], f0_upper_quarter_deposits[i],
|
|
"get_deposits() should be the same before and after finalization",
|
|
);
|
|
}
|
|
|
|
let f1_log = deposit_cache
|
|
.get_log((half - 2) as usize)
|
|
.cloned()
|
|
.expect("should return log");
|
|
// finalize a little less than half to test multiple finalization
|
|
let f1_block = fake_eth1_block(&deposit_cache, (half - 2) as usize)
|
|
.expect("should create fake eth1 block");
|
|
deposit_cache
|
|
.finalize(f1_block)
|
|
.expect("should finalize a little less than half");
|
|
// finalized count and block number should match f1_log
|
|
assert_eq!(
|
|
deposit_cache.finalized_deposit_count,
|
|
f1_log.index + 1,
|
|
"after calling finalize(eth1block) finalized_deposit_count should equal eth1_block.deposit_count",
|
|
);
|
|
assert_eq!(
|
|
deposit_cache.finalized_block_height,
|
|
f1_log.block_number,
|
|
"after calling finalize(eth1block) finalized_block_number should equal eth1block.block_number"
|
|
);
|
|
// check get_log boundaries
|
|
assert!(
|
|
deposit_cache.get_log((half - 2) as usize).is_none(),
|
|
"get_log() should return None for index <= finalized log index",
|
|
);
|
|
assert!(
|
|
deposit_cache.get_log((half - 1) as usize).is_some(),
|
|
"get_log() should return Some(log) for index >= finalized_deposit_count",
|
|
);
|
|
|
|
// full root should still be unchanged
|
|
assert_eq!(
|
|
full_root_before_finalization,
|
|
deposit_cache.deposit_tree.root(),
|
|
"root should be the same before and after finalization",
|
|
);
|
|
|
|
// again get upper quarter of deposits with slightly reduced deposit count after second finalization
|
|
let (f1_upper_quarter_root, f1_upper_quarter_deposits) = deposit_cache
|
|
.get_deposits(half, half + quarter, n - 2)
|
|
.expect("should get upper quarter");
|
|
|
|
// upper quarter root and deposits should be the same after second finalization
|
|
assert_eq!(
|
|
f0_upper_quarter_root, f1_upper_quarter_root,
|
|
"subtree root should be the same after multiple finalization",
|
|
);
|
|
for i in 0..f0_upper_quarter_deposits.len() {
|
|
assert_eq!(
|
|
f0_upper_quarter_deposits[i], f1_upper_quarter_deposits[i],
|
|
"get_deposits() should be the same before and after finalization",
|
|
);
|
|
}
|
|
}
|
|
|
|
fn verify_equality(original: &DepositCache, copy: &DepositCache) {
|
|
// verify each field individually so that if one field should
|
|
// fail to recover, this test will point right to it
|
|
assert_eq!(original.deposit_contract_deploy_block, copy.deposit_contract_deploy_block, "DepositCache: deposit_contract_deploy_block should remain the same after encoding and decoding from ssz" );
|
|
assert_eq!(
|
|
original.leaves, copy.leaves,
|
|
"DepositCache: leaves should remain the same after encoding and decoding from ssz"
|
|
);
|
|
assert_eq!(
|
|
original.logs, copy.logs,
|
|
"DepositCache: logs should remain the same after encoding and decoding from ssz"
|
|
);
|
|
assert_eq!(original.finalized_deposit_count, copy.finalized_deposit_count, "DepositCache: finalized_deposit_count should remain the same after encoding and decoding from ssz");
|
|
assert_eq!(original.finalized_block_height, copy.finalized_block_height, "DepositCache: finalized_block_height should remain the same after encoding and decoding from ssz");
|
|
assert_eq!(original.deposit_roots, copy.deposit_roots, "DepositCache: deposit_roots should remain the same before and after encoding and decoding from ssz");
|
|
assert!(original.deposit_tree == copy.deposit_tree, "DepositCache: deposit_tree should remain the same before and after encoding and decoding from ssz");
|
|
// verify all together for good measure
|
|
assert!(
|
|
original == copy,
|
|
"Deposit cache should remain the same after encoding and decoding from ssz"
|
|
);
|
|
}
|
|
|
|
fn ssz_round_trip(original: &DepositCache) -> DepositCache {
|
|
use ssz::{Decode, Encode};
|
|
let bytes = SszDepositCache::from_deposit_cache(original).as_ssz_bytes();
|
|
let ssz_cache =
|
|
SszDepositCache::from_ssz_bytes(&bytes).expect("should decode from ssz bytes");
|
|
|
|
SszDepositCache::to_deposit_cache(&ssz_cache).expect("should recover cache")
|
|
}
|
|
|
|
#[test]
|
|
fn ssz_encode_decode() {
|
|
let deposit_cache = get_cache_with_deposits(512);
|
|
let recovered_cache = ssz_round_trip(&deposit_cache);
|
|
|
|
verify_equality(&deposit_cache, &recovered_cache);
|
|
}
|
|
|
|
#[test]
|
|
fn ssz_encode_decode_with_finalization() {
|
|
let mut deposit_cache = get_cache_with_deposits(512);
|
|
let block383 = fake_eth1_block(&deposit_cache, 383).expect("should create fake eth1 block");
|
|
deposit_cache.finalize(block383).expect("should finalize");
|
|
let mut first_recovery = ssz_round_trip(&deposit_cache);
|
|
|
|
verify_equality(&deposit_cache, &first_recovery);
|
|
// finalize again to verify equality after multiple finalizations
|
|
let block447 = fake_eth1_block(&deposit_cache, 447).expect("should create fake eth1 block");
|
|
first_recovery.finalize(block447).expect("should finalize");
|
|
|
|
let mut second_recovery = ssz_round_trip(&first_recovery);
|
|
verify_equality(&first_recovery, &second_recovery);
|
|
|
|
// verify equality of a tree that finalized block383, block447, block479
|
|
// with a tree that finalized block383, block479
|
|
let block479 = fake_eth1_block(&deposit_cache, 479).expect("should create fake eth1 block");
|
|
second_recovery
|
|
.finalize(block479.clone())
|
|
.expect("should finalize");
|
|
let third_recovery = ssz_round_trip(&second_recovery);
|
|
deposit_cache.finalize(block479).expect("should finalize");
|
|
|
|
verify_equality(&deposit_cache, &third_recovery);
|
|
}
|
|
}
|