diff --git a/Cargo.lock b/Cargo.lock index de70b4057e..8358e1d8b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2085,6 +2085,7 @@ dependencies = [ "task_executor", "tokio", "tree_hash", + "tree_hash_derive", "types", "warp", ] diff --git a/beacon_node/execution_layer/Cargo.toml b/beacon_node/execution_layer/Cargo.toml index bff2ff9b07..cf6a4c822b 100644 --- a/beacon_node/execution_layer/Cargo.toml +++ b/beacon_node/execution_layer/Cargo.toml @@ -26,3 +26,4 @@ eth2_ssz_types = { path = "../../consensus/ssz_types"} lru = "0.6.0" exit-future = "0.2.0" tree_hash = { path = "../../consensus/tree_hash"} +tree_hash_derive = { path = "../../consensus/tree_hash_derive"} diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 4d34628f95..c2bc593c94 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -107,6 +107,7 @@ pub enum BlockByNumberQuery<'a> { #[serde(rename_all = "camelCase")] pub struct ExecutionBlock { pub block_hash: Hash256, + pub block_number: u64, pub parent_hash: Hash256, pub total_difficulty: Uint256, } diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 2173acf355..002020a9f4 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -410,7 +410,7 @@ impl ExecutionLayer { #[cfg(test)] mod test { use super::*; - use crate::test_utils::{block_number_to_hash, MockServer, DEFAULT_TERMINAL_DIFFICULTY}; + use crate::test_utils::{MockServer, DEFAULT_TERMINAL_DIFFICULTY}; use environment::null_logger; use types::MainnetEthSpec; @@ -424,6 +424,7 @@ mod test { impl SingleEngineTester { pub fn new() -> Self { let server = MockServer::unit_testing(); + let url = SensitiveUrl::parse(&server.url()).unwrap(); let log = null_logger().unwrap(); @@ -457,26 +458,37 @@ mod test { } pub async fn move_to_block_prior_to_terminal_block(self) -> Self { - { - let mut block_gen = self.server.execution_block_generator().await; - let target_block = block_gen.terminal_block_number.checked_sub(1).unwrap(); - block_gen.set_clock_for_block_number(target_block) - } - self + let target_block = { + let block_gen = self.server.execution_block_generator().await; + block_gen.terminal_block_number.checked_sub(1).unwrap() + }; + self.move_to_pow_block(target_block).await } pub async fn move_to_terminal_block(self) -> Self { + let target_block = { + let block_gen = self.server.execution_block_generator().await; + block_gen.terminal_block_number + }; + self.move_to_pow_block(target_block).await + } + + pub async fn move_to_pow_block(self, target_block: u64) -> Self { { let mut block_gen = self.server.execution_block_generator().await; - let target_block = block_gen.terminal_block_number; - block_gen.set_clock_for_block_number(target_block) + let next_block = block_gen.latest_block().unwrap().block_number() + 1; + assert!(target_block >= next_block); + + block_gen + .insert_pow_blocks(next_block..=target_block) + .unwrap(); } self } - pub async fn with_terminal_block_number<'a, T, U>(self, func: T) -> Self + pub async fn with_terminal_block<'a, T, U>(self, func: T) -> Self where - T: Fn(ExecutionLayer, u64) -> U, + T: Fn(ExecutionLayer, Option) -> U, U: Future, { let terminal_block_number = self @@ -484,7 +496,13 @@ mod test { .execution_block_generator() .await .terminal_block_number; - func(self.el.clone(), terminal_block_number).await; + let terminal_block = self + .server + .execution_block_generator() + .await + .execution_block_by_number(terminal_block_number); + + func(self.el.clone(), terminal_block).await; self } @@ -506,7 +524,7 @@ mod test { SingleEngineTester::new() .move_to_block_prior_to_terminal_block() .await - .with_terminal_block_number(|el, _| async move { + .with_terminal_block(|el, _| async move { assert_eq!( el.get_pow_block_hash_at_total_difficulty().await.unwrap(), None @@ -515,10 +533,10 @@ mod test { .await .move_to_terminal_block() .await - .with_terminal_block_number(|el, terminal_block_number| async move { + .with_terminal_block(|el, terminal_block| async move { assert_eq!( el.get_pow_block_hash_at_total_difficulty().await.unwrap(), - Some(block_number_to_hash(terminal_block_number)) + Some(terminal_block.unwrap().block_hash) ) }) .await; @@ -529,13 +547,11 @@ mod test { SingleEngineTester::new() .move_to_terminal_block() .await - .with_terminal_block_number(|el, terminal_block_number| async move { + .with_terminal_block(|el, terminal_block| async move { assert_eq!( - el.is_valid_terminal_pow_block_hash(block_number_to_hash( - terminal_block_number - )) - .await - .unwrap(), + el.is_valid_terminal_pow_block_hash(terminal_block.unwrap().block_hash) + .await + .unwrap(), Some(true) ) }) @@ -547,15 +563,13 @@ mod test { SingleEngineTester::new() .move_to_terminal_block() .await - .with_terminal_block_number(|el, terminal_block_number| async move { - let invalid_terminal_block = terminal_block_number.checked_sub(1).unwrap(); + .with_terminal_block(|el, terminal_block| async move { + let invalid_terminal_block = terminal_block.unwrap().parent_hash; assert_eq!( - el.is_valid_terminal_pow_block_hash(block_number_to_hash( - invalid_terminal_block - )) - .await - .unwrap(), + el.is_valid_terminal_pow_block_hash(invalid_terminal_block) + .await + .unwrap(), Some(false) ) }) @@ -567,15 +581,13 @@ mod test { SingleEngineTester::new() .move_to_terminal_block() .await - .with_terminal_block_number(|el, terminal_block_number| async move { - let missing_terminal_block = terminal_block_number.checked_add(1).unwrap(); + .with_terminal_block(|el, _| async move { + let missing_terminal_block = Hash256::repeat_byte(42); assert_eq!( - el.is_valid_terminal_pow_block_hash(block_number_to_hash( - missing_terminal_block - )) - .await - .unwrap(), + el.is_valid_terminal_pow_block_hash(missing_terminal_block) + .await + .unwrap(), None ) }) diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 5c49e96eea..523d439647 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -1,13 +1,67 @@ -use crate::engine_api::{http::JsonPreparePayloadRequest, ExecutePayloadResponse}; +use crate::engine_api::{http::JsonPreparePayloadRequest, ExecutePayloadResponse, ExecutionBlock}; use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; use std::collections::HashMap; use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; use types::{EthSpec, ExecutionPayload, Hash256, Uint256}; -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq)] +#[allow(clippy::large_enum_variant)] // This struct is only for testing. +pub enum Block { + PoW(PoWBlock), + PoS(ExecutionPayload), +} + +impl Block { + pub fn block_number(&self) -> u64 { + match self { + Block::PoW(block) => block.block_number, + Block::PoS(payload) => payload.block_number, + } + } + + pub fn parent_hash(&self) -> Hash256 { + match self { + Block::PoW(block) => block.parent_hash, + Block::PoS(payload) => payload.parent_hash, + } + } + + pub fn block_hash(&self) -> Hash256 { + match self { + Block::PoW(block) => block.block_hash, + Block::PoS(payload) => payload.block_hash, + } + } + + pub fn total_difficulty(&self) -> Option { + match self { + Block::PoW(block) => Some(block.total_difficulty), + Block::PoS(_) => None, + } + } + + pub fn as_execution_block(&self, total_difficulty: u64) -> ExecutionBlock { + match self { + Block::PoW(block) => ExecutionBlock { + block_hash: block.block_hash, + block_number: block.block_number, + parent_hash: block.parent_hash, + total_difficulty: block.total_difficulty, + }, + Block::PoS(payload) => ExecutionBlock { + block_hash: payload.block_hash, + block_number: payload.block_number, + parent_hash: payload.parent_hash, + total_difficulty: total_difficulty.into(), + }, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, TreeHash)] #[serde(rename_all = "camelCase")] -pub struct Block { +pub struct PoWBlock { pub block_number: u64, pub block_hash: Hash256, pub parent_hash: Hash256, @@ -15,113 +69,145 @@ pub struct Block { } pub struct ExecutionBlockGenerator { + /* + * Common database + */ + blocks: HashMap>, + block_hashes: HashMap, /* * PoW block parameters */ - pub seconds_since_genesis: u64, - pub block_interval_secs: u64, pub terminal_total_difficulty: u64, pub terminal_block_number: u64, /* * PoS block parameters */ pub pending_payloads: HashMap>, - pub merge_blocks: HashMap>, - pub merge_block_numbers: HashMap, - pub latest_merge_block: Option, pub next_payload_id: u64, pub payload_ids: HashMap>, } impl ExecutionBlockGenerator { pub fn new(terminal_total_difficulty: u64, terminal_block_number: u64) -> Self { - Self { - // PoW params - seconds_since_genesis: 0, - block_interval_secs: 1, + let mut gen = Self { + blocks: <_>::default(), + block_hashes: <_>::default(), terminal_total_difficulty, terminal_block_number, - // PoS params pending_payloads: <_>::default(), - merge_blocks: <_>::default(), - merge_block_numbers: <_>::default(), - latest_merge_block: None, next_payload_id: 0, payload_ids: <_>::default(), + }; + + gen.insert_pow_block(0).unwrap(); + + gen + } + + pub fn latest_block(&self) -> Option> { + let hash = *self + .block_hashes + .iter() + .max_by_key(|(number, _)| *number) + .map(|(_, hash)| hash)?; + + self.block_by_hash(hash) + } + + pub fn latest_execution_block(&self) -> Option { + self.latest_block() + .map(|block| block.as_execution_block(self.terminal_total_difficulty)) + } + + pub fn block_by_number(&self, number: u64) -> Option> { + let hash = *self.block_hashes.get(&number)?; + self.block_by_hash(hash) + } + + pub fn execution_block_by_number(&self, number: u64) -> Option { + self.block_by_number(number) + .map(|block| block.as_execution_block(self.terminal_total_difficulty)) + } + + pub fn block_by_hash(&self, hash: Hash256) -> Option> { + self.blocks.get(&hash).cloned() + } + + pub fn execution_block_by_hash(&self, hash: Hash256) -> Option { + self.block_by_hash(hash) + .map(|block| block.as_execution_block(self.terminal_total_difficulty)) + } + + pub fn insert_pow_blocks( + &mut self, + block_numbers: impl Iterator, + ) -> Result<(), String> { + for i in block_numbers { + self.insert_pow_block(i)?; } + + Ok(()) } - pub fn set_clock_for_block_number(&mut self, number: u64) { - self.seconds_since_genesis = number - .checked_mul(self.block_interval_secs) - .expect("overflow setting clock"); - } - - pub fn increment_seconds_since_genesis(&mut self, inc: u64) { - self.seconds_since_genesis += inc; - } - - fn block_number_at(&self, unix_seconds: u64) -> u64 { - unix_seconds - .checked_div(self.block_interval_secs) - .expect("block interval cannot be zero") - } - - fn total_difficulty_for_block(&self, number: u64) -> u64 { - if number >= self.terminal_block_number { - self.terminal_total_difficulty - } else { - let increment = self - .terminal_total_difficulty - .checked_div(self.terminal_block_number) - .expect("terminal block number must be non-zero"); - increment - .checked_mul(number) - .expect("overflow computing total difficulty") - } - } - - fn sanitize_pos_block_number(&self, number: u64) -> Result<(), String> { - if number <= self.terminal_block_number { + pub fn insert_pow_block(&mut self, block_number: u64) -> Result<(), String> { + if block_number > self.terminal_block_number { return Err(format!( - "cannot insert block {} as it is prior to terminal block {}", - number, self.terminal_block_number + "{} is beyond terminal pow block {}", + block_number, self.terminal_block_number )); } - let time_based_block = self.block_number_at(self.seconds_since_genesis); - if time_based_block < self.terminal_block_number && number > time_based_block { - return Err(format!("it is too early to insert block {}", number)); - } + let parent_hash = if block_number == 0 { + Hash256::zero() + } else if let Some(hash) = self.block_hashes.get(&(block_number - 1)) { + *hash + } else { + return Err(format!( + "parent with block number {} not found", + block_number - 1 + )); + }; - let next_block = self - .latest_merge_block - .unwrap_or(self.terminal_block_number) - + 1; + let increment = self + .terminal_total_difficulty + .checked_div(self.terminal_block_number) + .expect("terminal block number must be non-zero"); + let total_difficulty = increment + .checked_mul(block_number) + .expect("overflow computing total difficulty") + .into(); - match number.cmp(&next_block) { - Ordering::Equal => Ok(()), - Ordering::Less => Err(format!( - "cannot insert block {} which already exists", - number - )), - Ordering::Greater => Err(format!( - "cannot insert block {} before inserting {}", - number, next_block - )), - } + let mut block = PoWBlock { + block_number, + block_hash: Hash256::zero(), + parent_hash, + total_difficulty, + }; + + block.block_hash = block.tree_hash_root(); + + self.block_hashes + .insert(block.block_number, block.block_hash); + self.blocks.insert(block.block_hash, Block::PoW(block)); + + Ok(()) } pub fn prepare_payload_id( &mut self, payload: JsonPreparePayloadRequest, ) -> Result { - if self.block_number_at(self.seconds_since_genesis) < self.terminal_block_number { + if !self + .blocks + .iter() + .any(|(_, block)| block.block_number() == self.terminal_block_number) + { return Err("refusing to create payload id before terminal block".to_string()); } let parent = self - .block_by_hash(payload.parent_hash) + .blocks + .get(&payload.parent_hash) .ok_or_else(|| format!("unknown parent block {:?}", payload.parent_hash))?; let id = self.next_payload_id; @@ -134,7 +220,7 @@ impl ExecutionBlockGenerator { state_root: Hash256::repeat_byte(43), logs_bloom: vec![0; 256].into(), random: payload.random, - block_number: parent.block_number + 1, + block_number: parent.block_number() + 1, gas_limit: 10, gas_used: 9, timestamp: payload.timestamp, @@ -156,13 +242,13 @@ impl ExecutionBlockGenerator { } pub fn execute_payload(&mut self, payload: ExecutionPayload) -> ExecutePayloadResponse { - let parent = if let Some(parent) = self.block_by_hash(payload.parent_hash) { + let parent = if let Some(parent) = self.blocks.get(&payload.parent_hash) { parent } else { return ExecutePayloadResponse::Invalid; }; - if payload.block_number != parent.block_number + 1 { + if payload.block_number != parent.block_number() + 1 { return ExecutePayloadResponse::Invalid; } @@ -170,78 +256,6 @@ impl ExecutionBlockGenerator { ExecutePayloadResponse::Valid } - - pub fn insert_pos_block(&mut self, number: u64) -> Result<(), String> { - self.sanitize_pos_block_number(number)?; - self.latest_merge_block = Some(number); - Ok(()) - } - - fn latest_block_number(&self) -> u64 { - let time_based = self.block_number_at(self.seconds_since_genesis); - - if time_based < self.terminal_block_number { - time_based - } else { - self.latest_merge_block - .unwrap_or(self.terminal_block_number) - } - } - - pub fn latest_block(&self) -> Option { - self.block_by_number(self.latest_block_number()) - } - - pub fn block_by_number(&self, number: u64) -> Option { - let parent_hash = number - .checked_sub(1) - .map(block_number_to_hash) - .unwrap_or_else(Hash256::zero); - let block_hash = block_number_to_hash(number); - - if number <= self.terminal_block_number { - if number <= self.latest_block_number() { - Some(Block { - block_number: number, - block_hash, - parent_hash, - total_difficulty: Uint256::from(self.total_difficulty_for_block(number)), - }) - } else { - None - } - } else { - let latest_block = self - .latest_merge_block - .unwrap_or(self.terminal_block_number); - - if number <= latest_block { - Some(Block { - block_number: number, - block_hash, - parent_hash, - total_difficulty: Uint256::from(self.terminal_total_difficulty), - }) - } else { - None - } - } - } - - pub fn block_by_hash(&self, hash: Hash256) -> Option { - let block_number = block_hash_to_number(hash); - self.block_by_number(block_number) - } -} - -pub fn block_number_to_hash(n: u64) -> Hash256 { - Hash256::from_low_u64_be(n + 1) -} - -pub fn block_hash_to_number(hash: Hash256) -> u64 { - hash.to_low_u64_be() - .checked_sub(1) - .expect("do not query for zero hash") } #[cfg(test)] @@ -258,26 +272,28 @@ mod test { let mut generator: ExecutionBlockGenerator = ExecutionBlockGenerator::new(TERMINAL_DIFFICULTY, TERMINAL_BLOCK); - for mut i in 0..(TERMINAL_BLOCK + 5) { - i = std::cmp::min(i, TERMINAL_BLOCK); + for i in 0..=TERMINAL_BLOCK { + generator.insert_pow_block(i).unwrap(); /* * Generate a block, inspect it. */ let block = generator.latest_block().unwrap(); - assert_eq!(block.block_hash, block_number_to_hash(i)); - assert_eq!(block_hash_to_number(block.block_hash), i); + assert_eq!(block.block_number(), i); let expected_parent = i .checked_sub(1) - .map(block_number_to_hash) + .map(|i| generator.block_by_number(i).unwrap().block_hash()) .unwrap_or_else(Hash256::zero); - assert_eq!(block.parent_hash, expected_parent); + assert_eq!(block.parent_hash(), expected_parent); - assert_eq!(block.total_difficulty, (i * DIFFICULTY_INCREMENT).into()); + assert_eq!( + block.total_difficulty().unwrap(), + (i * DIFFICULTY_INCREMENT).into() + ); - assert_eq!(generator.block_by_hash(block.block_hash).unwrap(), block); + assert_eq!(generator.block_by_hash(block.block_hash()).unwrap(), block); assert_eq!(generator.block_by_number(i).unwrap(), block); /* @@ -287,7 +303,7 @@ mod test { if let Some(prev_i) = i.checked_sub(1) { assert_eq!( generator.block_by_number(prev_i).unwrap(), - generator.block_by_hash(block.parent_hash).unwrap() + generator.block_by_hash(block.parent_hash()).unwrap() ); } @@ -297,14 +313,10 @@ mod test { let next_i = i + 1; assert!(generator.block_by_number(next_i).is_none()); - assert!(generator - .block_by_hash(block_number_to_hash(next_i)) - .is_none()); - - generator.increment_seconds_since_genesis(1); } } + /* #[test] fn pos_blocks() { const TERMINAL_DIFFICULTY: u64 = 10; @@ -318,7 +330,7 @@ mod test { let first_pos_block = generator.terminal_block_number + 1; let second_pos_block = first_pos_block + 1; - generator.set_clock_for_block_number(penultimate_pow_block); + generator.insert_pow_blocks(0..=penultimate_pow_block); assert!(generator.block_by_number(last_pow_block).is_none()); @@ -338,4 +350,5 @@ mod test { generator.insert_pos_block(second_pos_block).unwrap(); } + */ } diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index ecce225a42..3e4560cbd8 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -27,7 +27,10 @@ pub async fn handle_rpc( match tag { "latest" => Ok(serde_json::to_value( - ctx.execution_block_generator.read().await.latest_block(), + ctx.execution_block_generator + .read() + .await + .latest_execution_block(), ) .unwrap()), other => Err(format!("The tag {} is not supported", other)), @@ -47,7 +50,7 @@ pub async fn handle_rpc( ctx.execution_block_generator .read() .await - .block_by_hash(hash), + .execution_block_by_hash(hash), ) .unwrap()) } diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index a97a661d3c..01e6e8ddac 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -14,8 +14,6 @@ use tokio::sync::{oneshot, RwLock, RwLockWriteGuard}; use types::EthSpec; use warp::Filter; -pub use execution_block_generator::{block_hash_to_number, block_number_to_hash}; - pub const DEFAULT_TERMINAL_DIFFICULTY: u64 = 6400; pub const DEFAULT_TERMINAL_BLOCK: u64 = 64;