lighthouse/beacon_node/eth1/src/deposit_cache.rs
Divma ffbf70e2d9 Clippy lints for rust 1.66 (#3810)
## Issue Addressed
Fixes the new clippy lints for rust 1.66

## Proposed Changes

Most of the changes come from:
- [unnecessary_cast](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast)
- [iter_kv_map](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map)
- [needless_borrow](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow)

## Additional Info

na
2022-12-16 04:04:00 +00:00

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;
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,
"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);
}
}