mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-21 22:04:44 +00:00
merge with capella
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
use crate::engines::ForkchoiceState;
|
||||
pub use ethers_core::types::Transaction;
|
||||
use ethers_core::utils::rlp::{Decodable, Rlp};
|
||||
use ethers_core::utils::rlp::{self, Decodable, Rlp};
|
||||
use http::deposit_methods::RpcError;
|
||||
pub use json_structures::TransitionConfigurationV1;
|
||||
pub use json_structures::{JsonWithdrawal, TransitionConfigurationV1};
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use strum::IntoStaticStr;
|
||||
use superstruct::superstruct;
|
||||
pub use types::{
|
||||
@@ -46,6 +47,7 @@ pub enum Error {
|
||||
RequiredMethodUnsupported(&'static str),
|
||||
UnsupportedForkVariant(String),
|
||||
BadConversion(String),
|
||||
RlpDecoderError(rlp::DecoderError),
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for Error {
|
||||
@@ -79,6 +81,12 @@ impl From<builder_client::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rlp::DecoderError> for Error {
|
||||
fn from(e: rlp::DecoderError) -> Self {
|
||||
Error::RlpDecoderError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, IntoStaticStr)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum PayloadStatusV1Status {
|
||||
@@ -159,12 +167,14 @@ pub struct ExecutionBlockWithTransactions<T: EthSpec> {
|
||||
pub transactions: Vec<Transaction>,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
#[superstruct(only(Capella, Eip4844))]
|
||||
pub withdrawals: Vec<Withdrawal>,
|
||||
pub withdrawals: Vec<JsonWithdrawal>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<ExecutionPayload<T>> for ExecutionBlockWithTransactions<T> {
|
||||
fn from(payload: ExecutionPayload<T>) -> Self {
|
||||
match payload {
|
||||
impl<T: EthSpec> TryFrom<ExecutionPayload<T>> for ExecutionBlockWithTransactions<T> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(payload: ExecutionPayload<T>) -> Result<Self, Error> {
|
||||
let json_payload = match payload {
|
||||
ExecutionPayload::Merge(block) => Self::Merge(ExecutionBlockWithTransactionsMerge {
|
||||
parent_hash: block.parent_hash,
|
||||
fee_recipient: block.fee_recipient,
|
||||
@@ -183,8 +193,7 @@ impl<T: EthSpec> From<ExecutionPayload<T>> for ExecutionBlockWithTransactions<T>
|
||||
.transactions
|
||||
.iter()
|
||||
.map(|tx| Transaction::decode(&Rlp::new(tx)))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap_or_else(|_| Vec::new()),
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
}),
|
||||
ExecutionPayload::Capella(block) => {
|
||||
Self::Capella(ExecutionBlockWithTransactionsCapella {
|
||||
@@ -205,10 +214,12 @@ impl<T: EthSpec> From<ExecutionPayload<T>> for ExecutionBlockWithTransactions<T>
|
||||
.transactions
|
||||
.iter()
|
||||
.map(|tx| Transaction::decode(&Rlp::new(tx)))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap_or_else(|_| Vec::new()),
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals: block.withdrawals.into(),
|
||||
withdrawals: Vec::from(block.withdrawals)
|
||||
.into_iter()
|
||||
.map(|withdrawal| withdrawal.into())
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
ExecutionPayload::Eip4844(block) => {
|
||||
@@ -231,13 +242,16 @@ impl<T: EthSpec> From<ExecutionPayload<T>> for ExecutionBlockWithTransactions<T>
|
||||
.transactions
|
||||
.iter()
|
||||
.map(|tx| Transaction::decode(&Rlp::new(tx)))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap_or_else(|_| Vec::new()),
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals: block.withdrawals.into(),
|
||||
withdrawals: Vec::from(block.withdrawals)
|
||||
.into_iter()
|
||||
.map(|withdrawal| withdrawal.into())
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(json_payload)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -666,14 +666,41 @@ impl HttpJsonRpc {
|
||||
pub async fn get_block_by_hash_with_txns<T: EthSpec>(
|
||||
&self,
|
||||
block_hash: ExecutionBlockHash,
|
||||
fork: ForkName,
|
||||
) -> Result<Option<ExecutionBlockWithTransactions<T>>, Error> {
|
||||
let params = json!([block_hash, true]);
|
||||
self.rpc_request(
|
||||
ETH_GET_BLOCK_BY_HASH,
|
||||
params,
|
||||
ETH_GET_BLOCK_BY_HASH_TIMEOUT * self.execution_timeout_multiplier,
|
||||
)
|
||||
.await
|
||||
Ok(Some(match fork {
|
||||
ForkName::Merge => ExecutionBlockWithTransactions::Merge(
|
||||
self.rpc_request(
|
||||
ETH_GET_BLOCK_BY_HASH,
|
||||
params,
|
||||
ETH_GET_BLOCK_BY_HASH_TIMEOUT * self.execution_timeout_multiplier,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
ForkName::Capella => ExecutionBlockWithTransactions::Capella(
|
||||
self.rpc_request(
|
||||
ETH_GET_BLOCK_BY_HASH,
|
||||
params,
|
||||
ETH_GET_BLOCK_BY_HASH_TIMEOUT * self.execution_timeout_multiplier,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
ForkName::Eip4844 => ExecutionBlockWithTransactions::Eip4844(
|
||||
self.rpc_request(
|
||||
ETH_GET_BLOCK_BY_HASH,
|
||||
params,
|
||||
ETH_GET_BLOCK_BY_HASH_TIMEOUT * self.execution_timeout_multiplier,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
ForkName::Base | ForkName::Altair => {
|
||||
return Err(Error::UnsupportedForkVariant(format!(
|
||||
"called get_block_by_hash_with_txns with fork {:?}",
|
||||
fork
|
||||
)))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn new_payload_v1<T: EthSpec>(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -174,7 +174,7 @@ impl<T: EthSpec> JsonExecutionPayload<T> {
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
})
|
||||
.ok_or(Error::BadConversion("Null withdrawal field converting JsonExecutionPayloadV2 -> ExecutionPayloadCapella".to_string()))?
|
||||
.ok_or_else(|| Error::BadConversion("Null withdrawal field converting JsonExecutionPayloadV2 -> ExecutionPayloadCapella".to_string()))?
|
||||
})),
|
||||
ForkName::Eip4844 => Ok(ExecutionPayload::Eip4844(ExecutionPayloadEip4844 {
|
||||
parent_hash: v2.parent_hash,
|
||||
@@ -189,7 +189,7 @@ impl<T: EthSpec> JsonExecutionPayload<T> {
|
||||
timestamp: v2.timestamp,
|
||||
extra_data: v2.extra_data,
|
||||
base_fee_per_gas: v2.base_fee_per_gas,
|
||||
excess_data_gas: v2.excess_data_gas.ok_or(Error::BadConversion("Null `excess_data_gas` field converting JsonExecutionPayloadV2 -> ExecutionPayloadEip4844".to_string()))?,
|
||||
excess_data_gas: v2.excess_data_gas.ok_or_else(|| Error::BadConversion("Null `excess_data_gas` field converting JsonExecutionPayloadV2 -> ExecutionPayloadEip4844".to_string()))?,
|
||||
block_hash: v2.block_hash,
|
||||
transactions: v2.transactions,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
@@ -202,7 +202,7 @@ impl<T: EthSpec> JsonExecutionPayload<T> {
|
||||
.collect::<Vec<_>>()
|
||||
.into()
|
||||
})
|
||||
.ok_or(Error::BadConversion("Null withdrawal field converting JsonExecutionPayloadV2 -> ExecutionPayloadEip4844".to_string()))?
|
||||
.ok_or_else(|| Error::BadConversion("Null withdrawal field converting JsonExecutionPayloadV2 -> ExecutionPayloadEip4844".to_string()))?
|
||||
})),
|
||||
_ => Err(Error::UnsupportedForkVariant(format!("Unsupported conversion from JsonExecutionPayloadV2 for {}", fork_name))),
|
||||
}
|
||||
@@ -322,7 +322,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")]
|
||||
@@ -360,13 +360,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")]
|
||||
@@ -428,7 +428,7 @@ pub struct JsonBlobsBundle<T: EthSpec> {
|
||||
pub blobs: VariableList<Blob<T>, T::MaxBlobsPerBlock>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsonForkchoiceStateV1 {
|
||||
pub head_block_hash: ExecutionBlockHash,
|
||||
@@ -481,7 +481,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,
|
||||
@@ -546,7 +546,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,
|
||||
|
||||
@@ -349,7 +349,7 @@ impl Engine {
|
||||
impl PayloadIdCacheKey {
|
||||
fn new(head_block_hash: &ExecutionBlockHash, attributes: &PayloadAttributes) -> Self {
|
||||
Self {
|
||||
head_block_hash: head_block_hash.clone(),
|
||||
head_block_hash: *head_block_hash,
|
||||
payload_attributes: attributes.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,6 +584,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) =
|
||||
@@ -1232,12 +1242,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;
|
||||
@@ -1559,10 +1571,11 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
pub async fn get_payload_by_block_hash(
|
||||
&self,
|
||||
hash: ExecutionBlockHash,
|
||||
fork: ForkName,
|
||||
) -> Result<Option<ExecutionPayload<T>>, Error> {
|
||||
self.engine()
|
||||
.request(|engine| async move {
|
||||
self.get_payload_by_block_hash_from_engine(engine, hash)
|
||||
self.get_payload_by_block_hash_from_engine(engine, hash, fork)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
@@ -1574,15 +1587,26 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
&self,
|
||||
engine: &Engine,
|
||||
hash: ExecutionBlockHash,
|
||||
fork: ForkName,
|
||||
) -> Result<Option<ExecutionPayload<T>>, ApiError> {
|
||||
let _timer = metrics::start_timer(&metrics::EXECUTION_LAYER_GET_PAYLOAD_BY_BLOCK_HASH);
|
||||
|
||||
if hash == ExecutionBlockHash::zero() {
|
||||
// FIXME: how to handle forks properly here?
|
||||
return Ok(Some(ExecutionPayloadMerge::default().into()));
|
||||
return match fork {
|
||||
ForkName::Merge => Ok(Some(ExecutionPayloadMerge::default().into())),
|
||||
ForkName::Capella => Ok(Some(ExecutionPayloadCapella::default().into())),
|
||||
ForkName::Eip4844 => Ok(Some(ExecutionPayloadEip4844::default().into())),
|
||||
ForkName::Base | ForkName::Altair => Err(ApiError::UnsupportedForkVariant(
|
||||
format!("called get_payload_by_block_hash_from_engine with {}", fork),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
let block = if let Some(block) = engine.api.get_block_by_hash_with_txns::<T>(hash).await? {
|
||||
let block = if let Some(block) = engine
|
||||
.api
|
||||
.get_block_by_hash_with_txns::<T>(hash, fork)
|
||||
.await?
|
||||
{
|
||||
block
|
||||
} else {
|
||||
return Ok(None);
|
||||
@@ -1591,7 +1615,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
let transactions = VariableList::new(
|
||||
block
|
||||
.transactions()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|transaction| VariableList::new(transaction.rlp().to_vec()))
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(ApiError::DeserializeTransaction)?,
|
||||
@@ -1619,8 +1643,14 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
}
|
||||
ExecutionBlockWithTransactions::Capella(capella_block) => {
|
||||
#[cfg(feature = "withdrawals")]
|
||||
let withdrawals = VariableList::new(capella_block.withdrawals.clone())
|
||||
.map_err(ApiError::DeserializeWithdrawals)?;
|
||||
let withdrawals = VariableList::new(
|
||||
capella_block
|
||||
.withdrawals
|
||||
.into_iter()
|
||||
.map(|w| w.into())
|
||||
.collect(),
|
||||
)
|
||||
.map_err(ApiError::DeserializeWithdrawals)?;
|
||||
|
||||
ExecutionPayload::Capella(ExecutionPayloadCapella {
|
||||
parent_hash: capella_block.parent_hash,
|
||||
@@ -1643,8 +1673,14 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
}
|
||||
ExecutionBlockWithTransactions::Eip4844(eip4844_block) => {
|
||||
#[cfg(feature = "withdrawals")]
|
||||
let withdrawals = VariableList::new(eip4844_block.withdrawals.clone())
|
||||
.map_err(ApiError::DeserializeWithdrawals)?;
|
||||
let withdrawals = VariableList::new(
|
||||
eip4844_block
|
||||
.withdrawals
|
||||
.into_iter()
|
||||
.map(|w| w.into())
|
||||
.collect(),
|
||||
)
|
||||
.map_err(ApiError::DeserializeWithdrawals)?;
|
||||
|
||||
ExecutionPayload::Eip4844(ExecutionPayloadEip4844 {
|
||||
parent_hash: eip4844_block.parent_hash,
|
||||
@@ -1714,7 +1750,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",
|
||||
@@ -1920,6 +1956,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();
|
||||
|
||||
@@ -76,7 +76,7 @@ impl<T: EthSpec> Block<T> {
|
||||
|
||||
pub fn as_execution_block_with_tx(&self) -> Option<ExecutionBlockWithTransactions<T>> {
|
||||
match self {
|
||||
Block::PoS(payload) => Some(payload.clone().into()),
|
||||
Block::PoS(payload) => Some(payload.clone().try_into().unwrap()),
|
||||
Block::PoW(_) => None,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -75,10 +75,28 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
}
|
||||
}
|
||||
ENGINE_NEW_PAYLOAD_V1 | ENGINE_NEW_PAYLOAD_V2 => {
|
||||
let request: JsonExecutionPayload<T> = get_param(params, 0)?;
|
||||
let request = match method {
|
||||
ENGINE_NEW_PAYLOAD_V1 => {
|
||||
JsonExecutionPayload::V1(get_param::<JsonExecutionPayloadV1<T>>(params, 0)?)
|
||||
}
|
||||
ENGINE_NEW_PAYLOAD_V2 => {
|
||||
JsonExecutionPayload::V2(get_param::<JsonExecutionPayloadV2<T>>(params, 0)?)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let fork = match request {
|
||||
JsonExecutionPayload::V1(_) => ForkName::Merge,
|
||||
JsonExecutionPayload::V2(ref payload) => {
|
||||
if payload.withdrawals.is_none() {
|
||||
ForkName::Merge
|
||||
} else {
|
||||
ForkName::Capella
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Canned responses set by block hash take priority.
|
||||
if let Some(status) = ctx.get_new_payload_status(&request.block_hash()) {
|
||||
if let Some(status) = ctx.get_new_payload_status(request.block_hash()) {
|
||||
return Ok(serde_json::to_value(JsonPayloadStatusV1::from(status)).unwrap());
|
||||
}
|
||||
|
||||
@@ -97,8 +115,7 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
Some(
|
||||
ctx.execution_block_generator
|
||||
.write()
|
||||
// FIXME: should this worry about other forks?
|
||||
.new_payload(request.try_into_execution_payload(ForkName::Merge).unwrap()),
|
||||
.new_payload(request.try_into_execution_payload(fork).unwrap()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@@ -120,10 +137,19 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
|
||||
Ok(serde_json::to_value(JsonExecutionPayloadV1::try_from(response).unwrap()).unwrap())
|
||||
}
|
||||
ENGINE_FORKCHOICE_UPDATED_V1 | ENGINE_FORKCHOICE_UPDATED_V2 => {
|
||||
// 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.
|
||||
@@ -153,19 +179,6 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
|
||||
Ok(serde_json::to_value(response).unwrap())
|
||||
}
|
||||
|
||||
ENGINE_GET_PAYLOAD_V2 => {
|
||||
let request: JsonPayloadIdRequest = get_param(params, 0)?;
|
||||
let id = request.into();
|
||||
|
||||
let response = ctx
|
||||
.execution_block_generator
|
||||
.write()
|
||||
.get_payload(&id)
|
||||
.ok_or_else(|| format!("no payload for id {:?}", id))?;
|
||||
|
||||
Ok(serde_json::to_value(JsonExecutionPayloadV2::try_from(response).unwrap()).unwrap())
|
||||
}
|
||||
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1 => {
|
||||
let block_generator = ctx.execution_block_generator.read();
|
||||
let transition_config: TransitionConfigurationV1 = TransitionConfigurationV1 {
|
||||
|
||||
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