Refactor block gen

This commit is contained in:
Paul Hauner
2021-09-29 12:17:38 +10:00
parent 837acc11e6
commit 57f6a9b1f1
7 changed files with 230 additions and 201 deletions

View File

@@ -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"}

View File

@@ -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,
}

View File

@@ -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<ExecutionBlock>) -> U,
U: Future<Output = ()>,
{
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
)
})

View File

@@ -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<T: EthSpec> {
PoW(PoWBlock),
PoS(ExecutionPayload<T>),
}
impl<T: EthSpec> Block<T> {
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<Uint256> {
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<T: EthSpec> {
/*
* Common database
*/
blocks: HashMap<Hash256, Block<T>>,
block_hashes: HashMap<u64, Hash256>,
/*
* 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<Hash256, ExecutionPayload<T>>,
pub merge_blocks: HashMap<Hash256, ExecutionPayload<T>>,
pub merge_block_numbers: HashMap<u64, Hash256>,
pub latest_merge_block: Option<u64>,
pub next_payload_id: u64,
pub payload_ids: HashMap<u64, ExecutionPayload<T>>,
}
impl<T: EthSpec> ExecutionBlockGenerator<T> {
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<Block<T>> {
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<ExecutionBlock> {
self.latest_block()
.map(|block| block.as_execution_block(self.terminal_total_difficulty))
}
pub fn block_by_number(&self, number: u64) -> Option<Block<T>> {
let hash = *self.block_hashes.get(&number)?;
self.block_by_hash(hash)
}
pub fn execution_block_by_number(&self, number: u64) -> Option<ExecutionBlock> {
self.block_by_number(number)
.map(|block| block.as_execution_block(self.terminal_total_difficulty))
}
pub fn block_by_hash(&self, hash: Hash256) -> Option<Block<T>> {
self.blocks.get(&hash).cloned()
}
pub fn execution_block_by_hash(&self, hash: Hash256) -> Option<ExecutionBlock> {
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<Item = u64>,
) -> 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<u64, String> {
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<T: EthSpec> ExecutionBlockGenerator<T> {
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<T: EthSpec> ExecutionBlockGenerator<T> {
}
pub fn execute_payload(&mut self, payload: ExecutionPayload<T>) -> 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<T: EthSpec> ExecutionBlockGenerator<T> {
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<Block> {
self.block_by_number(self.latest_block_number())
}
pub fn block_by_number(&self, number: u64) -> Option<Block> {
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<Block> {
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<MainnetEthSpec> =
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();
}
*/
}

View File

@@ -27,7 +27,10 @@ pub async fn handle_rpc<T: EthSpec>(
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<T: EthSpec>(
ctx.execution_block_generator
.read()
.await
.block_by_hash(hash),
.execution_block_by_hash(hash),
)
.unwrap())
}

View File

@@ -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;