Merge branch 'unstable' into electra-focil

This commit is contained in:
jacobkaufmann
2024-12-26 12:54:09 -05:00
230 changed files with 3831 additions and 3317 deletions

View File

@@ -7,7 +7,7 @@ use keccak_hash::KECCAK_EMPTY_LIST_RLP;
use triehash::ordered_trie_root;
use types::{
EncodableExecutionBlockHeader, EthSpec, ExecutionBlockHash, ExecutionBlockHeader,
ExecutionPayloadRef, Hash256,
ExecutionPayloadRef, ExecutionRequests, Hash256,
};
/// Calculate the block hash of an execution block.
@@ -17,6 +17,7 @@ use types::{
pub fn calculate_execution_block_hash<E: EthSpec>(
payload: ExecutionPayloadRef<E>,
parent_beacon_block_root: Option<Hash256>,
execution_requests: Option<&ExecutionRequests<E>>,
) -> (ExecutionBlockHash, Hash256) {
// Calculate the transactions root.
// We're currently using a deprecated Parity library for this. We should move to a
@@ -38,6 +39,7 @@ pub fn calculate_execution_block_hash<E: EthSpec>(
let rlp_blob_gas_used = payload.blob_gas_used().ok();
let rlp_excess_blob_gas = payload.excess_blob_gas().ok();
let requests_root = execution_requests.map(|requests| requests.requests_hash());
// Construct the block header.
let exec_block_header = ExecutionBlockHeader::from_payload(
@@ -48,6 +50,7 @@ pub fn calculate_execution_block_hash<E: EthSpec>(
rlp_blob_gas_used,
rlp_excess_blob_gas,
parent_beacon_block_root,
requests_root,
);
// Hash the RLP encoding of the block header.
@@ -118,6 +121,7 @@ mod test {
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_root: None,
};
let expected_rlp = "f90200a0e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ba5e000000000000000000000000000000000000a0ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7a050f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accfa029b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200000188016345785d8a00008301553482079e42a0000000000000000000000000000000000000000000000000000000000000000088000000000000000082036b";
let expected_hash =
@@ -149,6 +153,7 @@ mod test {
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_root: None,
};
let expected_rlp = "f901fda0927ca537f06c783a3a2635b8805eef1c8c2124f7444ad4a3389898dd832f2dbea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ba5e000000000000000000000000000000000000a0e97859b065bd8dbbb4519c7cb935024de2484c2b7f881181b4360492f0b06b82a050f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accfa029b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800188016345785d8a00008301553482079e42a0000000000000000000000000000000000000000000000000000000000002000088000000000000000082036b";
let expected_hash =
@@ -181,6 +186,7 @@ mod test {
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_root: None,
};
let expected_hash =
Hash256::from_str("6da69709cd5a34079b6604d29cd78fc01dacd7c6268980057ad92a2bede87351")
@@ -211,6 +217,7 @@ mod test {
blob_gas_used: Some(0x0u64),
excess_blob_gas: Some(0x0u64),
parent_beacon_block_root: Some(Hash256::from_str("f7d327d2c04e4f12e9cdd492e53d39a1d390f8b1571e3b2a22ac6e1e170e5b1a").unwrap()),
requests_root: None,
};
let expected_hash =
Hash256::from_str("a7448e600ead0a23d16f96aa46e8dea9eef8a7c5669a5f0a5ff32709afe9c408")
@@ -221,29 +228,30 @@ mod test {
#[test]
fn test_rlp_encode_block_electra() {
let header = ExecutionBlockHeader {
parent_hash: Hash256::from_str("172864416698b842f4c92f7b476be294b4ef720202779df194cd225f531053ab").unwrap(),
parent_hash: Hash256::from_str("a628f146df398a339768bd101f7dc41d828be79aca5dd02cc878a51bdbadd761").unwrap(),
ommers_hash: Hash256::from_str("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(),
beneficiary: Address::from_str("878705ba3f8bc32fcf7f4caa1a35e72af65cf766").unwrap(),
state_root: Hash256::from_str("c6457d0df85c84c62d1c68f68138b6e796e8a44fb44de221386fb2d5611c41e0").unwrap(),
transactions_root: Hash256::from_str("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").unwrap(),
receipts_root: Hash256::from_str("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").unwrap(),
logs_bloom:<[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(),
beneficiary: Address::from_str("f97e180c050e5ab072211ad2c213eb5aee4df134").unwrap(),
state_root: Hash256::from_str("fdff009f8280bd113ebb4df8ce4e2dcc9322d43184a0b506e70b7f4823ca1253").unwrap(),
transactions_root: Hash256::from_str("452806578b4fa881cafb019c47e767e37e2249accf859159f00cddefb2579bb5").unwrap(),
receipts_root: Hash256::from_str("72ceac0f16a32041c881b3220d39ca506a286bef163c01a4d0821cd4027d31c7").unwrap(),
logs_bloom:<[u8; 256]>::from_hex("10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000").unwrap().into(),
difficulty: Uint256::ZERO,
number: Uint256::from(97),
gas_limit: Uint256::from(27482534),
gas_used: Uint256::ZERO,
timestamp: 1692132829u64,
extra_data: hex::decode("d883010d00846765746888676f312e32302e37856c696e7578").unwrap(),
mix_hash: Hash256::from_str("0b493c22d2ad4ca76c77ae6ad916af429b42b1dc98fdcb8e5ddbd049bbc5d623").unwrap(),
number: Uint256::from(8230),
gas_limit: Uint256::from(30000000),
gas_used: Uint256::from(3716848),
timestamp: 1730921268,
extra_data: hex::decode("d883010e0c846765746888676f312e32332e32856c696e7578").unwrap(),
mix_hash: Hash256::from_str("e87ca9a45b2e61bbe9080d897db1d584b5d2367d22e898af901091883b7b96ec").unwrap(),
nonce: Hash64::ZERO,
base_fee_per_gas: Uint256::from(2374u64),
base_fee_per_gas: Uint256::from(7u64),
withdrawals_root: Some(Hash256::from_str("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").unwrap()),
blob_gas_used: Some(0x0u64),
excess_blob_gas: Some(0x0u64),
parent_beacon_block_root: Some(Hash256::from_str("f7d327d2c04e4f12e9cdd492e53d39a1d390f8b1571e3b2a22ac6e1e170e5b1a").unwrap()),
blob_gas_used: Some(786432),
excess_blob_gas: Some(44695552),
parent_beacon_block_root: Some(Hash256::from_str("f3a888fee010ebb1ae083547004e96c254b240437823326fdff8354b1fc25629").unwrap()),
requests_root: Some(Hash256::from_str("9440d3365f07573919e1e9ac5178c20ec6fe267357ee4baf8b6409901f331b62").unwrap()),
};
let expected_hash =
Hash256::from_str("a7448e600ead0a23d16f96aa46e8dea9eef8a7c5669a5f0a5ff32709afe9c408")
Hash256::from_str("61e67afc96bf21be6aab52c1ace1db48de7b83f03119b0644deb4b69e87e09e1")
.unwrap();
test_rlp_encoding(&header, None, expected_hash);
}

View File

@@ -833,7 +833,7 @@ impl HttpJsonRpc {
new_payload_request_electra.versioned_hashes,
new_payload_request_electra.parent_beacon_block_root,
new_payload_request_electra
.execution_requests_list
.execution_requests
.get_execution_requests_list(),
]);

View File

@@ -6,7 +6,9 @@ use strum::EnumString;
use superstruct::superstruct;
use types::beacon_block_body::KzgCommitments;
use types::blob_sidecar::BlobsList;
use types::execution_requests::{ConsolidationRequests, DepositRequests, WithdrawalRequests};
use types::execution_requests::{
ConsolidationRequests, DepositRequests, RequestPrefix, WithdrawalRequests,
};
use types::{Blob, FixedVector, KzgProof, Unsigned};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
@@ -339,25 +341,6 @@ impl<E: EthSpec> From<JsonExecutionPayload<E>> for ExecutionPayload<E> {
}
}
/// This is used to index into the `execution_requests` array.
#[derive(Debug, Copy, Clone)]
enum RequestPrefix {
Deposit,
Withdrawal,
Consolidation,
}
impl RequestPrefix {
pub fn from_prefix(prefix: u8) -> Option<Self> {
match prefix {
0 => Some(Self::Deposit),
1 => Some(Self::Withdrawal),
2 => Some(Self::Consolidation),
_ => None,
}
}
}
/// Format of `ExecutionRequests` received over the engine api.
///
/// Array of ssz-encoded requests list encoded as hex bytes.
@@ -379,7 +362,8 @@ impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequests<E> {
for (i, request) in value.0.into_iter().enumerate() {
// hex string
let decoded_bytes = hex::decode(request).map_err(|e| format!("Invalid hex {:?}", e))?;
let decoded_bytes = hex::decode(request.strip_prefix("0x").unwrap_or(&request))
.map_err(|e| format!("Invalid hex {:?}", e))?;
match RequestPrefix::from_prefix(i as u8) {
Some(RequestPrefix::Deposit) => {
requests.deposits = DepositRequests::<E>::from_ssz_bytes(&decoded_bytes)
@@ -431,7 +415,7 @@ pub struct JsonGetPayloadResponse<E: EthSpec> {
#[superstruct(only(V3, V4))]
pub should_override_builder: bool,
#[superstruct(only(V4))]
pub requests: JsonExecutionRequests,
pub execution_requests: JsonExecutionRequests,
}
impl<E: EthSpec> TryFrom<JsonGetPayloadResponse<E>> for GetPayloadResponse<E> {
@@ -464,7 +448,7 @@ impl<E: EthSpec> TryFrom<JsonGetPayloadResponse<E>> for GetPayloadResponse<E> {
block_value: response.block_value,
blobs_bundle: response.blobs_bundle.into(),
should_override_builder: response.should_override_builder,
requests: response.requests.try_into()?,
requests: response.execution_requests.try_into()?,
}))
}
}

View File

@@ -44,7 +44,7 @@ pub struct NewPayloadRequest<'block, E: EthSpec> {
#[superstruct(only(Deneb, Electra))]
pub parent_beacon_block_root: Hash256,
#[superstruct(only(Electra))]
pub execution_requests_list: &'block ExecutionRequests<E>,
pub execution_requests: &'block ExecutionRequests<E>,
}
impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
@@ -121,8 +121,11 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
let _timer = metrics::start_timer(&metrics::EXECUTION_LAYER_VERIFY_BLOCK_HASH);
let (header_hash, rlp_transactions_root) =
calculate_execution_block_hash(payload, parent_beacon_block_root);
let (header_hash, rlp_transactions_root) = calculate_execution_block_hash(
payload,
parent_beacon_block_root,
self.execution_requests().ok().copied(),
);
if header_hash != self.block_hash() {
return Err(Error::BlockHashMismatch {
@@ -185,7 +188,7 @@ impl<'a, E: EthSpec> TryFrom<BeaconBlockRef<'a, E>> for NewPayloadRequest<'a, E>
.map(kzg_commitment_to_versioned_hash)
.collect(),
parent_beacon_block_root: block_ref.parent_root,
execution_requests_list: &block_ref.body.execution_requests,
execution_requests: &block_ref.body.execution_requests,
})),
}
}

View File

@@ -28,7 +28,7 @@ use sensitive_url::SensitiveUrl;
use serde::{Deserialize, Serialize};
use slog::{crit, debug, error, info, warn, Logger};
use slot_clock::SlotClock;
use std::collections::HashMap;
use std::collections::{hash_map::Entry, HashMap};
use std::fmt;
use std::future::Future;
use std::io::Write;
@@ -319,10 +319,52 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> BlockProposalContents<E, Paylo
}
}
// This just groups together a bunch of parameters that commonly
// get passed around together in calls to get_payload.
#[derive(Clone, Copy)]
pub struct PayloadParameters<'a> {
pub parent_hash: ExecutionBlockHash,
pub parent_gas_limit: u64,
pub proposer_gas_limit: Option<u64>,
pub payload_attributes: &'a PayloadAttributes,
pub forkchoice_update_params: &'a ForkchoiceUpdateParameters,
pub current_fork: ForkName,
}
#[derive(Clone, PartialEq)]
pub struct ProposerPreparationDataEntry {
update_epoch: Epoch,
preparation_data: ProposerPreparationData,
gas_limit: Option<u64>,
}
impl ProposerPreparationDataEntry {
pub fn update(&mut self, updated: Self) -> bool {
let mut changed = false;
// Update `gas_limit` if `updated.gas_limit` is `Some` and:
// - `self.gas_limit` is `None`, or
// - both are `Some` but the values differ.
if let Some(updated_gas_limit) = updated.gas_limit {
if self.gas_limit != Some(updated_gas_limit) {
self.gas_limit = Some(updated_gas_limit);
changed = true;
}
}
// Update `update_epoch` if it differs
if self.update_epoch != updated.update_epoch {
self.update_epoch = updated.update_epoch;
changed = true;
}
// Update `preparation_data` if it differs
if self.preparation_data != updated.preparation_data {
self.preparation_data = updated.preparation_data;
changed = true;
}
changed
}
}
#[derive(Hash, PartialEq, Eq)]
@@ -711,23 +753,29 @@ impl<E: EthSpec> ExecutionLayer<E> {
}
/// Updates the proposer preparation data provided by validators
pub async fn update_proposer_preparation(
&self,
update_epoch: Epoch,
preparation_data: &[ProposerPreparationData],
) {
pub async fn update_proposer_preparation<'a, I>(&self, update_epoch: Epoch, proposer_data: I)
where
I: IntoIterator<Item = (&'a ProposerPreparationData, &'a Option<u64>)>,
{
let mut proposer_preparation_data = self.proposer_preparation_data().await;
for preparation_entry in preparation_data {
for (preparation_entry, gas_limit) in proposer_data {
let new = ProposerPreparationDataEntry {
update_epoch,
preparation_data: preparation_entry.clone(),
gas_limit: *gas_limit,
};
let existing =
proposer_preparation_data.insert(preparation_entry.validator_index, new.clone());
if existing != Some(new) {
metrics::inc_counter(&metrics::EXECUTION_LAYER_PROPOSER_DATA_UPDATED);
match proposer_preparation_data.entry(preparation_entry.validator_index) {
Entry::Occupied(mut entry) => {
if entry.get_mut().update(new) {
metrics::inc_counter(&metrics::EXECUTION_LAYER_PROPOSER_DATA_UPDATED);
}
}
Entry::Vacant(entry) => {
entry.insert(new);
metrics::inc_counter(&metrics::EXECUTION_LAYER_PROPOSER_DATA_UPDATED);
}
}
}
}
@@ -809,6 +857,13 @@ impl<E: EthSpec> ExecutionLayer<E> {
}
}
pub async fn get_proposer_gas_limit(&self, proposer_index: u64) -> Option<u64> {
self.proposer_preparation_data()
.await
.get(&proposer_index)
.and_then(|entry| entry.gas_limit)
}
/// Maps to the `engine_getPayload` JSON-RPC call.
///
/// However, it will attempt to call `self.prepare_payload` if it cannot find an existing
@@ -818,14 +873,10 @@ impl<E: EthSpec> ExecutionLayer<E> {
///
/// The result will be returned from the first node that returns successfully. No more nodes
/// will be contacted.
#[allow(clippy::too_many_arguments)]
pub async fn get_payload(
&self,
parent_hash: ExecutionBlockHash,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
payload_parameters: PayloadParameters<'_>,
builder_params: BuilderParams,
current_fork: ForkName,
spec: &ChainSpec,
builder_boost_factor: Option<u64>,
block_production_version: BlockProductionVersion,
@@ -833,11 +884,8 @@ impl<E: EthSpec> ExecutionLayer<E> {
let payload_result_type = match block_production_version {
BlockProductionVersion::V3 => match self
.determine_and_fetch_payload(
parent_hash,
payload_attributes,
forkchoice_update_params,
payload_parameters,
builder_params,
current_fork,
builder_boost_factor,
spec,
)
@@ -857,25 +905,11 @@ impl<E: EthSpec> ExecutionLayer<E> {
&metrics::EXECUTION_LAYER_REQUEST_TIMES,
&[metrics::GET_BLINDED_PAYLOAD],
);
self.determine_and_fetch_payload(
parent_hash,
payload_attributes,
forkchoice_update_params,
builder_params,
current_fork,
None,
spec,
)
.await?
self.determine_and_fetch_payload(payload_parameters, builder_params, None, spec)
.await?
}
BlockProductionVersion::FullV2 => self
.get_full_payload_with(
parent_hash,
payload_attributes,
forkchoice_update_params,
current_fork,
noop,
)
.get_full_payload_with(payload_parameters, noop)
.await
.and_then(GetPayloadResponseType::try_into)
.map(ProvenancedPayload::Local)?,
@@ -922,17 +956,15 @@ impl<E: EthSpec> ExecutionLayer<E> {
async fn fetch_builder_and_local_payloads(
&self,
builder: &BuilderHttpClient,
parent_hash: ExecutionBlockHash,
builder_params: &BuilderParams,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
current_fork: ForkName,
payload_parameters: PayloadParameters<'_>,
) -> (
Result<Option<ForkVersionedResponse<SignedBuilderBid<E>>>, builder_client::Error>,
Result<GetPayloadResponse<E>, Error>,
) {
let slot = builder_params.slot;
let pubkey = &builder_params.pubkey;
let parent_hash = payload_parameters.parent_hash;
info!(
self.log(),
@@ -950,17 +982,12 @@ impl<E: EthSpec> ExecutionLayer<E> {
.await
}),
timed_future(metrics::GET_BLINDED_PAYLOAD_LOCAL, async {
self.get_full_payload_caching(
parent_hash,
payload_attributes,
forkchoice_update_params,
current_fork,
)
.await
.and_then(|local_result_type| match local_result_type {
GetPayloadResponseType::Full(payload) => Ok(payload),
GetPayloadResponseType::Blinded(_) => Err(Error::PayloadTypeMismatch),
})
self.get_full_payload_caching(payload_parameters)
.await
.and_then(|local_result_type| match local_result_type {
GetPayloadResponseType::Full(payload) => Ok(payload),
GetPayloadResponseType::Blinded(_) => Err(Error::PayloadTypeMismatch),
})
})
);
@@ -984,26 +1011,17 @@ impl<E: EthSpec> ExecutionLayer<E> {
(relay_result, local_result)
}
#[allow(clippy::too_many_arguments)]
async fn determine_and_fetch_payload(
&self,
parent_hash: ExecutionBlockHash,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
payload_parameters: PayloadParameters<'_>,
builder_params: BuilderParams,
current_fork: ForkName,
builder_boost_factor: Option<u64>,
spec: &ChainSpec,
) -> Result<ProvenancedPayload<BlockProposalContentsType<E>>, Error> {
let Some(builder) = self.builder() else {
// no builder.. return local payload
return self
.get_full_payload_caching(
parent_hash,
payload_attributes,
forkchoice_update_params,
current_fork,
)
.get_full_payload_caching(payload_parameters)
.await
.and_then(GetPayloadResponseType::try_into)
.map(ProvenancedPayload::Local);
@@ -1034,26 +1052,15 @@ impl<E: EthSpec> ExecutionLayer<E> {
),
}
return self
.get_full_payload_caching(
parent_hash,
payload_attributes,
forkchoice_update_params,
current_fork,
)
.get_full_payload_caching(payload_parameters)
.await
.and_then(GetPayloadResponseType::try_into)
.map(ProvenancedPayload::Local);
}
let parent_hash = payload_parameters.parent_hash;
let (relay_result, local_result) = self
.fetch_builder_and_local_payloads(
builder.as_ref(),
parent_hash,
&builder_params,
payload_attributes,
forkchoice_update_params,
current_fork,
)
.fetch_builder_and_local_payloads(builder.as_ref(), &builder_params, payload_parameters)
.await;
match (relay_result, local_result) {
@@ -1118,14 +1125,9 @@ impl<E: EthSpec> ExecutionLayer<E> {
);
// check relay payload validity
if let Err(reason) = verify_builder_bid(
&relay,
parent_hash,
payload_attributes,
Some(local.block_number()),
current_fork,
spec,
) {
if let Err(reason) =
verify_builder_bid(&relay, payload_parameters, Some(local.block_number()), spec)
{
// relay payload invalid -> return local
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS,
@@ -1202,14 +1204,7 @@ impl<E: EthSpec> ExecutionLayer<E> {
"parent_hash" => ?parent_hash,
);
match verify_builder_bid(
&relay,
parent_hash,
payload_attributes,
None,
current_fork,
spec,
) {
match verify_builder_bid(&relay, payload_parameters, None, spec) {
Ok(()) => Ok(ProvenancedPayload::try_from(relay.data.message)?),
Err(reason) => {
metrics::inc_counter_vec(
@@ -1234,32 +1229,28 @@ impl<E: EthSpec> ExecutionLayer<E> {
/// Get a full payload and cache its result in the execution layer's payload cache.
async fn get_full_payload_caching(
&self,
parent_hash: ExecutionBlockHash,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
current_fork: ForkName,
payload_parameters: PayloadParameters<'_>,
) -> Result<GetPayloadResponseType<E>, Error> {
self.get_full_payload_with(
parent_hash,
payload_attributes,
forkchoice_update_params,
current_fork,
Self::cache_payload,
)
.await
self.get_full_payload_with(payload_parameters, Self::cache_payload)
.await
}
async fn get_full_payload_with(
&self,
parent_hash: ExecutionBlockHash,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
current_fork: ForkName,
payload_parameters: PayloadParameters<'_>,
cache_fn: fn(
&ExecutionLayer<E>,
PayloadContentsRefTuple<E>,
) -> Option<FullPayloadContents<E>>,
) -> Result<GetPayloadResponseType<E>, Error> {
let PayloadParameters {
parent_hash,
payload_attributes,
forkchoice_update_params,
current_fork,
..
} = payload_parameters;
self.engine()
.request(move |engine| async move {
let payload_id = if let Some(id) = engine
@@ -1997,6 +1988,10 @@ enum InvalidBuilderPayload {
payload: Option<Hash256>,
expected: Option<Hash256>,
},
GasLimitMismatch {
payload: u64,
expected: u64,
},
}
impl fmt::Display for InvalidBuilderPayload {
@@ -2035,19 +2030,51 @@ impl fmt::Display for InvalidBuilderPayload {
opt_string(expected)
)
}
InvalidBuilderPayload::GasLimitMismatch { payload, expected } => {
write!(f, "payload gas limit was {} not {}", payload, expected)
}
}
}
}
/// Calculate the expected gas limit for a block.
pub fn expected_gas_limit(
parent_gas_limit: u64,
target_gas_limit: u64,
spec: &ChainSpec,
) -> Option<u64> {
// Calculate the maximum gas limit difference allowed safely
let max_gas_limit_difference = parent_gas_limit
.checked_div(spec.gas_limit_adjustment_factor)
.and_then(|result| result.checked_sub(1))
.unwrap_or(0);
// Adjust the gas limit safely
if target_gas_limit > parent_gas_limit {
let gas_diff = target_gas_limit.saturating_sub(parent_gas_limit);
parent_gas_limit.checked_add(std::cmp::min(gas_diff, max_gas_limit_difference))
} else {
let gas_diff = parent_gas_limit.saturating_sub(target_gas_limit);
parent_gas_limit.checked_sub(std::cmp::min(gas_diff, max_gas_limit_difference))
}
}
/// Perform some cursory, non-exhaustive validation of the bid returned from the builder.
fn verify_builder_bid<E: EthSpec>(
bid: &ForkVersionedResponse<SignedBuilderBid<E>>,
parent_hash: ExecutionBlockHash,
payload_attributes: &PayloadAttributes,
payload_parameters: PayloadParameters<'_>,
block_number: Option<u64>,
current_fork: ForkName,
spec: &ChainSpec,
) -> Result<(), Box<InvalidBuilderPayload>> {
let PayloadParameters {
parent_hash,
payload_attributes,
current_fork,
parent_gas_limit,
proposer_gas_limit,
..
} = payload_parameters;
let is_signature_valid = bid.data.verify_signature(spec);
let header = &bid.data.message.header();
@@ -2063,6 +2090,8 @@ fn verify_builder_bid<E: EthSpec>(
.cloned()
.map(|withdrawals| Withdrawals::<E>::from(withdrawals).tree_hash_root());
let payload_withdrawals_root = header.withdrawals_root().ok();
let expected_gas_limit = proposer_gas_limit
.and_then(|target_gas_limit| expected_gas_limit(parent_gas_limit, target_gas_limit, spec));
if header.parent_hash() != parent_hash {
Err(Box::new(InvalidBuilderPayload::ParentHash {
@@ -2099,6 +2128,14 @@ fn verify_builder_bid<E: EthSpec>(
payload: payload_withdrawals_root,
expected: expected_withdrawals_root,
}))
} else if expected_gas_limit
.map(|gas_limit| header.gas_limit() != gas_limit)
.unwrap_or(false)
{
Err(Box::new(InvalidBuilderPayload::GasLimitMismatch {
payload: header.gas_limit(),
expected: expected_gas_limit.unwrap_or(0),
}))
} else {
Ok(())
}
@@ -2151,6 +2188,27 @@ mod test {
.await;
}
#[tokio::test]
async fn test_expected_gas_limit() {
let spec = ChainSpec::mainnet();
assert_eq!(
expected_gas_limit(30_000_000, 30_000_000, &spec),
Some(30_000_000)
);
assert_eq!(
expected_gas_limit(30_000_000, 40_000_000, &spec),
Some(30_029_295)
);
assert_eq!(
expected_gas_limit(30_029_295, 40_000_000, &spec),
Some(30_058_619)
);
assert_eq!(
expected_gas_limit(30_058_619, 30_000_000, &spec),
Some(30_029_266)
);
}
#[tokio::test]
async fn test_forked_terminal_block() {
let runtime = TestRuntime::default();

View File

@@ -28,8 +28,8 @@ use super::DEFAULT_TERMINAL_BLOCK;
const TEST_BLOB_BUNDLE: &[u8] = include_bytes!("fixtures/mainnet/test_blobs_bundle.ssz");
const GAS_LIMIT: u64 = 16384;
const GAS_USED: u64 = GAS_LIMIT - 1;
pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
const GAS_USED: u64 = DEFAULT_GAS_LIMIT - 1;
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)] // This struct is only for testing.
@@ -38,6 +38,10 @@ pub enum Block<E: EthSpec> {
PoS(ExecutionPayload<E>),
}
pub fn mock_el_extra_data<E: EthSpec>() -> types::VariableList<u8, E::MaxExtraDataBytes> {
"block gen was here".as_bytes().to_vec().into()
}
impl<E: EthSpec> Block<E> {
pub fn block_number(&self) -> u64 {
match self {
@@ -67,6 +71,13 @@ impl<E: EthSpec> Block<E> {
}
}
pub fn gas_limit(&self) -> u64 {
match self {
Block::PoW(_) => DEFAULT_GAS_LIMIT,
Block::PoS(payload) => payload.gas_limit(),
}
}
pub fn as_execution_block(&self, total_difficulty: Uint256) -> ExecutionBlock {
match self {
Block::PoW(block) => ExecutionBlock {
@@ -570,10 +581,10 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
logs_bloom: vec![0; 256].into(),
prev_randao: pa.prev_randao,
block_number: parent.block_number() + 1,
gas_limit: GAS_LIMIT,
gas_limit: DEFAULT_GAS_LIMIT,
gas_used: GAS_USED,
timestamp: pa.timestamp,
extra_data: "block gen was here".as_bytes().to_vec().into(),
extra_data: mock_el_extra_data::<E>(),
base_fee_per_gas: Uint256::from(1u64),
block_hash: ExecutionBlockHash::zero(),
transactions: vec![].into(),
@@ -587,10 +598,10 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
logs_bloom: vec![0; 256].into(),
prev_randao: pa.prev_randao,
block_number: parent.block_number() + 1,
gas_limit: GAS_LIMIT,
gas_limit: DEFAULT_GAS_LIMIT,
gas_used: GAS_USED,
timestamp: pa.timestamp,
extra_data: "block gen was here".as_bytes().to_vec().into(),
extra_data: mock_el_extra_data::<E>(),
base_fee_per_gas: Uint256::from(1u64),
block_hash: ExecutionBlockHash::zero(),
transactions: vec![].into(),
@@ -603,10 +614,10 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
logs_bloom: vec![0; 256].into(),
prev_randao: pa.prev_randao,
block_number: parent.block_number() + 1,
gas_limit: GAS_LIMIT,
gas_limit: DEFAULT_GAS_LIMIT,
gas_used: GAS_USED,
timestamp: pa.timestamp,
extra_data: "block gen was here".as_bytes().to_vec().into(),
extra_data: mock_el_extra_data::<E>(),
base_fee_per_gas: Uint256::from(1u64),
block_hash: ExecutionBlockHash::zero(),
transactions: vec![].into(),
@@ -623,10 +634,10 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
logs_bloom: vec![0; 256].into(),
prev_randao: pa.prev_randao,
block_number: parent.block_number() + 1,
gas_limit: GAS_LIMIT,
gas_limit: DEFAULT_GAS_LIMIT,
gas_used: GAS_USED,
timestamp: pa.timestamp,
extra_data: "block gen was here".as_bytes().to_vec().into(),
extra_data: mock_el_extra_data::<E>(),
base_fee_per_gas: Uint256::from(1u64),
block_hash: ExecutionBlockHash::zero(),
transactions: vec![].into(),
@@ -642,10 +653,10 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
logs_bloom: vec![0; 256].into(),
prev_randao: pa.prev_randao,
block_number: parent.block_number() + 1,
gas_limit: GAS_LIMIT,
gas_limit: DEFAULT_GAS_LIMIT,
gas_used: GAS_USED,
timestamp: pa.timestamp,
extra_data: "block gen was here".as_bytes().to_vec().into(),
extra_data: mock_el_extra_data::<E>(),
base_fee_per_gas: Uint256::from(1u64),
block_hash: ExecutionBlockHash::zero(),
transactions: vec![].into(),

View File

@@ -374,7 +374,7 @@ pub async fn handle_rpc<E: EthSpec>(
.into(),
should_override_builder: false,
// TODO(electra): add EL requests in mock el
requests: Default::default(),
execution_requests: Default::default(),
})
.unwrap()
}

View File

@@ -1,5 +1,5 @@
use crate::test_utils::{DEFAULT_BUILDER_PAYLOAD_VALUE_WEI, DEFAULT_JWT_SECRET};
use crate::{Config, ExecutionLayer, PayloadAttributes};
use crate::{Config, ExecutionLayer, PayloadAttributes, PayloadParameters};
use eth2::types::{BlobsBundle, BlockId, StateId, ValidatorId};
use eth2::{BeaconNodeHttpClient, Timeouts, CONSENSUS_VERSION_HEADER};
use fork_choice::ForkchoiceUpdateParameters;
@@ -54,6 +54,10 @@ impl Operation {
}
}
pub fn mock_builder_extra_data<E: EthSpec>() -> types::VariableList<u8, E::MaxExtraDataBytes> {
"mock_builder".as_bytes().to_vec().into()
}
#[derive(Debug)]
// We don't use the string value directly, but it's used in the Debug impl which is required by `warp::reject::Reject`.
struct Custom(#[allow(dead_code)] String);
@@ -72,6 +76,8 @@ pub trait BidStuff<E: EthSpec> {
fn set_withdrawals_root(&mut self, withdrawals_root: Hash256);
fn sign_builder_message(&mut self, sk: &SecretKey, spec: &ChainSpec) -> Signature;
fn stamp_payload(&mut self);
}
impl<E: EthSpec> BidStuff<E> for BuilderBid<E> {
@@ -203,6 +209,29 @@ impl<E: EthSpec> BidStuff<E> for BuilderBid<E> {
let message = self.signing_root(domain);
sk.sign(message)
}
// this helps differentiate a builder block from a regular block
fn stamp_payload(&mut self) {
let extra_data = mock_builder_extra_data::<E>();
match self.to_mut().header_mut() {
ExecutionPayloadHeaderRefMut::Bellatrix(header) => {
header.extra_data = extra_data;
header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root());
}
ExecutionPayloadHeaderRefMut::Capella(header) => {
header.extra_data = extra_data;
header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root());
}
ExecutionPayloadHeaderRefMut::Deneb(header) => {
header.extra_data = extra_data;
header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root());
}
ExecutionPayloadHeaderRefMut::Electra(header) => {
header.extra_data = extra_data;
header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root());
}
}
}
}
#[derive(Clone)]
@@ -286,6 +315,7 @@ impl<E: EthSpec> MockBuilder<E> {
while let Some(op) = guard.pop() {
op.apply(bid);
}
bid.stamp_payload();
}
}
@@ -413,11 +443,12 @@ pub fn serve<E: EthSpec>(
let block = head.data.message();
let head_block_root = block.tree_hash_root();
let head_execution_hash = block
let head_execution_payload = block
.body()
.execution_payload()
.map_err(|_| reject("pre-merge block"))?
.block_hash();
.map_err(|_| reject("pre-merge block"))?;
let head_execution_hash = head_execution_payload.block_hash();
let head_gas_limit = head_execution_payload.gas_limit();
if head_execution_hash != parent_hash {
return Err(reject("head mismatch"));
}
@@ -529,14 +560,24 @@ pub fn serve<E: EthSpec>(
finalized_hash: Some(finalized_execution_hash),
};
let proposer_gas_limit = builder
.val_registration_cache
.read()
.get(&pubkey)
.map(|v| v.message.gas_limit);
let payload_parameters = PayloadParameters {
parent_hash: head_execution_hash,
parent_gas_limit: head_gas_limit,
proposer_gas_limit,
payload_attributes: &payload_attributes,
forkchoice_update_params: &forkchoice_update_params,
current_fork: fork,
};
let payload_response_type = builder
.el
.get_full_payload_caching(
head_execution_hash,
&payload_attributes,
forkchoice_update_params,
fork,
)
.get_full_payload_caching(payload_parameters)
.await
.map_err(|_| reject("couldn't get payload"))?;
@@ -648,8 +689,6 @@ pub fn serve<E: EthSpec>(
}
};
message.set_gas_limit(cached_data.gas_limit);
builder.apply_operations(&mut message);
let mut signature =

View File

@@ -90,6 +90,7 @@ impl<E: EthSpec> MockExecutionLayer<E> {
};
let parent_hash = latest_execution_block.block_hash();
let parent_gas_limit = latest_execution_block.gas_limit();
let block_number = latest_execution_block.block_number() + 1;
let timestamp = block_number;
let prev_randao = Hash256::from_low_u64_be(block_number);
@@ -131,14 +132,20 @@ impl<E: EthSpec> MockExecutionLayer<E> {
let payload_attributes =
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None);
let payload_parameters = PayloadParameters {
parent_hash,
parent_gas_limit,
proposer_gas_limit: None,
payload_attributes: &payload_attributes,
forkchoice_update_params: &forkchoice_update_params,
current_fork: ForkName::Bellatrix,
};
let block_proposal_content_type = self
.el
.get_payload(
parent_hash,
&payload_attributes,
forkchoice_update_params,
payload_parameters,
builder_params,
ForkName::Bellatrix,
&self.spec,
None,
BlockProductionVersion::FullV2,
@@ -171,14 +178,20 @@ impl<E: EthSpec> MockExecutionLayer<E> {
let payload_attributes =
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None);
let payload_parameters = PayloadParameters {
parent_hash,
parent_gas_limit,
proposer_gas_limit: None,
payload_attributes: &payload_attributes,
forkchoice_update_params: &forkchoice_update_params,
current_fork: ForkName::Bellatrix,
};
let block_proposal_content_type = self
.el
.get_payload(
parent_hash,
&payload_attributes,
forkchoice_update_params,
payload_parameters,
builder_params,
ForkName::Bellatrix,
&self.spec,
None,
BlockProductionVersion::BlindedV2,

View File

@@ -25,12 +25,13 @@ use types::{EthSpec, ExecutionBlockHash, Uint256};
use warp::{http::StatusCode, Filter, Rejection};
use crate::EngineCapabilities;
pub use execution_block_generator::DEFAULT_GAS_LIMIT;
pub use execution_block_generator::{
generate_blobs, generate_genesis_block, generate_genesis_header, generate_pow_block,
static_valid_tx, Block, ExecutionBlockGenerator,
mock_el_extra_data, static_valid_tx, Block, ExecutionBlockGenerator,
};
pub use hook::Hook;
pub use mock_builder::{MockBuilder, Operation};
pub use mock_builder::{mock_builder_extra_data, MockBuilder, Operation};
pub use mock_execution_layer::MockExecutionLayer;
pub const DEFAULT_TERMINAL_DIFFICULTY: u64 = 6400;