mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-15 02:42:38 +00:00
Merge remote-tracking branch 'origin/unstable' into capella-update
This commit is contained in:
@@ -36,7 +36,7 @@ pub struct JsonResponseBody {
|
||||
pub id: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct TransparentJsonPayloadId(#[serde(with = "eth2_serde_utils::bytes_8_hex")] pub PayloadId);
|
||||
|
||||
@@ -324,7 +324,7 @@ impl<T: EthSpec> TryFrom<ExecutionPayload<T>> for JsonExecutionPayloadV2<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsonWithdrawal {
|
||||
#[serde(with = "eth2_serde_utils::u64_hex_be")]
|
||||
@@ -362,13 +362,13 @@ impl From<JsonWithdrawal> for Withdrawal {
|
||||
#[superstruct(
|
||||
variants(V1, V2),
|
||||
variant_attributes(
|
||||
derive(Clone, Debug, PartialEq, Serialize, Deserialize),
|
||||
derive(Debug, Clone, PartialEq, Serialize, Deserialize),
|
||||
serde(rename_all = "camelCase")
|
||||
),
|
||||
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
|
||||
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub struct JsonPayloadAttributes {
|
||||
#[serde(with = "eth2_serde_utils::u64_hex_be")]
|
||||
@@ -429,7 +429,7 @@ pub struct JsonBlobBundles<T: EthSpec> {
|
||||
pub blobs: Vec<Blob<T>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsonForkchoiceStateV1 {
|
||||
pub head_block_hash: ExecutionBlockHash,
|
||||
@@ -482,7 +482,7 @@ pub enum JsonPayloadStatusV1Status {
|
||||
InvalidBlockHash,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsonPayloadStatusV1 {
|
||||
pub status: JsonPayloadStatusV1Status,
|
||||
@@ -547,7 +547,7 @@ impl From<JsonPayloadStatusV1> for PayloadStatusV1 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsonForkchoiceUpdatedV1Response {
|
||||
pub payload_status: JsonPayloadStatusV1,
|
||||
|
||||
@@ -582,6 +582,16 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
.contains_key(&proposer_index)
|
||||
}
|
||||
|
||||
/// Check if a proposer is registered as a local validator, *from a synchronous context*.
|
||||
///
|
||||
/// This method MUST NOT be called from an async task.
|
||||
pub fn has_proposer_preparation_data_blocking(&self, proposer_index: u64) -> bool {
|
||||
self.inner
|
||||
.proposer_preparation_data
|
||||
.blocking_lock()
|
||||
.contains_key(&proposer_index)
|
||||
}
|
||||
|
||||
/// Returns the fee-recipient address that should be used to build a block
|
||||
pub async fn get_suggested_fee_recipient(&self, proposer_index: u64) -> Address {
|
||||
if let Some(preparation_data_entry) =
|
||||
@@ -1229,12 +1239,14 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
&[metrics::FORKCHOICE_UPDATED],
|
||||
);
|
||||
|
||||
trace!(
|
||||
debug!(
|
||||
self.log(),
|
||||
"Issuing engine_forkchoiceUpdated";
|
||||
"finalized_block_hash" => ?finalized_block_hash,
|
||||
"justified_block_hash" => ?justified_block_hash,
|
||||
"head_block_hash" => ?head_block_hash,
|
||||
"head_block_root" => ?head_block_root,
|
||||
"current_slot" => current_slot,
|
||||
);
|
||||
|
||||
let next_slot = current_slot + 1;
|
||||
@@ -1729,7 +1741,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
&metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME,
|
||||
&[metrics::FAILURE],
|
||||
);
|
||||
crit!(
|
||||
error!(
|
||||
self.log(),
|
||||
"Builder failed to reveal payload";
|
||||
"info" => "this relay failure may cause a missed proposal",
|
||||
@@ -1935,6 +1947,20 @@ mod test {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_forked_terminal_block() {
|
||||
let runtime = TestRuntime::default();
|
||||
let (mock, block_hash) = MockExecutionLayer::default_params(runtime.task_executor.clone())
|
||||
.move_to_terminal_block()
|
||||
.produce_forked_pow_block();
|
||||
assert!(mock
|
||||
.el
|
||||
.is_valid_terminal_pow_block_hash(block_hash, &mock.spec)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn finds_valid_terminal_block_hash() {
|
||||
let runtime = TestRuntime::default();
|
||||
|
||||
@@ -92,13 +92,15 @@ pub struct PoWBlock {
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExecutionBlockGenerator<T: EthSpec> {
|
||||
/*
|
||||
* Common database
|
||||
*/
|
||||
head_block: Option<Block<T>>,
|
||||
finalized_block_hash: Option<ExecutionBlockHash>,
|
||||
blocks: HashMap<ExecutionBlockHash, Block<T>>,
|
||||
block_hashes: HashMap<u64, ExecutionBlockHash>,
|
||||
block_hashes: HashMap<u64, Vec<ExecutionBlockHash>>,
|
||||
/*
|
||||
* PoW block parameters
|
||||
*/
|
||||
@@ -120,6 +122,8 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
terminal_block_hash: ExecutionBlockHash,
|
||||
) -> Self {
|
||||
let mut gen = Self {
|
||||
head_block: <_>::default(),
|
||||
finalized_block_hash: <_>::default(),
|
||||
blocks: <_>::default(),
|
||||
block_hashes: <_>::default(),
|
||||
terminal_total_difficulty,
|
||||
@@ -136,13 +140,7 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
}
|
||||
|
||||
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)
|
||||
self.head_block.clone()
|
||||
}
|
||||
|
||||
pub fn latest_execution_block(&self) -> Option<ExecutionBlock> {
|
||||
@@ -151,8 +149,18 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
}
|
||||
|
||||
pub fn block_by_number(&self, number: u64) -> Option<Block<T>> {
|
||||
let hash = *self.block_hashes.get(&number)?;
|
||||
self.block_by_hash(hash)
|
||||
// Get the latest canonical head block
|
||||
let mut latest_block = self.latest_block()?;
|
||||
loop {
|
||||
let block_number = latest_block.block_number();
|
||||
if block_number < number {
|
||||
return None;
|
||||
}
|
||||
if block_number == number {
|
||||
return Some(latest_block);
|
||||
}
|
||||
latest_block = self.block_by_hash(latest_block.parent_hash())?;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execution_block_by_number(&self, number: u64) -> Option<ExecutionBlock> {
|
||||
@@ -213,10 +221,16 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
}
|
||||
|
||||
pub fn insert_pow_block(&mut self, block_number: u64) -> Result<(), String> {
|
||||
if let Some(finalized_block_hash) = self.finalized_block_hash {
|
||||
return Err(format!(
|
||||
"terminal block {} has been finalized. PoW chain has stopped building",
|
||||
finalized_block_hash
|
||||
));
|
||||
}
|
||||
let parent_hash = if block_number == 0 {
|
||||
ExecutionBlockHash::zero()
|
||||
} else if let Some(hash) = self.block_hashes.get(&(block_number - 1)) {
|
||||
*hash
|
||||
} else if let Some(block) = self.block_by_number(block_number - 1) {
|
||||
block.block_hash()
|
||||
} else {
|
||||
return Err(format!(
|
||||
"parent with block number {} not found",
|
||||
@@ -231,42 +245,102 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
parent_hash,
|
||||
)?;
|
||||
|
||||
self.insert_block(Block::PoW(block))
|
||||
}
|
||||
// Insert block into block tree
|
||||
self.insert_block(Block::PoW(block))?;
|
||||
|
||||
pub fn insert_block(&mut self, block: Block<T>) -> Result<(), String> {
|
||||
if self.blocks.contains_key(&block.block_hash()) {
|
||||
return Err(format!("{:?} is already known", block.block_hash()));
|
||||
} else if self.block_hashes.contains_key(&block.block_number()) {
|
||||
return Err(format!(
|
||||
"block {} is already known, forking is not supported",
|
||||
block.block_number()
|
||||
));
|
||||
} else if block.block_number() != 0 && !self.blocks.contains_key(&block.parent_hash()) {
|
||||
return Err(format!("parent block {:?} is unknown", block.parent_hash()));
|
||||
// Set head
|
||||
if let Some(head_total_difficulty) =
|
||||
self.head_block.as_ref().and_then(|b| b.total_difficulty())
|
||||
{
|
||||
if block.total_difficulty >= head_total_difficulty {
|
||||
self.head_block = Some(Block::PoW(block));
|
||||
}
|
||||
} else {
|
||||
self.head_block = Some(Block::PoW(block));
|
||||
}
|
||||
|
||||
self.insert_block_without_checks(block)
|
||||
}
|
||||
|
||||
pub fn insert_block_without_checks(&mut self, block: Block<T>) -> Result<(), String> {
|
||||
self.block_hashes
|
||||
.insert(block.block_number(), block.block_hash());
|
||||
self.blocks.insert(block.block_hash(), block);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Insert a PoW block given the parent hash.
|
||||
///
|
||||
/// Returns `Ok(hash)` of the inserted block.
|
||||
/// Returns an error if the `parent_hash` does not exist in the block tree or
|
||||
/// if the parent block is the terminal block.
|
||||
pub fn insert_pow_block_by_hash(
|
||||
&mut self,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
unique_id: u64,
|
||||
) -> Result<ExecutionBlockHash, String> {
|
||||
let parent_block = self.block_by_hash(parent_hash).ok_or_else(|| {
|
||||
format!(
|
||||
"Block corresponding to parent hash does not exist: {}",
|
||||
parent_hash
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut block = generate_pow_block(
|
||||
self.terminal_total_difficulty,
|
||||
self.terminal_block_number,
|
||||
parent_block.block_number() + 1,
|
||||
parent_hash,
|
||||
)?;
|
||||
|
||||
// Hack the block hash to make this block distinct from any other block with a different
|
||||
// `unique_id` (the default is 0).
|
||||
block.block_hash = ExecutionBlockHash::from_root(Hash256::from_low_u64_be(unique_id));
|
||||
block.block_hash = ExecutionBlockHash::from_root(block.tree_hash_root());
|
||||
|
||||
let hash = self.insert_block(Block::PoW(block))?;
|
||||
|
||||
// Set head
|
||||
if let Some(head_total_difficulty) =
|
||||
self.head_block.as_ref().and_then(|b| b.total_difficulty())
|
||||
{
|
||||
if block.total_difficulty >= head_total_difficulty {
|
||||
self.head_block = Some(Block::PoW(block));
|
||||
}
|
||||
} else {
|
||||
self.head_block = Some(Block::PoW(block));
|
||||
}
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
pub fn insert_block(&mut self, block: Block<T>) -> Result<ExecutionBlockHash, String> {
|
||||
if self.blocks.contains_key(&block.block_hash()) {
|
||||
return Err(format!("{:?} is already known", block.block_hash()));
|
||||
} else if block.parent_hash() != ExecutionBlockHash::zero()
|
||||
&& !self.blocks.contains_key(&block.parent_hash())
|
||||
{
|
||||
return Err(format!("parent block {:?} is unknown", block.parent_hash()));
|
||||
}
|
||||
|
||||
Ok(self.insert_block_without_checks(block))
|
||||
}
|
||||
|
||||
pub fn insert_block_without_checks(&mut self, block: Block<T>) -> ExecutionBlockHash {
|
||||
let block_hash = block.block_hash();
|
||||
self.block_hashes
|
||||
.entry(block.block_number())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(block_hash);
|
||||
self.blocks.insert(block_hash, block);
|
||||
|
||||
block_hash
|
||||
}
|
||||
|
||||
pub fn modify_last_block(&mut self, block_modifier: impl FnOnce(&mut Block<T>)) {
|
||||
if let Some((last_block_hash, block_number)) =
|
||||
self.block_hashes.keys().max().and_then(|block_number| {
|
||||
self.block_hashes
|
||||
.get(block_number)
|
||||
.map(|block| (block, *block_number))
|
||||
if let Some(last_block_hash) = self
|
||||
.block_hashes
|
||||
.iter_mut()
|
||||
.max_by_key(|(block_number, _)| *block_number)
|
||||
.and_then(|(_, block_hashes)| {
|
||||
// Remove block hash, we will re-insert with the new block hash after modifying it.
|
||||
block_hashes.pop()
|
||||
})
|
||||
{
|
||||
let mut block = self.blocks.remove(last_block_hash).unwrap();
|
||||
let mut block = self.blocks.remove(&last_block_hash).unwrap();
|
||||
block_modifier(&mut block);
|
||||
|
||||
// Update the block hash after modifying the block
|
||||
match &mut block {
|
||||
Block::PoW(b) => b.block_hash = ExecutionBlockHash::from_root(b.tree_hash_root()),
|
||||
@@ -274,8 +348,17 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
*b.block_hash_mut() = ExecutionBlockHash::from_root(b.tree_hash_root())
|
||||
}
|
||||
}
|
||||
self.block_hashes.insert(block_number, block.block_hash());
|
||||
self.blocks.insert(block.block_hash(), block);
|
||||
|
||||
// Update head.
|
||||
if self
|
||||
.head_block
|
||||
.as_ref()
|
||||
.map_or(true, |head| head.block_hash() == last_block_hash)
|
||||
{
|
||||
self.head_block = Some(block.clone());
|
||||
}
|
||||
|
||||
self.insert_block_without_checks(block);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,6 +498,17 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
}
|
||||
};
|
||||
|
||||
self.head_block = Some(
|
||||
self.blocks
|
||||
.get(&forkchoice_state.head_block_hash)
|
||||
.unwrap()
|
||||
.clone(),
|
||||
);
|
||||
|
||||
if forkchoice_state.finalized_block_hash != ExecutionBlockHash::zero() {
|
||||
self.finalized_block_hash = Some(forkchoice_state.finalized_block_hash);
|
||||
}
|
||||
|
||||
Ok(JsonForkchoiceUpdatedV1Response {
|
||||
payload_status: JsonPayloadStatusV1 {
|
||||
status: JsonPayloadStatusV1Status::Valid,
|
||||
|
||||
@@ -137,10 +137,19 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
|
||||
Ok(serde_json::to_value(JsonExecutionPayloadV1::try_from(response).unwrap()).unwrap())
|
||||
}
|
||||
// FIXME(capella): handle fcu version 2
|
||||
ENGINE_FORKCHOICE_UPDATED_V1 => {
|
||||
let forkchoice_state: JsonForkchoiceStateV1 = get_param(params, 0)?;
|
||||
let payload_attributes: Option<JsonPayloadAttributes> = get_param(params, 1)?;
|
||||
|
||||
if let Some(hook_response) = ctx
|
||||
.hook
|
||||
.lock()
|
||||
.on_forkchoice_updated(forkchoice_state.clone(), payload_attributes.clone())
|
||||
{
|
||||
return Ok(serde_json::to_value(hook_response).unwrap());
|
||||
}
|
||||
|
||||
let head_block_hash = forkchoice_state.head_block_hash;
|
||||
|
||||
// Canned responses set by block hash take priority.
|
||||
|
||||
27
beacon_node/execution_layer/src/test_utils/hook.rs
Normal file
27
beacon_node/execution_layer/src/test_utils/hook.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::json_structures::*;
|
||||
|
||||
type ForkChoiceUpdatedHook = dyn Fn(
|
||||
JsonForkchoiceStateV1,
|
||||
Option<JsonPayloadAttributes>,
|
||||
) -> Option<JsonForkchoiceUpdatedV1Response>
|
||||
+ Send
|
||||
+ Sync;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Hook {
|
||||
forkchoice_updated: Option<Box<ForkChoiceUpdatedHook>>,
|
||||
}
|
||||
|
||||
impl Hook {
|
||||
pub fn on_forkchoice_updated(
|
||||
&self,
|
||||
state: JsonForkchoiceStateV1,
|
||||
payload_attributes: Option<JsonPayloadAttributes>,
|
||||
) -> Option<JsonForkchoiceUpdatedV1Response> {
|
||||
(self.forkchoice_updated.as_ref()?)(state, payload_attributes)
|
||||
}
|
||||
|
||||
pub fn set_forkchoice_updated_hook(&mut self, f: Box<ForkChoiceUpdatedHook>) {
|
||||
self.forkchoice_updated = Some(f);
|
||||
}
|
||||
}
|
||||
@@ -244,6 +244,21 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn produce_forked_pow_block(self) -> (Self, ExecutionBlockHash) {
|
||||
let head_block = self
|
||||
.server
|
||||
.execution_block_generator()
|
||||
.latest_block()
|
||||
.unwrap();
|
||||
|
||||
let block_hash = self
|
||||
.server
|
||||
.execution_block_generator()
|
||||
.insert_pow_block_by_hash(head_block.parent_hash(), 1)
|
||||
.unwrap();
|
||||
(self, block_hash)
|
||||
}
|
||||
|
||||
pub async fn with_terminal_block<'a, U, V>(self, func: U) -> Self
|
||||
where
|
||||
U: Fn(ChainSpec, ExecutionLayer<T>, Option<ExecutionBlock>) -> V,
|
||||
|
||||
@@ -23,6 +23,7 @@ use types::{EthSpec, ExecutionBlockHash, Uint256};
|
||||
use warp::{http::StatusCode, Filter, Rejection};
|
||||
|
||||
pub use execution_block_generator::{generate_pow_block, Block, ExecutionBlockGenerator};
|
||||
pub use hook::Hook;
|
||||
pub use mock_builder::{Context as MockBuilderContext, MockBuilder, Operation, TestingBuilder};
|
||||
pub use mock_execution_layer::MockExecutionLayer;
|
||||
|
||||
@@ -33,6 +34,7 @@ pub const DEFAULT_BUILDER_THRESHOLD_WEI: u128 = 1_000_000_000_000_000_000;
|
||||
|
||||
mod execution_block_generator;
|
||||
mod handle_rpc;
|
||||
mod hook;
|
||||
mod mock_builder;
|
||||
mod mock_execution_layer;
|
||||
|
||||
@@ -99,6 +101,7 @@ impl<T: EthSpec> MockServer<T> {
|
||||
static_new_payload_response: <_>::default(),
|
||||
static_forkchoice_updated_response: <_>::default(),
|
||||
static_get_block_by_hash_response: <_>::default(),
|
||||
hook: <_>::default(),
|
||||
new_payload_statuses: <_>::default(),
|
||||
fcu_payload_statuses: <_>::default(),
|
||||
_phantom: PhantomData,
|
||||
@@ -359,8 +362,7 @@ impl<T: EthSpec> MockServer<T> {
|
||||
.write()
|
||||
// The EF tests supply blocks out of order, so we must import them "without checks" and
|
||||
// trust they form valid chains.
|
||||
.insert_block_without_checks(block)
|
||||
.unwrap()
|
||||
.insert_block_without_checks(block);
|
||||
}
|
||||
|
||||
pub fn get_block(&self, block_hash: ExecutionBlockHash) -> Option<Block<T>> {
|
||||
@@ -441,6 +443,7 @@ pub struct Context<T: EthSpec> {
|
||||
pub static_new_payload_response: Arc<Mutex<Option<StaticNewPayloadResponse>>>,
|
||||
pub static_forkchoice_updated_response: Arc<Mutex<Option<PayloadStatusV1>>>,
|
||||
pub static_get_block_by_hash_response: Arc<Mutex<Option<Option<ExecutionBlock>>>>,
|
||||
pub hook: Arc<Mutex<Hook>>,
|
||||
|
||||
// Canned responses by block hash.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user