merge with capella

This commit is contained in:
realbigsean
2022-12-15 09:33:18 -05:00
95 changed files with 3783 additions and 539 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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);
}
}

View File

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

View File

@@ -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.
//