Capella eip 4844 cleanup (#3652)

* add capella gossip boiler plate

* get everything compiling

Co-authored-by: realbigsean <sean@sigmaprime.io
Co-authored-by: Mark Mackey <mark@sigmaprime.io>

* small cleanup

* small cleanup

* cargo fix + some test cleanup

* improve block production

* add fixme for potential panic

Co-authored-by: Mark Mackey <mark@sigmaprime.io>
This commit is contained in:
realbigsean
2022-10-26 15:15:26 -04:00
committed by GitHub
parent 221c433d62
commit 137f230344
52 changed files with 1392 additions and 630 deletions

View File

@@ -1,13 +1,16 @@
use crate::engines::ForkChoiceState;
pub use ethers_core::types::Transaction;
use ethers_core::utils::rlp::{Decodable, Rlp};
use http::deposit_methods::RpcError;
pub use json_structures::TransitionConfigurationV1;
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use strum::IntoStaticStr;
use superstruct::superstruct;
use types::Withdrawal;
pub use types::{
blob::Blob, Address, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader,
FixedVector, Hash256, KzgCommitment, KzgProof, Uint256, VariableList,
Address, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader, FixedVector,
Hash256, Uint256, VariableList,
};
pub mod auth;
@@ -38,7 +41,9 @@ pub enum Error {
PayloadConversionLogicFlaw,
DeserializeTransaction(ssz_types::Error),
DeserializeTransactions(ssz_types::Error),
DeserializeWithdrawals(ssz_types::Error),
BuilderApi(builder_client::Error),
IncorrectStateVariant,
}
impl From<reqwest::Error> for Error {
@@ -111,9 +116,18 @@ pub struct ExecutionBlock {
pub timestamp: u64,
}
/// Representation of an exection block with enough detail to reconstruct a payload.
/// Representation of an execution block with enough detail to reconstruct a payload.
#[superstruct(
variants(Merge, Capella, Eip4844),
variant_attributes(
derive(Clone, Debug, PartialEq, Serialize, Deserialize,),
serde(bound = "T: EthSpec", rename_all = "camelCase"),
),
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(bound = "T: EthSpec", rename_all = "camelCase", untagged)]
pub struct ExecutionBlockWithTransactions<T: EthSpec> {
pub parent_hash: ExecutionBlockHash,
#[serde(alias = "miner")]
@@ -135,16 +149,120 @@ pub struct ExecutionBlockWithTransactions<T: EthSpec> {
#[serde(with = "ssz_types::serde_utils::hex_var_list")]
pub extra_data: VariableList<u8, T::MaxExtraDataBytes>,
pub base_fee_per_gas: Uint256,
#[superstruct(only(Eip4844))]
#[serde(with = "eth2_serde_utils::u64_hex_be")]
pub excess_blobs: u64,
#[serde(rename = "hash")]
pub block_hash: ExecutionBlockHash,
pub transactions: Vec<Transaction>,
#[superstruct(only(Capella, Eip4844))]
pub withdrawals: Vec<Withdrawal>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
impl<T: EthSpec> From<ExecutionPayload<T>> for ExecutionBlockWithTransactions<T> {
fn from(payload: ExecutionPayload<T>) -> Self {
match payload {
ExecutionPayload::Merge(block) => Self::Merge(ExecutionBlockWithTransactionsMerge {
parent_hash: block.parent_hash,
fee_recipient: block.fee_recipient,
state_root: block.state_root,
receipts_root: block.receipts_root,
logs_bloom: block.logs_bloom,
prev_randao: block.prev_randao,
block_number: block.block_number,
gas_limit: block.gas_limit,
gas_used: block.gas_used,
timestamp: block.timestamp,
extra_data: block.extra_data,
base_fee_per_gas: block.base_fee_per_gas,
block_hash: block.block_hash,
transactions: block
.transactions
.iter()
.map(|tx| Transaction::decode(&Rlp::new(tx)))
.collect::<Result<Vec<_>, _>>()
.unwrap_or_else(|_| Vec::new()),
}),
ExecutionPayload::Capella(block) => {
Self::Capella(ExecutionBlockWithTransactionsCapella {
parent_hash: block.parent_hash,
fee_recipient: block.fee_recipient,
state_root: block.state_root,
receipts_root: block.receipts_root,
logs_bloom: block.logs_bloom,
prev_randao: block.prev_randao,
block_number: block.block_number,
gas_limit: block.gas_limit,
gas_used: block.gas_used,
timestamp: block.timestamp,
extra_data: block.extra_data,
base_fee_per_gas: block.base_fee_per_gas,
block_hash: block.block_hash,
transactions: block
.transactions
.iter()
.map(|tx| Transaction::decode(&Rlp::new(tx)))
.collect::<Result<Vec<_>, _>>()
.unwrap_or_else(|_| Vec::new()),
withdrawals: block.withdrawals.into(),
})
}
ExecutionPayload::Eip4844(block) => {
Self::Eip4844(ExecutionBlockWithTransactionsEip4844 {
parent_hash: block.parent_hash,
fee_recipient: block.fee_recipient,
state_root: block.state_root,
receipts_root: block.receipts_root,
logs_bloom: block.logs_bloom,
prev_randao: block.prev_randao,
block_number: block.block_number,
gas_limit: block.gas_limit,
gas_used: block.gas_used,
timestamp: block.timestamp,
extra_data: block.extra_data,
base_fee_per_gas: block.base_fee_per_gas,
excess_blobs: block.excess_blobs,
block_hash: block.block_hash,
transactions: block
.transactions
.iter()
.map(|tx| Transaction::decode(&Rlp::new(tx)))
.collect::<Result<Vec<_>, _>>()
.unwrap_or_else(|_| Vec::new()),
withdrawals: block.withdrawals.into(),
})
}
}
}
}
/*
impl<T: EthSpec> From<ExecutionBlockWithTransactions<T>> for ExecutionPayload<T> {
fn from(block: ExecutionBlockWithTransactions<T>) -> Self {
map_execution_block_with_transactions!(block, |inner, cons| {
let block = inner.into();
cons(block)
})
}
}
*/
#[superstruct(
variants(V1, V2),
variant_attributes(derive(Clone, Debug, PartialEq),),
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
)]
#[derive(Clone, Debug, PartialEq)]
pub struct PayloadAttributes {
#[superstruct(getter(copy))]
pub timestamp: u64,
#[superstruct(getter(copy))]
pub prev_randao: Hash256,
#[superstruct(getter(copy))]
pub suggested_fee_recipient: Address,
#[superstruct(only(V2))]
pub withdrawals: Vec<Withdrawal>,
}
#[derive(Clone, Debug, PartialEq)]

View File

@@ -7,6 +7,7 @@ use reqwest::header::CONTENT_TYPE;
use sensitive_url::SensitiveUrl;
use serde::de::DeserializeOwned;
use serde_json::json;
use std::time::Duration;
use types::EthSpec;
@@ -46,7 +47,7 @@ pub const ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1_TIMEOUT: Duration = Durati
/// This error is returned during a `chainId` call by Geth.
pub const EIP155_ERROR_STR: &str = "chain not synced beyond EIP-155 replay-protection fork block";
/// Contains methods to convert arbitary bytes to an ETH2 deposit contract object.
/// Contains methods to convert arbitrary bytes to an ETH2 deposit contract object.
pub mod deposit_log {
use ssz::Decode;
use state_processing::per_block_processing::signature_sets::deposit_pubkey_signature_message;
@@ -644,7 +645,7 @@ impl HttpJsonRpc {
&self,
execution_payload: ExecutionPayload<T>,
) -> Result<PayloadStatusV1, Error> {
let params = json!([JsonExecutionPayloadV1::from(execution_payload)]);
let params = json!([JsonExecutionPayload::from(execution_payload)]);
let response: JsonPayloadStatusV1 = self
.rpc_request(ENGINE_NEW_PAYLOAD_V1, params, ENGINE_NEW_PAYLOAD_TIMEOUT)
@@ -659,7 +660,7 @@ impl HttpJsonRpc {
) -> Result<ExecutionPayload<T>, Error> {
let params = json!([JsonPayloadIdRequest::from(payload_id)]);
let response: JsonExecutionPayloadV1<T> = self
let response: JsonExecutionPayload<T> = self
.rpc_request(ENGINE_GET_PAYLOAD_V1, params, ENGINE_GET_PAYLOAD_TIMEOUT)
.await?;
@@ -669,10 +670,10 @@ impl HttpJsonRpc {
pub async fn get_blobs_bundle_v1<T: EthSpec>(
&self,
payload_id: PayloadId,
) -> Result<JsonBlobBundlesV1<T>, Error> {
) -> Result<JsonBlobBundles<T>, Error> {
let params = json!([JsonPayloadIdRequest::from(payload_id)]);
let response: JsonBlobBundlesV1<T> = self
let response: JsonBlobBundles<T> = self
.rpc_request(
ENGINE_GET_BLOBS_BUNDLE_V1,
params,
@@ -690,7 +691,7 @@ impl HttpJsonRpc {
) -> Result<ForkchoiceUpdatedResponse, Error> {
let params = json!([
JsonForkChoiceStateV1::from(forkchoice_state),
payload_attributes.map(JsonPayloadAttributesV1::from)
payload_attributes.map(JsonPayloadAttributes::from)
]);
let response: JsonForkchoiceUpdatedV1Response = self
@@ -730,7 +731,10 @@ mod test {
use std::future::Future;
use std::str::FromStr;
use std::sync::Arc;
use types::{MainnetEthSpec, Transactions, Unsigned, VariableList};
use types::{
AbstractExecPayload, ExecutionPayloadMerge, ForkName, FullPayload, MainnetEthSpec,
Transactions, Unsigned, VariableList,
};
struct Tester {
server: MockServer<MainnetEthSpec>,
@@ -836,10 +840,10 @@ mod test {
fn encode_transactions<E: EthSpec>(
transactions: Transactions<E>,
) -> Result<serde_json::Value, serde_json::Error> {
let ep: JsonExecutionPayloadV1<E> = JsonExecutionPayloadV1 {
let ep: JsonExecutionPayload<E> = JsonExecutionPayload::V1(JsonExecutionPayloadV1 {
transactions,
..<_>::default()
};
});
let json = serde_json::to_value(&ep)?;
Ok(json.get("transactions").unwrap().clone())
}
@@ -866,8 +870,8 @@ mod test {
json.as_object_mut()
.unwrap()
.insert("transactions".into(), transactions);
let ep: JsonExecutionPayloadV1<E> = serde_json::from_value(json)?;
Ok(ep.transactions)
let ep: JsonExecutionPayload<E> = serde_json::from_value(json)?;
Ok(ep.transactions().clone())
}
fn assert_transactions_serde<E: EthSpec>(
@@ -1018,11 +1022,11 @@ mod test {
safe_block_hash: ExecutionBlockHash::repeat_byte(1),
finalized_block_hash: ExecutionBlockHash::zero(),
},
Some(PayloadAttributes {
Some(PayloadAttributes::V1(PayloadAttributesV1 {
timestamp: 5,
prev_randao: Hash256::zero(),
suggested_fee_recipient: Address::repeat_byte(0),
}),
})),
)
.await;
},
@@ -1053,11 +1057,11 @@ mod test {
safe_block_hash: ExecutionBlockHash::repeat_byte(1),
finalized_block_hash: ExecutionBlockHash::zero(),
},
Some(PayloadAttributes {
Some(PayloadAttributes::V1(PayloadAttributesV1 {
timestamp: 5,
prev_randao: Hash256::zero(),
suggested_fee_recipient: Address::repeat_byte(0),
}),
})),
)
.await
})
@@ -1093,22 +1097,24 @@ mod test {
.assert_request_equals(
|client| async move {
let _ = client
.new_payload_v1::<MainnetEthSpec>(ExecutionPayload {
parent_hash: ExecutionBlockHash::repeat_byte(0),
fee_recipient: Address::repeat_byte(1),
state_root: Hash256::repeat_byte(1),
receipts_root: Hash256::repeat_byte(0),
logs_bloom: vec![1; 256].into(),
prev_randao: Hash256::repeat_byte(1),
block_number: 0,
gas_limit: 1,
gas_used: 2,
timestamp: 42,
extra_data: vec![].into(),
base_fee_per_gas: Uint256::from(1),
block_hash: ExecutionBlockHash::repeat_byte(1),
transactions: vec![].into(),
})
.new_payload_v1::<MainnetEthSpec>(ExecutionPayload::Merge(
ExecutionPayloadMerge {
parent_hash: ExecutionBlockHash::repeat_byte(0),
fee_recipient: Address::repeat_byte(1),
state_root: Hash256::repeat_byte(1),
receipts_root: Hash256::repeat_byte(0),
logs_bloom: vec![1; 256].into(),
prev_randao: Hash256::repeat_byte(1),
block_number: 0,
gas_limit: 1,
gas_used: 2,
timestamp: 42,
extra_data: vec![].into(),
base_fee_per_gas: Uint256::from(1),
block_hash: ExecutionBlockHash::repeat_byte(1),
transactions: vec![].into(),
},
))
.await;
},
json!({
@@ -1138,22 +1144,24 @@ mod test {
Tester::new(false)
.assert_auth_failure(|client| async move {
client
.new_payload_v1::<MainnetEthSpec>(ExecutionPayload {
parent_hash: ExecutionBlockHash::repeat_byte(0),
fee_recipient: Address::repeat_byte(1),
state_root: Hash256::repeat_byte(1),
receipts_root: Hash256::repeat_byte(0),
logs_bloom: vec![1; 256].into(),
prev_randao: Hash256::repeat_byte(1),
block_number: 0,
gas_limit: 1,
gas_used: 2,
timestamp: 42,
extra_data: vec![].into(),
base_fee_per_gas: Uint256::from(1),
block_hash: ExecutionBlockHash::repeat_byte(1),
transactions: vec![].into(),
})
.new_payload_v1::<MainnetEthSpec>(ExecutionPayload::Merge(
ExecutionPayloadMerge {
parent_hash: ExecutionBlockHash::repeat_byte(0),
fee_recipient: Address::repeat_byte(1),
state_root: Hash256::repeat_byte(1),
receipts_root: Hash256::repeat_byte(0),
logs_bloom: vec![1; 256].into(),
prev_randao: Hash256::repeat_byte(1),
block_number: 0,
gas_limit: 1,
gas_used: 2,
timestamp: 42,
extra_data: vec![].into(),
base_fee_per_gas: Uint256::from(1),
block_hash: ExecutionBlockHash::repeat_byte(1),
transactions: vec![].into(),
},
))
.await
})
.await;
@@ -1236,11 +1244,11 @@ mod test {
safe_block_hash: ExecutionBlockHash::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a").unwrap(),
finalized_block_hash: ExecutionBlockHash::zero(),
},
Some(PayloadAttributes {
Some(PayloadAttributes::V1(PayloadAttributesV1 {
timestamp: 5,
prev_randao: Hash256::zero(),
suggested_fee_recipient: Address::from_str("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(),
})
}))
)
.await;
},
@@ -1283,11 +1291,11 @@ mod test {
safe_block_hash: ExecutionBlockHash::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a").unwrap(),
finalized_block_hash: ExecutionBlockHash::zero(),
},
Some(PayloadAttributes {
Some(PayloadAttributes::V1(PayloadAttributesV1 {
timestamp: 5,
prev_randao: Hash256::zero(),
suggested_fee_recipient: Address::from_str("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(),
})
}))
)
.await
.unwrap();
@@ -1346,7 +1354,7 @@ mod test {
.await
.unwrap();
let expected = ExecutionPayload {
let expected = ExecutionPayload::Merge(ExecutionPayloadMerge {
parent_hash: ExecutionBlockHash::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a").unwrap(),
fee_recipient: Address::from_str("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(),
state_root: Hash256::from_str("0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45").unwrap(),
@@ -1361,7 +1369,7 @@ mod test {
base_fee_per_gas: Uint256::from(7),
block_hash: ExecutionBlockHash::from_str("0x6359b8381a370e2f54072a5784ddd78b6ed024991558c511d4452eb4f6ac898c").unwrap(),
transactions: vec![].into(),
};
});
assert_eq!(payload, expected);
},
@@ -1371,7 +1379,7 @@ mod test {
// engine_newPayloadV1 REQUEST validation
|client| async move {
let _ = client
.new_payload_v1::<MainnetEthSpec>(ExecutionPayload {
.new_payload_v1::<MainnetEthSpec>(ExecutionPayload::Merge(ExecutionPayloadMerge{
parent_hash: ExecutionBlockHash::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a").unwrap(),
fee_recipient: Address::from_str("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap(),
state_root: Hash256::from_str("0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45").unwrap(),
@@ -1386,7 +1394,7 @@ mod test {
base_fee_per_gas: Uint256::from(7),
block_hash: ExecutionBlockHash::from_str("0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858").unwrap(),
transactions: vec![].into(),
})
}))
.await;
},
json!({
@@ -1425,7 +1433,7 @@ mod test {
})],
|client| async move {
let response = client
.new_payload_v1::<MainnetEthSpec>(ExecutionPayload::default())
.new_payload_v1::<MainnetEthSpec>(FullPayload::default_at_fork(ForkName::Merge).into())
.await
.unwrap();

View File

@@ -1,6 +1,12 @@
use super::*;
use serde::{Deserialize, Serialize};
use types::{Blob, EthSpec, ExecutionBlockHash, FixedVector, Transaction, Unsigned, VariableList};
use superstruct::superstruct;
use types::{
Blob, EthSpec, ExecutionBlockHash, ExecutionPayloadEip4844, ExecutionPayloadHeaderEip4844,
FixedVector, KzgCommitment, Transaction, Unsigned, VariableList,
};
use types::{ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadMerge};
use types::{ExecutionPayloadHeader, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderMerge};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -55,9 +61,19 @@ pub struct JsonPayloadIdResponse {
pub payload_id: PayloadId,
}
#[derive(Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(bound = "T: EthSpec", rename_all = "camelCase")]
pub struct JsonExecutionPayloadHeaderV1<T: EthSpec> {
// (V1,V2,V3) -> (Merge,Capella,EIP4844)
#[superstruct(
variants(V1, V2, V3),
variant_attributes(
derive(Debug, PartialEq, Default, Serialize, Deserialize,),
serde(bound = "T: EthSpec", rename_all = "camelCase"),
),
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(bound = "T: EthSpec", rename_all = "camelCase", untagged)]
pub struct JsonExecutionPayloadHeader<T: EthSpec> {
pub parent_hash: ExecutionBlockHash,
pub fee_recipient: Address,
pub state_root: Hash256,
@@ -77,52 +93,144 @@ pub struct JsonExecutionPayloadHeaderV1<T: EthSpec> {
pub extra_data: VariableList<u8, T::MaxExtraDataBytes>,
#[serde(with = "eth2_serde_utils::u256_hex_be")]
pub base_fee_per_gas: Uint256,
#[serde(with = "eth2_serde_utils::u64_hex_be")]
#[superstruct(only(V3))]
pub excess_blobs: u64,
pub block_hash: ExecutionBlockHash,
pub transactions_root: Hash256,
#[superstruct(only(V2, V3))]
pub withdrawals_root: Hash256,
}
impl<T: EthSpec> From<JsonExecutionPayloadHeaderV1<T>> for ExecutionPayloadHeader<T> {
fn from(e: JsonExecutionPayloadHeaderV1<T>) -> Self {
// Use this verbose deconstruction pattern to ensure no field is left unused.
let JsonExecutionPayloadHeaderV1 {
parent_hash,
fee_recipient,
state_root,
receipts_root,
logs_bloom,
prev_randao,
block_number,
gas_limit,
gas_used,
timestamp,
extra_data,
base_fee_per_gas,
block_hash,
transactions_root,
} = e;
Self {
parent_hash,
fee_recipient,
state_root,
receipts_root,
logs_bloom,
prev_randao,
block_number,
gas_limit,
gas_used,
timestamp,
extra_data,
base_fee_per_gas,
block_hash,
transactions_root,
impl<T: EthSpec> From<JsonExecutionPayloadHeader<T>> for ExecutionPayloadHeader<T> {
fn from(json_header: JsonExecutionPayloadHeader<T>) -> Self {
match json_header {
JsonExecutionPayloadHeader::V1(v1) => Self::Merge(ExecutionPayloadHeaderMerge {
parent_hash: v1.parent_hash,
fee_recipient: v1.fee_recipient,
state_root: v1.state_root,
receipts_root: v1.receipts_root,
logs_bloom: v1.logs_bloom,
prev_randao: v1.prev_randao,
block_number: v1.block_number,
gas_limit: v1.gas_limit,
gas_used: v1.gas_used,
timestamp: v1.timestamp,
extra_data: v1.extra_data,
base_fee_per_gas: v1.base_fee_per_gas,
block_hash: v1.block_hash,
transactions_root: v1.transactions_root,
}),
JsonExecutionPayloadHeader::V2(v2) => Self::Capella(ExecutionPayloadHeaderCapella {
parent_hash: v2.parent_hash,
fee_recipient: v2.fee_recipient,
state_root: v2.state_root,
receipts_root: v2.receipts_root,
logs_bloom: v2.logs_bloom,
prev_randao: v2.prev_randao,
block_number: v2.block_number,
gas_limit: v2.gas_limit,
gas_used: v2.gas_used,
timestamp: v2.timestamp,
extra_data: v2.extra_data,
base_fee_per_gas: v2.base_fee_per_gas,
block_hash: v2.block_hash,
transactions_root: v2.transactions_root,
withdrawals_root: v2.withdrawals_root,
}),
JsonExecutionPayloadHeader::V3(v3) => Self::Eip4844(ExecutionPayloadHeaderEip4844 {
parent_hash: v3.parent_hash,
fee_recipient: v3.fee_recipient,
state_root: v3.state_root,
receipts_root: v3.receipts_root,
logs_bloom: v3.logs_bloom,
prev_randao: v3.prev_randao,
block_number: v3.block_number,
gas_limit: v3.gas_limit,
gas_used: v3.gas_used,
timestamp: v3.timestamp,
extra_data: v3.extra_data,
base_fee_per_gas: v3.base_fee_per_gas,
excess_blobs: v3.excess_blobs,
block_hash: v3.block_hash,
transactions_root: v3.transactions_root,
withdrawals_root: v3.withdrawals_root,
}),
}
}
}
#[derive(Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(bound = "T: EthSpec", rename_all = "camelCase")]
pub struct JsonExecutionPayloadV1<T: EthSpec> {
impl<T: EthSpec> From<ExecutionPayloadHeader<T>> for JsonExecutionPayloadHeader<T> {
fn from(header: ExecutionPayloadHeader<T>) -> Self {
match header {
ExecutionPayloadHeader::Merge(merge) => Self::V1(JsonExecutionPayloadHeaderV1 {
parent_hash: merge.parent_hash,
fee_recipient: merge.fee_recipient,
state_root: merge.state_root,
receipts_root: merge.receipts_root,
logs_bloom: merge.logs_bloom,
prev_randao: merge.prev_randao,
block_number: merge.block_number,
gas_limit: merge.gas_limit,
gas_used: merge.gas_used,
timestamp: merge.timestamp,
extra_data: merge.extra_data,
base_fee_per_gas: merge.base_fee_per_gas,
block_hash: merge.block_hash,
transactions_root: merge.transactions_root,
}),
ExecutionPayloadHeader::Capella(capella) => Self::V2(JsonExecutionPayloadHeaderV2 {
parent_hash: capella.parent_hash,
fee_recipient: capella.fee_recipient,
state_root: capella.state_root,
receipts_root: capella.receipts_root,
logs_bloom: capella.logs_bloom,
prev_randao: capella.prev_randao,
block_number: capella.block_number,
gas_limit: capella.gas_limit,
gas_used: capella.gas_used,
timestamp: capella.timestamp,
extra_data: capella.extra_data,
base_fee_per_gas: capella.base_fee_per_gas,
block_hash: capella.block_hash,
transactions_root: capella.transactions_root,
withdrawals_root: capella.withdrawals_root,
}),
ExecutionPayloadHeader::Eip4844(eip4844) => Self::V3(JsonExecutionPayloadHeaderV3 {
parent_hash: eip4844.parent_hash,
fee_recipient: eip4844.fee_recipient,
state_root: eip4844.state_root,
receipts_root: eip4844.receipts_root,
logs_bloom: eip4844.logs_bloom,
prev_randao: eip4844.prev_randao,
block_number: eip4844.block_number,
gas_limit: eip4844.gas_limit,
gas_used: eip4844.gas_used,
timestamp: eip4844.timestamp,
extra_data: eip4844.extra_data,
base_fee_per_gas: eip4844.base_fee_per_gas,
excess_blobs: eip4844.excess_blobs,
block_hash: eip4844.block_hash,
transactions_root: eip4844.transactions_root,
withdrawals_root: eip4844.withdrawals_root,
}),
}
}
}
// (V1,V2, V2) -> (Merge,Capella,EIP4844)
#[superstruct(
variants(V1, V2, V3),
variant_attributes(
derive(Debug, PartialEq, Default, Serialize, Deserialize,),
serde(bound = "T: EthSpec", rename_all = "camelCase"),
),
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(bound = "T: EthSpec", rename_all = "camelCase", untagged)]
pub struct JsonExecutionPayload<T: EthSpec> {
pub parent_hash: ExecutionBlockHash,
pub fee_recipient: Address,
pub state_root: Hash256,
@@ -142,136 +250,219 @@ pub struct JsonExecutionPayloadV1<T: EthSpec> {
pub extra_data: VariableList<u8, T::MaxExtraDataBytes>,
#[serde(with = "eth2_serde_utils::u256_hex_be")]
pub base_fee_per_gas: Uint256,
#[superstruct(only(V3))]
#[serde(with = "eth2_serde_utils::u64_hex_be")]
pub excess_blobs: u64,
pub block_hash: ExecutionBlockHash,
#[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")]
pub transactions:
VariableList<Transaction<T::MaxBytesPerTransaction>, T::MaxTransactionsPerPayload>,
#[superstruct(only(V2, V3))]
pub withdrawals: VariableList<Withdrawal, T::MaxWithdrawalsPerPayload>,
}
impl<T: EthSpec> From<ExecutionPayload<T>> for JsonExecutionPayloadV1<T> {
fn from(e: ExecutionPayload<T>) -> Self {
// Use this verbose deconstruction pattern to ensure no field is left unused.
let ExecutionPayload {
parent_hash,
fee_recipient,
state_root,
receipts_root,
logs_bloom,
prev_randao,
block_number,
gas_limit,
gas_used,
timestamp,
extra_data,
base_fee_per_gas,
block_hash,
transactions,
} = e;
Self {
parent_hash,
fee_recipient,
state_root,
receipts_root,
logs_bloom,
prev_randao,
block_number,
gas_limit,
gas_used,
timestamp,
extra_data,
base_fee_per_gas,
block_hash,
transactions,
impl<T: EthSpec> From<JsonExecutionPayload<T>> for ExecutionPayload<T> {
fn from(json_payload: JsonExecutionPayload<T>) -> Self {
match json_payload {
JsonExecutionPayload::V1(v1) => Self::Merge(ExecutionPayloadMerge {
parent_hash: v1.parent_hash,
fee_recipient: v1.fee_recipient,
state_root: v1.state_root,
receipts_root: v1.receipts_root,
logs_bloom: v1.logs_bloom,
prev_randao: v1.prev_randao,
block_number: v1.block_number,
gas_limit: v1.gas_limit,
gas_used: v1.gas_used,
timestamp: v1.timestamp,
extra_data: v1.extra_data,
base_fee_per_gas: v1.base_fee_per_gas,
block_hash: v1.block_hash,
transactions: v1.transactions,
}),
JsonExecutionPayload::V2(v2) => Self::Capella(ExecutionPayloadCapella {
parent_hash: v2.parent_hash,
fee_recipient: v2.fee_recipient,
state_root: v2.state_root,
receipts_root: v2.receipts_root,
logs_bloom: v2.logs_bloom,
prev_randao: v2.prev_randao,
block_number: v2.block_number,
gas_limit: v2.gas_limit,
gas_used: v2.gas_used,
timestamp: v2.timestamp,
extra_data: v2.extra_data,
base_fee_per_gas: v2.base_fee_per_gas,
block_hash: v2.block_hash,
transactions: v2.transactions,
withdrawals: v2.withdrawals,
}),
JsonExecutionPayload::V3(v3) => Self::Eip4844(ExecutionPayloadEip4844 {
parent_hash: v3.parent_hash,
fee_recipient: v3.fee_recipient,
state_root: v3.state_root,
receipts_root: v3.receipts_root,
logs_bloom: v3.logs_bloom,
prev_randao: v3.prev_randao,
block_number: v3.block_number,
gas_limit: v3.gas_limit,
gas_used: v3.gas_used,
timestamp: v3.timestamp,
extra_data: v3.extra_data,
base_fee_per_gas: v3.base_fee_per_gas,
excess_blobs: v3.excess_blobs,
block_hash: v3.block_hash,
transactions: v3.transactions,
withdrawals: v3.withdrawals,
}),
}
}
}
impl<T: EthSpec> From<JsonExecutionPayloadV1<T>> for ExecutionPayload<T> {
fn from(e: JsonExecutionPayloadV1<T>) -> Self {
// Use this verbose deconstruction pattern to ensure no field is left unused.
let JsonExecutionPayloadV1 {
parent_hash,
fee_recipient,
state_root,
receipts_root,
logs_bloom,
prev_randao,
block_number,
gas_limit,
gas_used,
timestamp,
extra_data,
base_fee_per_gas,
block_hash,
transactions,
} = e;
Self {
parent_hash,
fee_recipient,
state_root,
receipts_root,
logs_bloom,
prev_randao,
block_number,
gas_limit,
gas_used,
timestamp,
extra_data,
base_fee_per_gas,
block_hash,
transactions,
impl<T: EthSpec> From<ExecutionPayload<T>> for JsonExecutionPayload<T> {
fn from(payload: ExecutionPayload<T>) -> Self {
match payload {
ExecutionPayload::Merge(merge) => Self::V1(JsonExecutionPayloadV1 {
parent_hash: merge.parent_hash,
fee_recipient: merge.fee_recipient,
state_root: merge.state_root,
receipts_root: merge.receipts_root,
logs_bloom: merge.logs_bloom,
prev_randao: merge.prev_randao,
block_number: merge.block_number,
gas_limit: merge.gas_limit,
gas_used: merge.gas_used,
timestamp: merge.timestamp,
extra_data: merge.extra_data,
base_fee_per_gas: merge.base_fee_per_gas,
block_hash: merge.block_hash,
transactions: merge.transactions,
}),
ExecutionPayload::Capella(capella) => Self::V2(JsonExecutionPayloadV2 {
parent_hash: capella.parent_hash,
fee_recipient: capella.fee_recipient,
state_root: capella.state_root,
receipts_root: capella.receipts_root,
logs_bloom: capella.logs_bloom,
prev_randao: capella.prev_randao,
block_number: capella.block_number,
gas_limit: capella.gas_limit,
gas_used: capella.gas_used,
timestamp: capella.timestamp,
extra_data: capella.extra_data,
base_fee_per_gas: capella.base_fee_per_gas,
block_hash: capella.block_hash,
transactions: capella.transactions,
withdrawals: capella.withdrawals,
}),
ExecutionPayload::Eip4844(eip4844) => Self::V3(JsonExecutionPayloadV3 {
parent_hash: eip4844.parent_hash,
fee_recipient: eip4844.fee_recipient,
state_root: eip4844.state_root,
receipts_root: eip4844.receipts_root,
logs_bloom: eip4844.logs_bloom,
prev_randao: eip4844.prev_randao,
block_number: eip4844.block_number,
gas_limit: eip4844.gas_limit,
gas_used: eip4844.gas_used,
timestamp: eip4844.timestamp,
extra_data: eip4844.extra_data,
base_fee_per_gas: eip4844.base_fee_per_gas,
excess_blobs: eip4844.excess_blobs,
block_hash: eip4844.block_hash,
transactions: eip4844.transactions,
withdrawals: eip4844.withdrawals,
}),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct JsonWithdrawal {
#[serde(with = "eth2_serde_utils::u64_hex_be")]
pub index: u64,
pub address: Address,
#[serde(with = "eth2_serde_utils::u256_hex_be")]
pub amount: Uint256,
}
impl From<Withdrawal> for JsonWithdrawal {
fn from(withdrawal: Withdrawal) -> Self {
Self {
index: withdrawal.index,
address: withdrawal.address,
amount: Uint256::from((withdrawal.amount as u128) * 1000000000u128),
}
}
}
impl From<JsonWithdrawal> for Withdrawal {
fn from(jw: JsonWithdrawal) -> Self {
Self {
index: jw.index,
address: jw.address,
//FIXME(sean) if EE gives us too large a number this panics
amount: (jw.amount / 1000000000).as_u64(),
}
}
}
#[superstruct(
variants(V1, V2),
variant_attributes(derive(Clone, Debug, PartialEq, Serialize, Deserialize),),
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonPayloadAttributesV1 {
#[serde(rename_all = "camelCase", untagged)]
pub struct JsonPayloadAttributes {
#[serde(with = "eth2_serde_utils::u64_hex_be")]
pub timestamp: u64,
pub prev_randao: Hash256,
pub suggested_fee_recipient: Address,
#[superstruct(only(V2))]
pub withdrawals: Vec<JsonWithdrawal>,
}
impl From<PayloadAttributes> for JsonPayloadAttributesV1 {
fn from(p: PayloadAttributes) -> Self {
// Use this verbose deconstruction pattern to ensure no field is left unused.
let PayloadAttributes {
timestamp,
prev_randao,
suggested_fee_recipient,
} = p;
Self {
timestamp,
prev_randao,
suggested_fee_recipient,
impl From<PayloadAttributes> for JsonPayloadAttributes {
fn from(payload_atributes: PayloadAttributes) -> Self {
match payload_atributes {
PayloadAttributes::V1(pa) => Self::V1(JsonPayloadAttributesV1 {
timestamp: pa.timestamp,
prev_randao: pa.prev_randao,
suggested_fee_recipient: pa.suggested_fee_recipient,
}),
PayloadAttributes::V2(pa) => Self::V2(JsonPayloadAttributesV2 {
timestamp: pa.timestamp,
prev_randao: pa.prev_randao,
suggested_fee_recipient: pa.suggested_fee_recipient,
withdrawals: pa.withdrawals.into_iter().map(Into::into).collect(),
}),
}
}
}
impl From<JsonPayloadAttributesV1> for PayloadAttributes {
fn from(j: JsonPayloadAttributesV1) -> Self {
// Use this verbose deconstruction pattern to ensure no field is left unused.
let JsonPayloadAttributesV1 {
timestamp,
prev_randao,
suggested_fee_recipient,
} = j;
Self {
timestamp,
prev_randao,
suggested_fee_recipient,
impl From<JsonPayloadAttributes> for PayloadAttributes {
fn from(json_payload_attributes: JsonPayloadAttributes) -> Self {
match json_payload_attributes {
JsonPayloadAttributes::V1(jpa) => Self::V1(PayloadAttributesV1 {
timestamp: jpa.timestamp,
prev_randao: jpa.prev_randao,
suggested_fee_recipient: jpa.suggested_fee_recipient,
}),
JsonPayloadAttributes::V2(jpa) => Self::V2(PayloadAttributesV2 {
timestamp: jpa.timestamp,
prev_randao: jpa.prev_randao,
suggested_fee_recipient: jpa.suggested_fee_recipient,
withdrawals: jpa.withdrawals.into_iter().map(Into::into).collect(),
}),
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(bound = "T: EthSpec", rename_all = "camelCase")]
pub struct JsonBlobBundlesV1<T: EthSpec> {
pub struct JsonBlobBundles<T: EthSpec> {
pub block_hash: ExecutionBlockHash,
pub kzgs: Vec<KzgCommitment>,
pub blobs: Vec<Blob<T>>,

View File

@@ -167,7 +167,7 @@ impl Engine {
) -> Result<ForkchoiceUpdatedResponse, EngineApiError> {
let response = self
.api
.forkchoice_updated_v1(forkchoice_state, payload_attributes)
.forkchoice_updated_v1(forkchoice_state, payload_attributes.clone())
.await?;
if let Some(payload_id) = response.payload_id {
@@ -347,13 +347,14 @@ impl Engine {
}
}
// TODO: revisit this - do we need to key on withdrawals as well here?
impl PayloadIdCacheKey {
fn new(state: &ForkChoiceState, attributes: &PayloadAttributes) -> Self {
Self {
head_block_hash: state.head_block_hash,
timestamp: attributes.timestamp,
prev_randao: attributes.prev_randao,
suggested_fee_recipient: attributes.suggested_fee_recipient,
timestamp: attributes.timestamp(),
prev_randao: attributes.prev_randao(),
suggested_fee_recipient: attributes.suggested_fee_recipient(),
}
}
}

View File

@@ -4,7 +4,7 @@
//! This crate only provides useful functionality for "The Merge", it does not provide any of the
//! deposit-contract functionality that the `beacon_node/eth1` crate already provides.
use crate::json_structures::JsonBlobBundlesV1;
use crate::json_structures::JsonBlobBundles;
use crate::payload_cache::PayloadCache;
use auth::{strip_prefix, Auth, JwtKey};
use builder_client::BuilderHttpClient;
@@ -33,10 +33,12 @@ use tokio::{
time::sleep,
};
use tokio_stream::wrappers::WatchStream;
use types::{AbstractExecPayload, Blob, ExecPayload, ExecutionPayloadEip4844, KzgCommitment};
use types::{
BlindedPayload, BlockType, ChainSpec, Epoch, ExecPayload, ExecutionBlockHash, ForkName,
BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionBlockHash, ForkName,
ProposerPreparationData, PublicKeyBytes, SignedBeaconBlock, Slot,
};
use types::{ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadMerge};
mod engine_api;
mod engines;
@@ -88,6 +90,70 @@ impl From<ApiError> for Error {
}
}
pub enum BlockProposalContents<T: EthSpec, Payload: AbstractExecPayload<T>> {
Payload(Payload),
PayloadAndBlobs {
payload: Payload,
kzg_commitments: Vec<KzgCommitment>,
blobs: Vec<Blob<T>>,
},
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Payload> {
pub fn payload(&self) -> &Payload {
match self {
Self::Payload(payload) => payload,
Self::PayloadAndBlobs {
payload,
kzg_commitments: _,
blobs: _,
} => payload,
}
}
pub fn to_payload(self) -> Payload {
match self {
Self::Payload(payload) => payload,
Self::PayloadAndBlobs {
payload,
kzg_commitments: _,
blobs: _,
} => payload,
}
}
pub fn kzg_commitments(&self) -> Option<&[KzgCommitment]> {
match self {
Self::Payload(_) => None,
Self::PayloadAndBlobs {
payload: _,
kzg_commitments,
blobs: _,
} => Some(kzg_commitments),
}
}
pub fn blobs(&self) -> Option<&[Blob<T>]> {
match self {
Self::Payload(_) => None,
Self::PayloadAndBlobs {
payload: _,
kzg_commitments: _,
blobs,
} => Some(blobs),
}
}
pub fn default_at_fork(fork_name: ForkName) -> Self {
match fork_name {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
BlockProposalContents::Payload(Payload::default_at_fork(fork_name))
}
ForkName::Eip4844 => BlockProposalContents::PayloadAndBlobs {
payload: Payload::default_at_fork(fork_name),
blobs: vec![],
kzg_commitments: vec![],
},
}
}
}
#[derive(Clone, PartialEq)]
pub struct ProposerPreparationDataEntry {
update_epoch: Epoch,
@@ -536,7 +602,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
/// 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<Payload: ExecPayload<T>>(
pub async fn get_payload<Payload: AbstractExecPayload<T>>(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
@@ -545,7 +611,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
forkchoice_update_params: ForkchoiceUpdateParameters,
builder_params: BuilderParams,
spec: &ChainSpec,
) -> Result<Payload, Error> {
) -> Result<BlockProposalContents<T, Payload>, Error> {
let suggested_fee_recipient = self.get_suggested_fee_recipient(proposer_index).await;
match Payload::block_type() {
@@ -583,7 +649,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
}
#[allow(clippy::too_many_arguments)]
async fn get_blinded_payload<Payload: ExecPayload<T>>(
async fn get_blinded_payload<Payload: AbstractExecPayload<T>>(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
@@ -592,7 +658,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
forkchoice_update_params: ForkchoiceUpdateParameters,
builder_params: BuilderParams,
spec: &ChainSpec,
) -> Result<Payload, Error> {
) -> Result<BlockProposalContents<T, Payload>, Error> {
if let Some(builder) = self.builder() {
let slot = builder_params.slot;
let pubkey = builder_params.pubkey;
@@ -635,6 +701,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
Ok(local)
}
(Ok(Some(relay)), Ok(local)) => {
let local_payload = local.payload();
let is_signature_valid = relay.data.verify_signature(spec);
let header = relay.data.message.header;
@@ -668,14 +735,14 @@ impl<T: EthSpec> ExecutionLayer<T> {
falling back to local execution engine."
);
Ok(local)
} else if header.timestamp() != local.timestamp() {
} else if header.timestamp() != local_payload.timestamp() {
warn!(
self.log(),
"Invalid timestamp from connected builder, \
falling back to local execution engine."
);
Ok(local)
} else if header.block_number() != local.block_number() {
} else if header.block_number() != local_payload.block_number() {
warn!(
self.log(),
"Invalid block number from connected builder, \
@@ -706,7 +773,8 @@ impl<T: EthSpec> ExecutionLayer<T> {
not match, using it anyways."
);
}
Ok(header)
//FIXME(sean) the builder API needs to be updated
Ok(BlockProposalContents::Payload(header))
}
}
(relay_result, Err(local_error)) => {
@@ -715,7 +783,10 @@ impl<T: EthSpec> ExecutionLayer<T> {
relay_result
.map_err(Error::Builder)?
.ok_or(Error::NoHeaderFromBuilder)
.map(|d| d.data.message.header)
.map(|d| {
//FIXME(sean) the builder API needs to be updated
BlockProposalContents::Payload(d.data.message.header)
})
}
};
}
@@ -743,14 +814,14 @@ impl<T: EthSpec> ExecutionLayer<T> {
}
/// Get a full payload without caching its result in the execution layer's payload cache.
async fn get_full_payload<Payload: ExecPayload<T>>(
async fn get_full_payload<Payload: AbstractExecPayload<T>>(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
prev_randao: Hash256,
suggested_fee_recipient: Address,
forkchoice_update_params: ForkchoiceUpdateParameters,
) -> Result<Payload, Error> {
) -> Result<BlockProposalContents<T, Payload>, Error> {
self.get_full_payload_with(
parent_hash,
timestamp,
@@ -763,14 +834,14 @@ impl<T: EthSpec> ExecutionLayer<T> {
}
/// Get a full payload and cache its result in the execution layer's payload cache.
async fn get_full_payload_caching<Payload: ExecPayload<T>>(
async fn get_full_payload_caching<Payload: AbstractExecPayload<T>>(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
prev_randao: Hash256,
suggested_fee_recipient: Address,
forkchoice_update_params: ForkchoiceUpdateParameters,
) -> Result<Payload, Error> {
) -> Result<BlockProposalContents<T, Payload>, Error> {
self.get_full_payload_with(
parent_hash,
timestamp,
@@ -782,51 +853,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
.await
}
pub async fn get_blob_bundles(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
prev_randao: Hash256,
proposer_index: u64,
) -> Result<JsonBlobBundlesV1<T>, Error> {
let suggested_fee_recipient = self.get_suggested_fee_recipient(proposer_index).await;
debug!(
self.log(),
"Issuing engine_getBlobsBundle";
"suggested_fee_recipient" => ?suggested_fee_recipient,
"prev_randao" => ?prev_randao,
"timestamp" => timestamp,
"parent_hash" => ?parent_hash,
);
self.engine()
.request(|engine| async move {
let payload_id = if let Some(id) = engine
.get_payload_id(parent_hash, timestamp, prev_randao, suggested_fee_recipient)
.await
{
// The payload id has been cached for this engine.
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_PRE_PREPARED_PAYLOAD_ID,
&[metrics::HIT],
);
id
} else {
error!(
self.log(),
"Exec engine unable to produce blobs, did you call get_payload before?",
);
return Err(ApiError::PayloadIdUnavailable);
};
engine.api.get_blobs_bundle_v1::<T>(payload_id).await
})
.await
.map_err(Box::new)
.map_err(Error::EngineError)
}
async fn get_full_payload_with<Payload: ExecPayload<T>>(
async fn get_full_payload_with<Payload: AbstractExecPayload<T>>(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
@@ -834,15 +861,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
suggested_fee_recipient: Address,
forkchoice_update_params: ForkchoiceUpdateParameters,
f: fn(&ExecutionLayer<T>, &ExecutionPayload<T>) -> Option<ExecutionPayload<T>>,
) -> Result<Payload, Error> {
debug!(
self.log(),
"Issuing engine_getPayload";
"suggested_fee_recipient" => ?suggested_fee_recipient,
"prev_randao" => ?prev_randao,
"timestamp" => timestamp,
"parent_hash" => ?parent_hash,
);
) -> Result<BlockProposalContents<T, Payload>, Error> {
self.engine()
.request(|engine| async move {
let payload_id = if let Some(id) = engine
@@ -871,11 +890,13 @@ impl<T: EthSpec> ExecutionLayer<T> {
.finalized_hash
.unwrap_or_else(ExecutionBlockHash::zero),
};
let payload_attributes = PayloadAttributes {
// FIXME: This will have to properly handle forks. To do that,
// withdrawals will need to be passed into this function
let payload_attributes = PayloadAttributes::V1(PayloadAttributesV1 {
timestamp,
prev_randao,
suggested_fee_recipient,
};
});
let response = engine
.notify_forkchoice_updated(
@@ -900,33 +921,64 @@ impl<T: EthSpec> ExecutionLayer<T> {
}
};
engine
.api
.get_payload_v1::<T>(payload_id)
.await
.map(|full_payload| {
if full_payload.fee_recipient != suggested_fee_recipient {
error!(
self.log(),
"Inconsistent fee recipient";
"msg" => "The fee recipient returned from the Execution Engine differs \
from the suggested_fee_recipient set on the beacon node. This could \
indicate that fees are being diverted to another address. Please \
ensure that the value of suggested_fee_recipient is set correctly and \
that the Execution Engine is trusted.",
"fee_recipient" => ?full_payload.fee_recipient,
"suggested_fee_recipient" => ?suggested_fee_recipient,
);
}
if f(self, &full_payload).is_some() {
warn!(
self.log(),
"Duplicate payload cached, this might indicate redundant proposal \
let blob_fut = async {
//FIXME(sean) do a fork check here and return None otherwise
debug!(
self.log(),
"Issuing engine_getBlobsBundle";
"suggested_fee_recipient" => ?suggested_fee_recipient,
"prev_randao" => ?prev_randao,
"timestamp" => timestamp,
"parent_hash" => ?parent_hash,
);
Some(engine.api.get_blobs_bundle_v1::<T>(payload_id).await)
};
let payload_fut = async {
debug!(
self.log(),
"Issuing engine_getPayload";
"suggested_fee_recipient" => ?suggested_fee_recipient,
"prev_randao" => ?prev_randao,
"timestamp" => timestamp,
"parent_hash" => ?parent_hash,
);
engine.api.get_payload_v1::<T>(payload_id).await
};
let (blob, payload) = tokio::join!(blob_fut, payload_fut);
let payload = payload.map(|full_payload| {
if full_payload.fee_recipient() != suggested_fee_recipient {
error!(
self.log(),
"Inconsistent fee recipient";
"msg" => "The fee recipient returned from the Execution Engine differs \
from the suggested_fee_recipient set on the beacon node. This could \
indicate that fees are being diverted to another address. Please \
ensure that the value of suggested_fee_recipient is set correctly and \
that the Execution Engine is trusted.",
"fee_recipient" => ?full_payload.fee_recipient(),
"suggested_fee_recipient" => ?suggested_fee_recipient,
);
}
if f(self, &full_payload).is_some() {
warn!(
self.log(),
"Duplicate payload cached, this might indicate redundant proposal \
attempts."
);
}
full_payload.into()
);
}
full_payload.into()
})?;
if let Some(blob) = blob.transpose()? {
// FIXME(sean) cache blobs
Ok(BlockProposalContents::PayloadAndBlobs {
payload,
blobs: blob.blobs,
kzg_commitments: blob.kzgs,
})
} else {
Ok(BlockProposalContents::Payload(payload))
}
})
.await
.map_err(Box::new)
@@ -958,9 +1010,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
trace!(
self.log(),
"Issuing engine_newPayload";
"parent_hash" => ?execution_payload.parent_hash,
"block_hash" => ?execution_payload.block_hash,
"block_number" => execution_payload.block_number,
"parent_hash" => ?execution_payload.parent_hash(),
"block_hash" => ?execution_payload.block_hash(),
"block_number" => execution_payload.block_number(),
);
let result = self
@@ -975,7 +1027,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
);
}
process_payload_status(execution_payload.block_hash, result, self.log())
process_payload_status(execution_payload.block_hash(), result, self.log())
.map_err(Box::new)
.map_err(Error::EngineError)
}
@@ -1076,9 +1128,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
let payload_attributes = self.payload_attributes(next_slot, head_block_root).await;
// Compute the "lookahead", the time between when the payload will be produced and now.
if let Some(payload_attributes) = payload_attributes {
if let Some(ref payload_attributes) = payload_attributes {
if let Ok(now) = SystemTime::now().duration_since(UNIX_EPOCH) {
let timestamp = Duration::from_secs(payload_attributes.timestamp);
let timestamp = Duration::from_secs(payload_attributes.timestamp());
if let Some(lookahead) = timestamp.checked_sub(now) {
metrics::observe_duration(
&metrics::EXECUTION_LAYER_PAYLOAD_ATTRIBUTES_LOOKAHEAD,
@@ -1105,11 +1157,16 @@ impl<T: EthSpec> ExecutionLayer<T> {
.set_latest_forkchoice_state(forkchoice_state)
.await;
let payload_attributes_ref = &payload_attributes;
let result = self
.engine()
.request(|engine| async move {
engine
.notify_forkchoice_updated(forkchoice_state, payload_attributes, self.log())
.notify_forkchoice_updated(
forkchoice_state,
payload_attributes_ref.clone(),
self.log(),
)
.await
})
.await;
@@ -1399,7 +1456,8 @@ impl<T: EthSpec> ExecutionLayer<T> {
let _timer = metrics::start_timer(&metrics::EXECUTION_LAYER_GET_PAYLOAD_BY_BLOCK_HASH);
if hash == ExecutionBlockHash::zero() {
return Ok(Some(ExecutionPayload::default()));
// FIXME: how to handle forks properly here?
return Ok(Some(ExecutionPayloadMerge::default().into()));
}
let block = if let Some(block) = engine.api.get_block_by_hash_with_txns::<T>(hash).await? {
@@ -1410,7 +1468,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
let transactions = VariableList::new(
block
.transactions
.transactions()
.into_iter()
.map(|transaction| VariableList::new(transaction.rlp().to_vec()))
.collect::<Result<_, _>>()
@@ -1418,22 +1476,73 @@ impl<T: EthSpec> ExecutionLayer<T> {
)
.map_err(ApiError::DeserializeTransactions)?;
Ok(Some(ExecutionPayload {
parent_hash: block.parent_hash,
fee_recipient: block.fee_recipient,
state_root: block.state_root,
receipts_root: block.receipts_root,
logs_bloom: block.logs_bloom,
prev_randao: block.prev_randao,
block_number: block.block_number,
gas_limit: block.gas_limit,
gas_used: block.gas_used,
timestamp: block.timestamp,
extra_data: block.extra_data,
base_fee_per_gas: block.base_fee_per_gas,
block_hash: block.block_hash,
transactions,
}))
let payload = match block {
ExecutionBlockWithTransactions::Merge(merge_block) => {
ExecutionPayload::Merge(ExecutionPayloadMerge {
parent_hash: merge_block.parent_hash,
fee_recipient: merge_block.fee_recipient,
state_root: merge_block.state_root,
receipts_root: merge_block.receipts_root,
logs_bloom: merge_block.logs_bloom,
prev_randao: merge_block.prev_randao,
block_number: merge_block.block_number,
gas_limit: merge_block.gas_limit,
gas_used: merge_block.gas_used,
timestamp: merge_block.timestamp,
extra_data: merge_block.extra_data,
base_fee_per_gas: merge_block.base_fee_per_gas,
block_hash: merge_block.block_hash,
transactions,
})
}
ExecutionBlockWithTransactions::Capella(capella_block) => {
let withdrawals = VariableList::new(capella_block.withdrawals.clone())
.map_err(ApiError::DeserializeWithdrawals)?;
ExecutionPayload::Capella(ExecutionPayloadCapella {
parent_hash: capella_block.parent_hash,
fee_recipient: capella_block.fee_recipient,
state_root: capella_block.state_root,
receipts_root: capella_block.receipts_root,
logs_bloom: capella_block.logs_bloom,
prev_randao: capella_block.prev_randao,
block_number: capella_block.block_number,
gas_limit: capella_block.gas_limit,
gas_used: capella_block.gas_used,
timestamp: capella_block.timestamp,
extra_data: capella_block.extra_data,
base_fee_per_gas: capella_block.base_fee_per_gas,
block_hash: capella_block.block_hash,
transactions,
withdrawals,
})
}
ExecutionBlockWithTransactions::Eip4844(eip4844_block) => {
let withdrawals = VariableList::new(eip4844_block.withdrawals.clone())
.map_err(ApiError::DeserializeWithdrawals)?;
ExecutionPayload::Eip4844(ExecutionPayloadEip4844 {
parent_hash: eip4844_block.parent_hash,
fee_recipient: eip4844_block.fee_recipient,
state_root: eip4844_block.state_root,
receipts_root: eip4844_block.receipts_root,
logs_bloom: eip4844_block.logs_bloom,
prev_randao: eip4844_block.prev_randao,
block_number: eip4844_block.block_number,
gas_limit: eip4844_block.gas_limit,
gas_used: eip4844_block.gas_used,
timestamp: eip4844_block.timestamp,
extra_data: eip4844_block.extra_data,
base_fee_per_gas: eip4844_block.base_fee_per_gas,
excess_blobs: eip4844_block.excess_blobs,
block_hash: eip4844_block.block_hash,
transactions,
withdrawals,
})
}
};
Ok(Some(payload))
}
pub async fn propose_blinded_beacon_block(

View File

@@ -12,7 +12,10 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
use types::{EthSpec, ExecutionBlockHash, ExecutionPayload, Hash256, Uint256};
use types::{
EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadMerge,
Hash256, Uint256,
};
const GAS_LIMIT: u64 = 16384;
const GAS_USED: u64 = GAS_LIMIT - 1;
@@ -28,21 +31,21 @@ impl<T: EthSpec> Block<T> {
pub fn block_number(&self) -> u64 {
match self {
Block::PoW(block) => block.block_number,
Block::PoS(payload) => payload.block_number,
Block::PoS(payload) => payload.block_number(),
}
}
pub fn parent_hash(&self) -> ExecutionBlockHash {
match self {
Block::PoW(block) => block.parent_hash,
Block::PoS(payload) => payload.parent_hash,
Block::PoS(payload) => payload.parent_hash(),
}
}
pub fn block_hash(&self) -> ExecutionBlockHash {
match self {
Block::PoW(block) => block.block_hash,
Block::PoS(payload) => payload.block_hash,
Block::PoS(payload) => payload.block_hash(),
}
}
@@ -63,33 +66,18 @@ impl<T: EthSpec> Block<T> {
timestamp: block.timestamp,
},
Block::PoS(payload) => ExecutionBlock {
block_hash: payload.block_hash,
block_number: payload.block_number,
parent_hash: payload.parent_hash,
block_hash: payload.block_hash(),
block_number: payload.block_number(),
parent_hash: payload.parent_hash(),
total_difficulty,
timestamp: payload.timestamp,
timestamp: payload.timestamp(),
},
}
}
pub fn as_execution_block_with_tx(&self) -> Option<ExecutionBlockWithTransactions<T>> {
match self {
Block::PoS(payload) => Some(ExecutionBlockWithTransactions {
parent_hash: payload.parent_hash,
fee_recipient: payload.fee_recipient,
state_root: payload.state_root,
receipts_root: payload.receipts_root,
logs_bloom: payload.logs_bloom.clone(),
prev_randao: payload.prev_randao,
block_number: payload.block_number,
gas_limit: payload.gas_limit,
gas_used: payload.gas_used,
timestamp: payload.timestamp,
extra_data: payload.extra_data.clone(),
base_fee_per_gas: payload.base_fee_per_gas,
block_hash: payload.block_hash,
transactions: vec![],
}),
Block::PoS(payload) => Some(payload.clone().into()),
Block::PoW(_) => None,
}
}
@@ -283,7 +271,9 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
// Update the block hash after modifying the block
match &mut block {
Block::PoW(b) => b.block_hash = ExecutionBlockHash::from_root(b.tree_hash_root()),
Block::PoS(b) => b.block_hash = ExecutionBlockHash::from_root(b.tree_hash_root()),
Block::PoS(b) => {
*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);
@@ -295,7 +285,7 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
}
pub fn new_payload(&mut self, payload: ExecutionPayload<T>) -> PayloadStatusV1 {
let parent = if let Some(parent) = self.blocks.get(&payload.parent_hash) {
let parent = if let Some(parent) = self.blocks.get(&payload.parent_hash()) {
parent
} else {
return PayloadStatusV1 {
@@ -305,7 +295,7 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
};
};
if payload.block_number != parent.block_number() + 1 {
if payload.block_number() != parent.block_number() + 1 {
return PayloadStatusV1 {
status: PayloadStatusV1Status::Invalid,
latest_valid_hash: Some(parent.block_hash()),
@@ -313,8 +303,8 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
};
}
let valid_hash = payload.block_hash;
self.pending_payloads.insert(payload.block_hash, payload);
let valid_hash = payload.block_hash();
self.pending_payloads.insert(payload.block_hash(), payload);
PayloadStatusV1 {
status: PayloadStatusV1Status::Valid,
@@ -379,24 +369,52 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
let id = payload_id_from_u64(self.next_payload_id);
self.next_payload_id += 1;
let mut execution_payload = ExecutionPayload {
parent_hash: forkchoice_state.head_block_hash,
fee_recipient: attributes.suggested_fee_recipient,
receipts_root: Hash256::repeat_byte(42),
state_root: Hash256::repeat_byte(43),
logs_bloom: vec![0; 256].into(),
prev_randao: attributes.prev_randao,
block_number: parent.block_number() + 1,
gas_limit: GAS_LIMIT,
gas_used: GAS_USED,
timestamp: attributes.timestamp,
extra_data: "block gen was here".as_bytes().to_vec().into(),
base_fee_per_gas: Uint256::one(),
block_hash: ExecutionBlockHash::zero(),
transactions: vec![].into(),
// FIXME: think about how to test different forks
let mut execution_payload = match &attributes {
PayloadAttributes::V1(pa) => ExecutionPayload::Merge(ExecutionPayloadMerge {
parent_hash: forkchoice_state.head_block_hash,
fee_recipient: pa.suggested_fee_recipient,
receipts_root: Hash256::repeat_byte(42),
state_root: Hash256::repeat_byte(43),
logs_bloom: vec![0; 256].into(),
prev_randao: pa.prev_randao,
block_number: parent.block_number() + 1,
gas_limit: GAS_LIMIT,
gas_used: GAS_USED,
timestamp: pa.timestamp,
extra_data: "block gen was here".as_bytes().to_vec().into(),
base_fee_per_gas: Uint256::one(),
block_hash: ExecutionBlockHash::zero(),
transactions: vec![].into(),
}),
PayloadAttributes::V2(pa) => {
ExecutionPayload::Capella(ExecutionPayloadCapella {
parent_hash: forkchoice_state.head_block_hash,
fee_recipient: pa.suggested_fee_recipient,
receipts_root: Hash256::repeat_byte(42),
state_root: Hash256::repeat_byte(43),
logs_bloom: vec![0; 256].into(),
prev_randao: pa.prev_randao,
block_number: parent.block_number() + 1,
gas_limit: GAS_LIMIT,
gas_used: GAS_USED,
timestamp: pa.timestamp,
extra_data: "block gen was here".as_bytes().to_vec().into(),
base_fee_per_gas: Uint256::one(),
block_hash: ExecutionBlockHash::zero(),
transactions: vec![].into(),
withdrawals: pa
.withdrawals
.iter()
.cloned()
.map(Into::into)
.collect::<Vec<_>>()
.into(),
})
}
};
execution_payload.block_hash =
*execution_payload.block_hash_mut() =
ExecutionBlockHash::from_root(execution_payload.tree_hash_root());
self.payload_ids.insert(id, execution_payload);

View File

@@ -75,12 +75,12 @@ pub async fn handle_rpc<T: EthSpec>(
}
}
ENGINE_NEW_PAYLOAD_V1 => {
let request: JsonExecutionPayloadV1<T> = get_param(params, 0)?;
let request: JsonExecutionPayload<T> = get_param(params, 0)?;
let (static_response, should_import) =
if let Some(mut response) = ctx.static_new_payload_response.lock().clone() {
if response.status.status == PayloadStatusV1Status::Valid {
response.status.latest_valid_hash = Some(request.block_hash)
response.status.latest_valid_hash = Some(*request.block_hash())
}
(Some(response.status), response.should_import)
@@ -112,11 +112,11 @@ pub async fn handle_rpc<T: EthSpec>(
.get_payload(&id)
.ok_or_else(|| format!("no payload for id {:?}", id))?;
Ok(serde_json::to_value(JsonExecutionPayloadV1::from(response)).unwrap())
Ok(serde_json::to_value(JsonExecutionPayload::from(response)).unwrap())
}
ENGINE_FORKCHOICE_UPDATED_V1 => {
let forkchoice_state: JsonForkChoiceStateV1 = get_param(params, 0)?;
let payload_attributes: Option<JsonPayloadAttributesV1> = get_param(params, 1)?;
let payload_attributes: Option<JsonPayloadAttributes> = get_param(params, 1)?;
let head_block_hash = forkchoice_state.head_block_hash;

View File

@@ -1,5 +1,5 @@
use crate::test_utils::DEFAULT_JWT_SECRET;
use crate::{Config, ExecutionLayer, PayloadAttributes};
use crate::{Config, ExecutionLayer, PayloadAttributes, PayloadAttributesV1};
use async_trait::async_trait;
use eth2::types::{BlockId, StateId, ValidatorId};
use eth2::{BeaconNodeHttpClient, Timeouts};
@@ -287,11 +287,12 @@ impl<E: EthSpec> mev_build_rs::BlindedBlockProvider for MockBuilder<E> {
.get_randao_mix(head_state.current_epoch())
.map_err(convert_err)?;
let payload_attributes = PayloadAttributes {
// FIXME: think about proper fork here
let payload_attributes = PayloadAttributes::V1(PayloadAttributesV1 {
timestamp,
prev_randao: *prev_randao,
suggested_fee_recipient: fee_recipient,
};
});
self.el
.insert_proposer(slot, head_block_root, val_index, payload_attributes)
@@ -315,6 +316,7 @@ impl<E: EthSpec> mev_build_rs::BlindedBlockProvider for MockBuilder<E> {
)
.await
.map_err(convert_err)?
.to_payload()
.to_execution_payload_header();
let json_payload = serde_json::to_string(&payload).map_err(convert_err)?;

View File

@@ -1,7 +1,7 @@
use crate::{
test_utils::{
MockServer, DEFAULT_BUILDER_THRESHOLD_WEI, DEFAULT_JWT_SECRET, DEFAULT_TERMINAL_BLOCK,
DEFAULT_TERMINAL_DIFFICULTY,
Block, MockServer, DEFAULT_BUILDER_THRESHOLD_WEI, DEFAULT_JWT_SECRET,
DEFAULT_TERMINAL_BLOCK, DEFAULT_TERMINAL_DIFFICULTY,
},
Config, *,
};
@@ -99,20 +99,37 @@ impl<T: EthSpec> MockExecutionLayer<T> {
finalized_hash: None,
};
// FIXME: this is just best guess for how to deal with forks here..
let payload_attributes = match &latest_execution_block {
&Block::PoS(ref pos_block) => match pos_block {
&ExecutionPayload::Merge(_) => PayloadAttributes::V1(PayloadAttributesV1 {
timestamp,
prev_randao,
suggested_fee_recipient: Address::repeat_byte(42),
}),
&ExecutionPayload::Capella(_) | &ExecutionPayload::Eip4844(_) => {
PayloadAttributes::V2(PayloadAttributesV2 {
timestamp,
prev_randao,
suggested_fee_recipient: Address::repeat_byte(42),
// FIXME: think about adding withdrawals here..
withdrawals: vec![],
})
}
},
// I guess a PoW blocks means we should use Merge?
&Block::PoW(_) => PayloadAttributes::V1(PayloadAttributesV1 {
timestamp,
prev_randao,
suggested_fee_recipient: Address::repeat_byte(42),
}),
};
// Insert a proposer to ensure the fork choice updated command works.
let slot = Slot::new(0);
let validator_index = 0;
self.el
.insert_proposer(
slot,
head_block_root,
validator_index,
PayloadAttributes {
timestamp,
prev_randao,
suggested_fee_recipient: Address::repeat_byte(42),
},
)
.insert_proposer(slot, head_block_root, validator_index, payload_attributes)
.await;
self.el
@@ -132,7 +149,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
slot,
chain_health: ChainHealth::Healthy,
};
let payload = self
let payload: ExecutionPayload<T> = self
.el
.get_payload::<FullPayload<T>>(
parent_hash,
@@ -145,12 +162,14 @@ impl<T: EthSpec> MockExecutionLayer<T> {
)
.await
.unwrap()
.execution_payload;
let block_hash = payload.block_hash;
assert_eq!(payload.parent_hash, parent_hash);
assert_eq!(payload.block_number, block_number);
assert_eq!(payload.timestamp, timestamp);
assert_eq!(payload.prev_randao, prev_randao);
.to_payload()
.into();
let block_hash = payload.block_hash();
assert_eq!(payload.parent_hash(), parent_hash);
assert_eq!(payload.block_number(), block_number);
assert_eq!(payload.timestamp(), timestamp);
assert_eq!(payload.prev_randao(), prev_randao);
// Ensure the payload cache is empty.
assert!(self
@@ -175,12 +194,13 @@ impl<T: EthSpec> MockExecutionLayer<T> {
)
.await
.unwrap()
.execution_payload_header;
assert_eq!(payload_header.block_hash, block_hash);
assert_eq!(payload_header.parent_hash, parent_hash);
assert_eq!(payload_header.block_number, block_number);
assert_eq!(payload_header.timestamp, timestamp);
assert_eq!(payload_header.prev_randao, prev_randao);
.to_payload();
assert_eq!(payload_header.block_hash(), block_hash);
assert_eq!(payload_header.parent_hash(), parent_hash);
assert_eq!(payload_header.block_number(), block_number);
assert_eq!(payload_header.timestamp(), timestamp);
assert_eq!(payload_header.prev_randao(), prev_randao);
// Ensure the payload cache has the correct payload.
assert_eq!(