mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-17 20:02:43 +00:00
Merge remote-tracking branch 'origin/deneb-free-blobs' into tree-states
This commit is contained in:
@@ -7,7 +7,7 @@ use ethers_core::utils::rlp::RlpStream;
|
||||
use keccak_hash::KECCAK_EMPTY_LIST_RLP;
|
||||
use triehash::ordered_trie_root;
|
||||
use types::{
|
||||
map_execution_block_header_fields_except_withdrawals, Address, EthSpec, ExecutionBlockHash,
|
||||
map_execution_block_header_fields_base, Address, BeaconBlockRef, EthSpec, ExecutionBlockHash,
|
||||
ExecutionBlockHeader, ExecutionPayloadRef, Hash256, Hash64, Uint256,
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
/// transactions.
|
||||
pub fn calculate_execution_block_hash(
|
||||
payload: ExecutionPayloadRef<T>,
|
||||
parent_beacon_block_root: Hash256,
|
||||
) -> (ExecutionBlockHash, Hash256) {
|
||||
// Calculate the transactions root.
|
||||
// We're currently using a deprecated Parity library for this. We should move to a
|
||||
@@ -37,12 +38,23 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
None
|
||||
};
|
||||
|
||||
let rlp_blob_gas_used = payload.blob_gas_used().ok();
|
||||
let rlp_excess_blob_gas = payload.excess_blob_gas().ok();
|
||||
|
||||
// Calculate parent beacon block root (post-Deneb).
|
||||
let rlp_parent_beacon_block_root = rlp_excess_blob_gas
|
||||
.as_ref()
|
||||
.map(|_| parent_beacon_block_root);
|
||||
|
||||
// Construct the block header.
|
||||
let exec_block_header = ExecutionBlockHeader::from_payload(
|
||||
payload,
|
||||
KECCAK_EMPTY_LIST_RLP.as_fixed_bytes().into(),
|
||||
rlp_transactions_root,
|
||||
rlp_withdrawals_root,
|
||||
rlp_blob_gas_used,
|
||||
rlp_excess_blob_gas,
|
||||
rlp_parent_beacon_block_root,
|
||||
);
|
||||
|
||||
// Hash the RLP encoding of the block header.
|
||||
@@ -56,10 +68,14 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
/// Verify `payload.block_hash` locally within Lighthouse.
|
||||
///
|
||||
/// No remote calls to the execution client will be made, so this is quite a cheap check.
|
||||
pub fn verify_payload_block_hash(&self, payload: ExecutionPayloadRef<T>) -> Result<(), Error> {
|
||||
pub fn verify_payload_block_hash(&self, block: BeaconBlockRef<T>) -> Result<(), Error> {
|
||||
let payload = block.execution_payload()?.execution_payload_ref();
|
||||
let parent_beacon_block_root = block.parent_root();
|
||||
|
||||
let _timer = metrics::start_timer(&metrics::EXECUTION_LAYER_VERIFY_BLOCK_HASH);
|
||||
|
||||
let (header_hash, rlp_transactions_root) = Self::calculate_execution_block_hash(payload);
|
||||
let (header_hash, rlp_transactions_root) =
|
||||
Self::calculate_execution_block_hash(payload, parent_beacon_block_root);
|
||||
|
||||
if header_hash != payload.block_hash() {
|
||||
return Err(Error::BlockHashMismatch {
|
||||
@@ -88,12 +104,21 @@ pub fn rlp_encode_withdrawal(withdrawal: &JsonWithdrawal) -> Vec<u8> {
|
||||
pub fn rlp_encode_block_header(header: &ExecutionBlockHeader) -> Vec<u8> {
|
||||
let mut rlp_header_stream = RlpStream::new();
|
||||
rlp_header_stream.begin_unbounded_list();
|
||||
map_execution_block_header_fields_except_withdrawals!(&header, |_, field| {
|
||||
map_execution_block_header_fields_base!(&header, |_, field| {
|
||||
rlp_header_stream.append(field);
|
||||
});
|
||||
if let Some(withdrawals_root) = &header.withdrawals_root {
|
||||
rlp_header_stream.append(withdrawals_root);
|
||||
}
|
||||
if let Some(blob_gas_used) = &header.blob_gas_used {
|
||||
rlp_header_stream.append(blob_gas_used);
|
||||
}
|
||||
if let Some(excess_blob_gas) = &header.excess_blob_gas {
|
||||
rlp_header_stream.append(excess_blob_gas);
|
||||
}
|
||||
if let Some(parent_beacon_block_root) = &header.parent_beacon_block_root {
|
||||
rlp_header_stream.append(parent_beacon_block_root);
|
||||
}
|
||||
rlp_header_stream.finalize_unbounded_list();
|
||||
rlp_header_stream.out().into()
|
||||
}
|
||||
@@ -140,6 +165,9 @@ mod test {
|
||||
nonce: Hash64::zero(),
|
||||
base_fee_per_gas: 0x036b_u64.into(),
|
||||
withdrawals_root: None,
|
||||
blob_gas_used: None,
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
};
|
||||
let expected_rlp = "f90200a0e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ba5e000000000000000000000000000000000000a0ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7a050f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accfa029b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200000188016345785d8a00008301553482079e42a0000000000000000000000000000000000000000000000000000000000000000088000000000000000082036b";
|
||||
let expected_hash =
|
||||
@@ -168,6 +196,9 @@ mod test {
|
||||
nonce: Hash64::zero(),
|
||||
base_fee_per_gas: 0x036b_u64.into(),
|
||||
withdrawals_root: None,
|
||||
blob_gas_used: None,
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
};
|
||||
let expected_rlp = "f901fda0927ca537f06c783a3a2635b8805eef1c8c2124f7444ad4a3389898dd832f2dbea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ba5e000000000000000000000000000000000000a0e97859b065bd8dbbb4519c7cb935024de2484c2b7f881181b4360492f0b06b82a050f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accfa029b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800188016345785d8a00008301553482079e42a0000000000000000000000000000000000000000000000000000000000002000088000000000000000082036b";
|
||||
let expected_hash =
|
||||
@@ -197,10 +228,43 @@ mod test {
|
||||
nonce: Hash64::zero(),
|
||||
base_fee_per_gas: 0x34187b238_u64.into(),
|
||||
withdrawals_root: None,
|
||||
blob_gas_used: None,
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
};
|
||||
let expected_hash =
|
||||
Hash256::from_str("6da69709cd5a34079b6604d29cd78fc01dacd7c6268980057ad92a2bede87351")
|
||||
.unwrap();
|
||||
test_rlp_encoding(&header, None, expected_hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rlp_encode_block_deneb() {
|
||||
let header = ExecutionBlockHeader {
|
||||
parent_hash: Hash256::from_str("172864416698b842f4c92f7b476be294b4ef720202779df194cd225f531053ab").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(),
|
||||
difficulty: 0.into(),
|
||||
number: 97.into(),
|
||||
gas_limit: 27482534.into(),
|
||||
gas_used: 0.into(),
|
||||
timestamp: 1692132829u64,
|
||||
extra_data: hex::decode("d883010d00846765746888676f312e32302e37856c696e7578").unwrap(),
|
||||
mix_hash: Hash256::from_str("0b493c22d2ad4ca76c77ae6ad916af429b42b1dc98fdcb8e5ddbd049bbc5d623").unwrap(),
|
||||
nonce: Hash64::zero(),
|
||||
base_fee_per_gas: 2374u64.into(),
|
||||
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()),
|
||||
};
|
||||
let expected_hash =
|
||||
Hash256::from_str("a7448e600ead0a23d16f96aa46e8dea9eef8a7c5669a5f0a5ff32709afe9c408")
|
||||
.unwrap();
|
||||
test_rlp_encoding(&header, None, expected_hash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
use crate::engines::ForkchoiceState;
|
||||
use crate::http::{
|
||||
ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2,
|
||||
ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3,
|
||||
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1,
|
||||
ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2,
|
||||
ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, ENGINE_GET_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V1,
|
||||
ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3,
|
||||
};
|
||||
use eth2::types::{SsePayloadAttributes, SsePayloadAttributesV1, SsePayloadAttributesV2};
|
||||
pub use ethers_core::types::Transaction;
|
||||
use ethers_core::utils::rlp::{self, Decodable, Rlp};
|
||||
use eth2::types::{
|
||||
BlobsBundle, SsePayloadAttributes, SsePayloadAttributesV1, SsePayloadAttributesV2,
|
||||
SsePayloadAttributesV3,
|
||||
};
|
||||
use ethers_core::types::Transaction;
|
||||
use ethers_core::utils::rlp;
|
||||
use ethers_core::utils::rlp::{Decodable, Rlp};
|
||||
use http::deposit_methods::RpcError;
|
||||
pub use json_structures::{JsonWithdrawal, TransitionConfigurationV1};
|
||||
use pretty_reqwest_error::PrettyReqwestError;
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ssz_types::FixedVector;
|
||||
use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_hash;
|
||||
use std::convert::TryFrom;
|
||||
use strum::IntoStaticStr;
|
||||
use superstruct::superstruct;
|
||||
pub use types::{
|
||||
Address, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader,
|
||||
Address, BeaconBlockRef, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader,
|
||||
ExecutionPayloadRef, ForkName, Hash256, Transactions, Uint256, VariableList, Withdrawal,
|
||||
Withdrawals,
|
||||
};
|
||||
use types::{ExecutionPayloadCapella, ExecutionPayloadMerge};
|
||||
use types::{
|
||||
BeaconStateError, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge,
|
||||
KzgProofs, VersionedHash,
|
||||
};
|
||||
|
||||
pub mod auth;
|
||||
pub mod http;
|
||||
@@ -49,14 +58,12 @@ pub enum Error {
|
||||
PayloadIdUnavailable,
|
||||
TransitionConfigurationMismatch,
|
||||
PayloadConversionLogicFlaw,
|
||||
DeserializeTransaction(ssz_types::Error),
|
||||
DeserializeTransactions(ssz_types::Error),
|
||||
SszError(ssz_types::Error),
|
||||
DeserializeWithdrawals(ssz_types::Error),
|
||||
BuilderApi(builder_client::Error),
|
||||
IncorrectStateVariant,
|
||||
RequiredMethodUnsupported(&'static str),
|
||||
UnsupportedForkVariant(String),
|
||||
BadConversion(String),
|
||||
RlpDecoderError(rlp::DecoderError),
|
||||
}
|
||||
|
||||
@@ -97,6 +104,12 @@ impl From<rlp::DecoderError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ssz_types::Error> for Error {
|
||||
fn from(e: ssz_types::Error) -> Self {
|
||||
Error::SszError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, IntoStaticStr)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum PayloadStatusV1Status {
|
||||
@@ -138,7 +151,7 @@ pub struct ExecutionBlock {
|
||||
|
||||
/// Representation of an execution block with enough detail to reconstruct a payload.
|
||||
#[superstruct(
|
||||
variants(Merge, Capella),
|
||||
variants(Merge, Capella, Deneb),
|
||||
variant_attributes(
|
||||
derive(Clone, Debug, PartialEq, Serialize, Deserialize,),
|
||||
serde(bound = "T: EthSpec", rename_all = "camelCase"),
|
||||
@@ -172,8 +185,14 @@ pub struct ExecutionBlockWithTransactions<T: EthSpec> {
|
||||
#[serde(rename = "hash")]
|
||||
pub block_hash: ExecutionBlockHash,
|
||||
pub transactions: Vec<Transaction>,
|
||||
#[superstruct(only(Capella))]
|
||||
#[superstruct(only(Capella, Deneb))]
|
||||
pub withdrawals: Vec<JsonWithdrawal>,
|
||||
#[superstruct(only(Deneb))]
|
||||
#[serde(with = "serde_utils::u64_hex_be")]
|
||||
pub blob_gas_used: u64,
|
||||
#[superstruct(only(Deneb))]
|
||||
#[serde(with = "serde_utils::u64_hex_be")]
|
||||
pub excess_blob_gas: u64,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TryFrom<ExecutionPayload<T>> for ExecutionBlockWithTransactions<T> {
|
||||
@@ -227,13 +246,39 @@ impl<T: EthSpec> TryFrom<ExecutionPayload<T>> for ExecutionBlockWithTransactions
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
ExecutionPayload::Deneb(block) => Self::Deneb(ExecutionBlockWithTransactionsDeneb {
|
||||
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<_>, _>>()?,
|
||||
withdrawals: Vec::from(block.withdrawals)
|
||||
.into_iter()
|
||||
.map(|withdrawal| withdrawal.into())
|
||||
.collect(),
|
||||
blob_gas_used: block.blob_gas_used,
|
||||
excess_blob_gas: block.excess_blob_gas,
|
||||
}),
|
||||
};
|
||||
Ok(json_payload)
|
||||
}
|
||||
}
|
||||
|
||||
#[superstruct(
|
||||
variants(V1, V2),
|
||||
variants(V1, V2, V3),
|
||||
variant_attributes(derive(Clone, Debug, Eq, Hash, PartialEq),),
|
||||
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
|
||||
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
|
||||
@@ -246,8 +291,10 @@ pub struct PayloadAttributes {
|
||||
pub prev_randao: Hash256,
|
||||
#[superstruct(getter(copy))]
|
||||
pub suggested_fee_recipient: Address,
|
||||
#[superstruct(only(V2))]
|
||||
#[superstruct(only(V2, V3))]
|
||||
pub withdrawals: Vec<Withdrawal>,
|
||||
#[superstruct(only(V3), partial_getter(copy))]
|
||||
pub parent_beacon_block_root: Hash256,
|
||||
}
|
||||
|
||||
impl PayloadAttributes {
|
||||
@@ -256,14 +303,24 @@ impl PayloadAttributes {
|
||||
prev_randao: Hash256,
|
||||
suggested_fee_recipient: Address,
|
||||
withdrawals: Option<Vec<Withdrawal>>,
|
||||
parent_beacon_block_root: Option<Hash256>,
|
||||
) -> Self {
|
||||
match withdrawals {
|
||||
Some(withdrawals) => PayloadAttributes::V2(PayloadAttributesV2 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
withdrawals,
|
||||
}),
|
||||
Some(withdrawals) => match parent_beacon_block_root {
|
||||
Some(parent_beacon_block_root) => PayloadAttributes::V3(PayloadAttributesV3 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
withdrawals,
|
||||
parent_beacon_block_root,
|
||||
}),
|
||||
None => PayloadAttributes::V2(PayloadAttributesV2 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
withdrawals,
|
||||
}),
|
||||
},
|
||||
None => PayloadAttributes::V1(PayloadAttributesV1 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
@@ -296,6 +353,19 @@ impl From<PayloadAttributes> for SsePayloadAttributes {
|
||||
suggested_fee_recipient,
|
||||
withdrawals,
|
||||
}),
|
||||
PayloadAttributes::V3(PayloadAttributesV3 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
withdrawals,
|
||||
parent_beacon_block_root,
|
||||
}) => Self::V3(SsePayloadAttributesV3 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
withdrawals,
|
||||
parent_beacon_block_root,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -321,7 +391,7 @@ pub struct ProposeBlindedBlockResponse {
|
||||
}
|
||||
|
||||
#[superstruct(
|
||||
variants(Merge, Capella),
|
||||
variants(Merge, Capella, Deneb),
|
||||
variant_attributes(derive(Clone, Debug, PartialEq),),
|
||||
map_into(ExecutionPayload),
|
||||
map_ref_into(ExecutionPayloadRef),
|
||||
@@ -334,7 +404,27 @@ pub struct GetPayloadResponse<T: EthSpec> {
|
||||
pub execution_payload: ExecutionPayloadMerge<T>,
|
||||
#[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))]
|
||||
pub execution_payload: ExecutionPayloadCapella<T>,
|
||||
#[superstruct(only(Deneb), partial_getter(rename = "execution_payload_deneb"))]
|
||||
pub execution_payload: ExecutionPayloadDeneb<T>,
|
||||
pub block_value: Uint256,
|
||||
#[superstruct(only(Deneb))]
|
||||
pub blobs_bundle: BlobsBundle<T>,
|
||||
#[superstruct(only(Deneb), partial_getter(copy))]
|
||||
pub should_override_builder: bool,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> GetPayloadResponse<E> {
|
||||
pub fn fee_recipient(&self) -> Address {
|
||||
ExecutionPayloadRef::from(self.to_ref()).fee_recipient()
|
||||
}
|
||||
|
||||
pub fn block_hash(&self) -> ExecutionBlockHash {
|
||||
ExecutionPayloadRef::from(self.to_ref()).block_hash()
|
||||
}
|
||||
|
||||
pub fn block_number(&self) -> u64 {
|
||||
ExecutionPayloadRef::from(self.to_ref()).block_number()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: EthSpec> From<GetPayloadResponseRef<'a, T>> for ExecutionPayloadRef<'a, T> {
|
||||
@@ -353,16 +443,25 @@ impl<T: EthSpec> From<GetPayloadResponse<T>> for ExecutionPayload<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<GetPayloadResponse<T>> for (ExecutionPayload<T>, Uint256) {
|
||||
impl<T: EthSpec> From<GetPayloadResponse<T>>
|
||||
for (ExecutionPayload<T>, Uint256, Option<BlobsBundle<T>>)
|
||||
{
|
||||
fn from(response: GetPayloadResponse<T>) -> Self {
|
||||
match response {
|
||||
GetPayloadResponse::Merge(inner) => (
|
||||
ExecutionPayload::Merge(inner.execution_payload),
|
||||
inner.block_value,
|
||||
None,
|
||||
),
|
||||
GetPayloadResponse::Capella(inner) => (
|
||||
ExecutionPayload::Capella(inner.execution_payload),
|
||||
inner.block_value,
|
||||
None,
|
||||
),
|
||||
GetPayloadResponse::Deneb(inner) => (
|
||||
ExecutionPayload::Deneb(inner.execution_payload),
|
||||
inner.block_value,
|
||||
Some(inner.blobs_bundle),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -436,6 +535,138 @@ impl<E: EthSpec> ExecutionPayloadBodyV1<E> {
|
||||
))
|
||||
}
|
||||
}
|
||||
ExecutionPayloadHeader::Deneb(header) => {
|
||||
if let Some(withdrawals) = self.withdrawals {
|
||||
Ok(ExecutionPayload::Deneb(ExecutionPayloadDeneb {
|
||||
parent_hash: header.parent_hash,
|
||||
fee_recipient: header.fee_recipient,
|
||||
state_root: header.state_root,
|
||||
receipts_root: header.receipts_root,
|
||||
logs_bloom: header.logs_bloom,
|
||||
prev_randao: header.prev_randao,
|
||||
block_number: header.block_number,
|
||||
gas_limit: header.gas_limit,
|
||||
gas_used: header.gas_used,
|
||||
timestamp: header.timestamp,
|
||||
extra_data: header.extra_data,
|
||||
base_fee_per_gas: header.base_fee_per_gas,
|
||||
block_hash: header.block_hash,
|
||||
transactions: self.transactions,
|
||||
withdrawals,
|
||||
blob_gas_used: header.blob_gas_used,
|
||||
excess_blob_gas: header.excess_blob_gas,
|
||||
}))
|
||||
} else {
|
||||
Err(format!(
|
||||
"block {} is post capella but payload body doesn't have withdrawals",
|
||||
header.block_hash
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[superstruct(
|
||||
variants(Merge, Capella, Deneb),
|
||||
variant_attributes(derive(Clone, Debug, PartialEq),),
|
||||
map_into(ExecutionPayload),
|
||||
map_ref_into(ExecutionPayloadRef),
|
||||
cast_error(
|
||||
ty = "BeaconStateError",
|
||||
expr = "BeaconStateError::IncorrectStateVariant"
|
||||
),
|
||||
partial_getter_error(
|
||||
ty = "BeaconStateError",
|
||||
expr = "BeaconStateError::IncorrectStateVariant"
|
||||
)
|
||||
)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NewPayloadRequest<E: EthSpec> {
|
||||
#[superstruct(only(Merge), partial_getter(rename = "execution_payload_merge"))]
|
||||
pub execution_payload: ExecutionPayloadMerge<E>,
|
||||
#[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))]
|
||||
pub execution_payload: ExecutionPayloadCapella<E>,
|
||||
#[superstruct(only(Deneb), partial_getter(rename = "execution_payload_deneb"))]
|
||||
pub execution_payload: ExecutionPayloadDeneb<E>,
|
||||
#[superstruct(only(Deneb))]
|
||||
pub versioned_hashes: Vec<VersionedHash>,
|
||||
#[superstruct(only(Deneb))]
|
||||
pub parent_beacon_block_root: Hash256,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> NewPayloadRequest<E> {
|
||||
pub fn parent_hash(&self) -> ExecutionBlockHash {
|
||||
match self {
|
||||
Self::Merge(payload) => payload.execution_payload.parent_hash,
|
||||
Self::Capella(payload) => payload.execution_payload.parent_hash,
|
||||
Self::Deneb(payload) => payload.execution_payload.parent_hash,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_hash(&self) -> ExecutionBlockHash {
|
||||
match self {
|
||||
Self::Merge(payload) => payload.execution_payload.block_hash,
|
||||
Self::Capella(payload) => payload.execution_payload.block_hash,
|
||||
Self::Deneb(payload) => payload.execution_payload.block_hash,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_number(&self) -> u64 {
|
||||
match self {
|
||||
Self::Merge(payload) => payload.execution_payload.block_number,
|
||||
Self::Capella(payload) => payload.execution_payload.block_number,
|
||||
Self::Deneb(payload) => payload.execution_payload.block_number,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_execution_payload(self) -> ExecutionPayload<E> {
|
||||
map_new_payload_request_into_execution_payload!(self, |request, cons| {
|
||||
cons(request.execution_payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: EthSpec> TryFrom<BeaconBlockRef<'a, E>> for NewPayloadRequest<E> {
|
||||
type Error = BeaconStateError;
|
||||
|
||||
fn try_from(block: BeaconBlockRef<'a, E>) -> Result<Self, Self::Error> {
|
||||
match block {
|
||||
BeaconBlockRef::Base(_) | BeaconBlockRef::Altair(_) => {
|
||||
Err(Self::Error::IncorrectStateVariant)
|
||||
}
|
||||
BeaconBlockRef::Merge(block_ref) => Ok(Self::Merge(NewPayloadRequestMerge {
|
||||
execution_payload: block_ref.body.execution_payload.execution_payload.clone(),
|
||||
})),
|
||||
BeaconBlockRef::Capella(block_ref) => Ok(Self::Capella(NewPayloadRequestCapella {
|
||||
execution_payload: block_ref.body.execution_payload.execution_payload.clone(),
|
||||
})),
|
||||
BeaconBlockRef::Deneb(block_ref) => Ok(Self::Deneb(NewPayloadRequestDeneb {
|
||||
execution_payload: block_ref.body.execution_payload.execution_payload.clone(),
|
||||
versioned_hashes: block_ref
|
||||
.body
|
||||
.blob_kzg_commitments
|
||||
.iter()
|
||||
.map(kzg_commitment_to_versioned_hash)
|
||||
.collect(),
|
||||
parent_beacon_block_root: block_ref.parent_root,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> TryFrom<ExecutionPayload<E>> for NewPayloadRequest<E> {
|
||||
type Error = BeaconStateError;
|
||||
|
||||
fn try_from(payload: ExecutionPayload<E>) -> Result<Self, Self::Error> {
|
||||
match payload {
|
||||
ExecutionPayload::Merge(payload) => Ok(Self::Merge(NewPayloadRequestMerge {
|
||||
execution_payload: payload,
|
||||
})),
|
||||
ExecutionPayload::Capella(payload) => Ok(Self::Capella(NewPayloadRequestCapella {
|
||||
execution_payload: payload,
|
||||
})),
|
||||
ExecutionPayload::Deneb(_) => Err(Self::Error::IncorrectStateVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -444,12 +675,15 @@ impl<E: EthSpec> ExecutionPayloadBodyV1<E> {
|
||||
pub struct EngineCapabilities {
|
||||
pub new_payload_v1: bool,
|
||||
pub new_payload_v2: bool,
|
||||
pub new_payload_v3: bool,
|
||||
pub forkchoice_updated_v1: bool,
|
||||
pub forkchoice_updated_v2: bool,
|
||||
pub forkchoice_updated_v3: bool,
|
||||
pub get_payload_bodies_by_hash_v1: bool,
|
||||
pub get_payload_bodies_by_range_v1: bool,
|
||||
pub get_payload_v1: bool,
|
||||
pub get_payload_v2: bool,
|
||||
pub get_payload_v3: bool,
|
||||
}
|
||||
|
||||
impl EngineCapabilities {
|
||||
@@ -461,12 +695,18 @@ impl EngineCapabilities {
|
||||
if self.new_payload_v2 {
|
||||
response.push(ENGINE_NEW_PAYLOAD_V2);
|
||||
}
|
||||
if self.new_payload_v3 {
|
||||
response.push(ENGINE_NEW_PAYLOAD_V3);
|
||||
}
|
||||
if self.forkchoice_updated_v1 {
|
||||
response.push(ENGINE_FORKCHOICE_UPDATED_V1);
|
||||
}
|
||||
if self.forkchoice_updated_v2 {
|
||||
response.push(ENGINE_FORKCHOICE_UPDATED_V2);
|
||||
}
|
||||
if self.forkchoice_updated_v3 {
|
||||
response.push(ENGINE_FORKCHOICE_UPDATED_V3);
|
||||
}
|
||||
if self.get_payload_bodies_by_hash_v1 {
|
||||
response.push(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1);
|
||||
}
|
||||
@@ -479,6 +719,9 @@ impl EngineCapabilities {
|
||||
if self.get_payload_v2 {
|
||||
response.push(ENGINE_GET_PAYLOAD_V2);
|
||||
}
|
||||
if self.get_payload_v3 {
|
||||
response.push(ENGINE_GET_PAYLOAD_V3);
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
@@ -32,14 +32,17 @@ pub const ETH_SYNCING_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
pub const ENGINE_NEW_PAYLOAD_V1: &str = "engine_newPayloadV1";
|
||||
pub const ENGINE_NEW_PAYLOAD_V2: &str = "engine_newPayloadV2";
|
||||
pub const ENGINE_NEW_PAYLOAD_V3: &str = "engine_newPayloadV3";
|
||||
pub const ENGINE_NEW_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(8);
|
||||
|
||||
pub const ENGINE_GET_PAYLOAD_V1: &str = "engine_getPayloadV1";
|
||||
pub const ENGINE_GET_PAYLOAD_V2: &str = "engine_getPayloadV2";
|
||||
pub const ENGINE_GET_PAYLOAD_V3: &str = "engine_getPayloadV3";
|
||||
pub const ENGINE_GET_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
|
||||
pub const ENGINE_FORKCHOICE_UPDATED_V1: &str = "engine_forkchoiceUpdatedV1";
|
||||
pub const ENGINE_FORKCHOICE_UPDATED_V2: &str = "engine_forkchoiceUpdatedV2";
|
||||
pub const ENGINE_FORKCHOICE_UPDATED_V3: &str = "engine_forkchoiceUpdatedV3";
|
||||
pub const ENGINE_FORKCHOICE_UPDATED_TIMEOUT: Duration = Duration::from_secs(8);
|
||||
|
||||
pub const ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1: &str = "engine_getPayloadBodiesByHashV1";
|
||||
@@ -58,10 +61,13 @@ pub const METHOD_NOT_FOUND_CODE: i64 = -32601;
|
||||
pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[
|
||||
ENGINE_NEW_PAYLOAD_V1,
|
||||
ENGINE_NEW_PAYLOAD_V2,
|
||||
ENGINE_NEW_PAYLOAD_V3,
|
||||
ENGINE_GET_PAYLOAD_V1,
|
||||
ENGINE_GET_PAYLOAD_V2,
|
||||
ENGINE_GET_PAYLOAD_V3,
|
||||
ENGINE_FORKCHOICE_UPDATED_V1,
|
||||
ENGINE_FORKCHOICE_UPDATED_V2,
|
||||
ENGINE_FORKCHOICE_UPDATED_V3,
|
||||
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1,
|
||||
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1,
|
||||
];
|
||||
@@ -72,12 +78,15 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[
|
||||
pub static PRE_CAPELLA_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities {
|
||||
new_payload_v1: true,
|
||||
new_payload_v2: false,
|
||||
new_payload_v3: false,
|
||||
forkchoice_updated_v1: true,
|
||||
forkchoice_updated_v2: false,
|
||||
forkchoice_updated_v3: false,
|
||||
get_payload_bodies_by_hash_v1: false,
|
||||
get_payload_bodies_by_range_v1: false,
|
||||
get_payload_v1: true,
|
||||
get_payload_v2: false,
|
||||
get_payload_v3: false,
|
||||
};
|
||||
|
||||
/// Contains methods to convert arbitrary bytes to an ETH2 deposit contract object.
|
||||
@@ -741,6 +750,14 @@ impl HttpJsonRpc {
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
ForkName::Deneb => ExecutionBlockWithTransactions::Deneb(
|
||||
self.rpc_request(
|
||||
ETH_GET_BLOCK_BY_HASH,
|
||||
params,
|
||||
ETH_GET_BLOCK_BY_HASH_TIMEOUT * self.execution_timeout_multiplier,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
ForkName::Base | ForkName::Altair => {
|
||||
return Err(Error::UnsupportedForkVariant(format!(
|
||||
"called get_block_by_hash_with_txns with fork {:?}",
|
||||
@@ -784,6 +801,27 @@ impl HttpJsonRpc {
|
||||
Ok(response.into())
|
||||
}
|
||||
|
||||
pub async fn new_payload_v3<T: EthSpec>(
|
||||
&self,
|
||||
new_payload_request_deneb: NewPayloadRequestDeneb<T>,
|
||||
) -> Result<PayloadStatusV1, Error> {
|
||||
let params = json!([
|
||||
JsonExecutionPayload::V3(new_payload_request_deneb.execution_payload.into()),
|
||||
new_payload_request_deneb.versioned_hashes,
|
||||
new_payload_request_deneb.parent_beacon_block_root,
|
||||
]);
|
||||
|
||||
let response: JsonPayloadStatusV1 = self
|
||||
.rpc_request(
|
||||
ENGINE_NEW_PAYLOAD_V3,
|
||||
params,
|
||||
ENGINE_NEW_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(response.into())
|
||||
}
|
||||
|
||||
pub async fn get_payload_v1<T: EthSpec>(
|
||||
&self,
|
||||
payload_id: PayloadId,
|
||||
@@ -835,8 +873,32 @@ impl HttpJsonRpc {
|
||||
.await?;
|
||||
Ok(JsonGetPayloadResponse::V2(response).into())
|
||||
}
|
||||
ForkName::Base | ForkName::Altair => Err(Error::UnsupportedForkVariant(format!(
|
||||
"called get_payload_v2 with {}",
|
||||
ForkName::Base | ForkName::Altair | ForkName::Deneb => Err(
|
||||
Error::UnsupportedForkVariant(format!("called get_payload_v2 with {}", fork_name)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_payload_v3<T: EthSpec>(
|
||||
&self,
|
||||
fork_name: ForkName,
|
||||
payload_id: PayloadId,
|
||||
) -> Result<GetPayloadResponse<T>, Error> {
|
||||
let params = json!([JsonPayloadIdRequest::from(payload_id)]);
|
||||
|
||||
match fork_name {
|
||||
ForkName::Deneb => {
|
||||
let response: JsonGetPayloadResponseV3<T> = self
|
||||
.rpc_request(
|
||||
ENGINE_GET_PAYLOAD_V3,
|
||||
params,
|
||||
ENGINE_GET_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier,
|
||||
)
|
||||
.await?;
|
||||
Ok(JsonGetPayloadResponse::V3(response).into())
|
||||
}
|
||||
_ => Err(Error::UnsupportedForkVariant(format!(
|
||||
"called get_payload_v3 with {}",
|
||||
fork_name
|
||||
))),
|
||||
}
|
||||
@@ -884,6 +946,27 @@ impl HttpJsonRpc {
|
||||
Ok(response.into())
|
||||
}
|
||||
|
||||
pub async fn forkchoice_updated_v3(
|
||||
&self,
|
||||
forkchoice_state: ForkchoiceState,
|
||||
payload_attributes: Option<PayloadAttributes>,
|
||||
) -> Result<ForkchoiceUpdatedResponse, Error> {
|
||||
let params = json!([
|
||||
JsonForkchoiceStateV1::from(forkchoice_state),
|
||||
payload_attributes.map(JsonPayloadAttributes::from)
|
||||
]);
|
||||
|
||||
let response: JsonForkchoiceUpdatedV1Response = self
|
||||
.rpc_request(
|
||||
ENGINE_FORKCHOICE_UPDATED_V3,
|
||||
params,
|
||||
ENGINE_FORKCHOICE_UPDATED_TIMEOUT * self.execution_timeout_multiplier,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(response.into())
|
||||
}
|
||||
|
||||
pub async fn get_payload_bodies_by_hash_v1<E: EthSpec>(
|
||||
&self,
|
||||
block_hashes: Vec<ExecutionBlockHash>,
|
||||
@@ -950,14 +1033,17 @@ impl HttpJsonRpc {
|
||||
Ok(capabilities) => Ok(EngineCapabilities {
|
||||
new_payload_v1: capabilities.contains(ENGINE_NEW_PAYLOAD_V1),
|
||||
new_payload_v2: capabilities.contains(ENGINE_NEW_PAYLOAD_V2),
|
||||
new_payload_v3: capabilities.contains(ENGINE_NEW_PAYLOAD_V3),
|
||||
forkchoice_updated_v1: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V1),
|
||||
forkchoice_updated_v2: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V2),
|
||||
forkchoice_updated_v3: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V3),
|
||||
get_payload_bodies_by_hash_v1: capabilities
|
||||
.contains(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1),
|
||||
get_payload_bodies_by_range_v1: capabilities
|
||||
.contains(ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1),
|
||||
get_payload_v1: capabilities.contains(ENGINE_GET_PAYLOAD_V1),
|
||||
get_payload_v2: capabilities.contains(ENGINE_GET_PAYLOAD_V2),
|
||||
get_payload_v3: capabilities.contains(ENGINE_GET_PAYLOAD_V3),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -994,15 +1080,28 @@ impl HttpJsonRpc {
|
||||
// new_payload that the execution engine supports
|
||||
pub async fn new_payload<T: EthSpec>(
|
||||
&self,
|
||||
execution_payload: ExecutionPayload<T>,
|
||||
new_payload_request: NewPayloadRequest<T>,
|
||||
) -> Result<PayloadStatusV1, Error> {
|
||||
let engine_capabilities = self.get_engine_capabilities(None).await?;
|
||||
if engine_capabilities.new_payload_v2 {
|
||||
self.new_payload_v2(execution_payload).await
|
||||
} else if engine_capabilities.new_payload_v1 {
|
||||
self.new_payload_v1(execution_payload).await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported("engine_newPayload"))
|
||||
match new_payload_request {
|
||||
NewPayloadRequest::Merge(_) | NewPayloadRequest::Capella(_) => {
|
||||
if engine_capabilities.new_payload_v2 {
|
||||
self.new_payload_v2(new_payload_request.into_execution_payload())
|
||||
.await
|
||||
} else if engine_capabilities.new_payload_v1 {
|
||||
self.new_payload_v1(new_payload_request.into_execution_payload())
|
||||
.await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported("engine_newPayload"))
|
||||
}
|
||||
}
|
||||
NewPayloadRequest::Deneb(new_payload_request_deneb) => {
|
||||
if engine_capabilities.new_payload_v3 {
|
||||
self.new_payload_v3(new_payload_request_deneb).await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported("engine_newPayloadV3"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1014,12 +1113,27 @@ impl HttpJsonRpc {
|
||||
payload_id: PayloadId,
|
||||
) -> Result<GetPayloadResponse<T>, Error> {
|
||||
let engine_capabilities = self.get_engine_capabilities(None).await?;
|
||||
if engine_capabilities.get_payload_v2 {
|
||||
self.get_payload_v2(fork_name, payload_id).await
|
||||
} else if engine_capabilities.new_payload_v1 {
|
||||
self.get_payload_v1(payload_id).await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported("engine_getPayload"))
|
||||
match fork_name {
|
||||
ForkName::Merge | ForkName::Capella => {
|
||||
if engine_capabilities.get_payload_v2 {
|
||||
self.get_payload_v2(fork_name, payload_id).await
|
||||
} else if engine_capabilities.new_payload_v1 {
|
||||
self.get_payload_v1(payload_id).await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported("engine_getPayload"))
|
||||
}
|
||||
}
|
||||
ForkName::Deneb => {
|
||||
if engine_capabilities.get_payload_v3 {
|
||||
self.get_payload_v3(fork_name, payload_id).await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported("engine_getPayloadV3"))
|
||||
}
|
||||
}
|
||||
ForkName::Base | ForkName::Altair => Err(Error::UnsupportedForkVariant(format!(
|
||||
"called get_payload with {}",
|
||||
fork_name
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1028,14 +1142,41 @@ impl HttpJsonRpc {
|
||||
pub async fn forkchoice_updated(
|
||||
&self,
|
||||
forkchoice_state: ForkchoiceState,
|
||||
payload_attributes: Option<PayloadAttributes>,
|
||||
maybe_payload_attributes: Option<PayloadAttributes>,
|
||||
) -> Result<ForkchoiceUpdatedResponse, Error> {
|
||||
let engine_capabilities = self.get_engine_capabilities(None).await?;
|
||||
if engine_capabilities.forkchoice_updated_v2 {
|
||||
self.forkchoice_updated_v2(forkchoice_state, payload_attributes)
|
||||
if let Some(payload_attributes) = maybe_payload_attributes.as_ref() {
|
||||
match payload_attributes {
|
||||
PayloadAttributes::V1(_) | PayloadAttributes::V2(_) => {
|
||||
if engine_capabilities.forkchoice_updated_v2 {
|
||||
self.forkchoice_updated_v2(forkchoice_state, maybe_payload_attributes)
|
||||
.await
|
||||
} else if engine_capabilities.forkchoice_updated_v1 {
|
||||
self.forkchoice_updated_v1(forkchoice_state, maybe_payload_attributes)
|
||||
.await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported("engine_forkchoiceUpdated"))
|
||||
}
|
||||
}
|
||||
PayloadAttributes::V3(_) => {
|
||||
if engine_capabilities.forkchoice_updated_v3 {
|
||||
self.forkchoice_updated_v3(forkchoice_state, maybe_payload_attributes)
|
||||
.await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported(
|
||||
"engine_forkchoiceUpdatedV3",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if engine_capabilities.forkchoice_updated_v3 {
|
||||
self.forkchoice_updated_v3(forkchoice_state, maybe_payload_attributes)
|
||||
.await
|
||||
} else if engine_capabilities.forkchoice_updated_v2 {
|
||||
self.forkchoice_updated_v2(forkchoice_state, maybe_payload_attributes)
|
||||
.await
|
||||
} else if engine_capabilities.forkchoice_updated_v1 {
|
||||
self.forkchoice_updated_v1(forkchoice_state, payload_attributes)
|
||||
self.forkchoice_updated_v1(forkchoice_state, maybe_payload_attributes)
|
||||
.await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported("engine_forkchoiceUpdated"))
|
||||
|
||||
@@ -3,9 +3,11 @@ use serde::{Deserialize, Serialize};
|
||||
use ssz_types::FixedVector;
|
||||
use strum::EnumString;
|
||||
use superstruct::superstruct;
|
||||
use types::beacon_block_body::KzgCommitments;
|
||||
use types::blob_sidecar::BlobsList;
|
||||
use types::{
|
||||
EthSpec, ExecutionBlockHash, ExecutionPayloadCapella, ExecutionPayloadMerge, Transactions,
|
||||
Unsigned, VariableList, Withdrawal,
|
||||
EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadDeneb,
|
||||
ExecutionPayloadMerge, Transactions, Unsigned, VariableList, Withdrawal,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
@@ -62,7 +64,7 @@ pub struct JsonPayloadIdResponse {
|
||||
}
|
||||
|
||||
#[superstruct(
|
||||
variants(V1, V2),
|
||||
variants(V1, V2, V3),
|
||||
variant_attributes(
|
||||
derive(Debug, PartialEq, Default, Serialize, Deserialize,),
|
||||
serde(bound = "T: EthSpec", rename_all = "camelCase"),
|
||||
@@ -95,8 +97,14 @@ pub struct JsonExecutionPayload<T: EthSpec> {
|
||||
pub block_hash: ExecutionBlockHash,
|
||||
#[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")]
|
||||
pub transactions: Transactions<T>,
|
||||
#[superstruct(only(V2))]
|
||||
#[superstruct(only(V2, V3))]
|
||||
pub withdrawals: VariableList<JsonWithdrawal, T::MaxWithdrawalsPerPayload>,
|
||||
#[superstruct(only(V3))]
|
||||
#[serde(with = "serde_utils::u64_hex_be")]
|
||||
pub blob_gas_used: u64,
|
||||
#[superstruct(only(V3))]
|
||||
#[serde(with = "serde_utils::u64_hex_be")]
|
||||
pub excess_blob_gas: u64,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<ExecutionPayloadMerge<T>> for JsonExecutionPayloadV1<T> {
|
||||
@@ -145,12 +153,41 @@ impl<T: EthSpec> From<ExecutionPayloadCapella<T>> for JsonExecutionPayloadV2<T>
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: EthSpec> From<ExecutionPayloadDeneb<T>> for JsonExecutionPayloadV3<T> {
|
||||
fn from(payload: ExecutionPayloadDeneb<T>) -> Self {
|
||||
JsonExecutionPayloadV3 {
|
||||
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,
|
||||
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,
|
||||
base_fee_per_gas: payload.base_fee_per_gas,
|
||||
block_hash: payload.block_hash,
|
||||
transactions: payload.transactions,
|
||||
withdrawals: payload
|
||||
.withdrawals
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
blob_gas_used: payload.blob_gas_used,
|
||||
excess_blob_gas: payload.excess_blob_gas,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<ExecutionPayload<T>> for JsonExecutionPayload<T> {
|
||||
fn from(execution_payload: ExecutionPayload<T>) -> Self {
|
||||
match execution_payload {
|
||||
ExecutionPayload::Merge(payload) => JsonExecutionPayload::V1(payload.into()),
|
||||
ExecutionPayload::Capella(payload) => JsonExecutionPayload::V2(payload.into()),
|
||||
ExecutionPayload::Deneb(payload) => JsonExecutionPayload::V3(payload.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,18 +238,47 @@ impl<T: EthSpec> From<JsonExecutionPayloadV2<T>> for ExecutionPayloadCapella<T>
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: EthSpec> From<JsonExecutionPayloadV3<T>> for ExecutionPayloadDeneb<T> {
|
||||
fn from(payload: JsonExecutionPayloadV3<T>) -> Self {
|
||||
ExecutionPayloadDeneb {
|
||||
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,
|
||||
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,
|
||||
base_fee_per_gas: payload.base_fee_per_gas,
|
||||
block_hash: payload.block_hash,
|
||||
transactions: payload.transactions,
|
||||
withdrawals: payload
|
||||
.withdrawals
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
blob_gas_used: payload.blob_gas_used,
|
||||
excess_blob_gas: payload.excess_blob_gas,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<JsonExecutionPayload<T>> for ExecutionPayload<T> {
|
||||
fn from(json_execution_payload: JsonExecutionPayload<T>) -> Self {
|
||||
match json_execution_payload {
|
||||
JsonExecutionPayload::V1(payload) => ExecutionPayload::Merge(payload.into()),
|
||||
JsonExecutionPayload::V2(payload) => ExecutionPayload::Capella(payload.into()),
|
||||
JsonExecutionPayload::V3(payload) => ExecutionPayload::Deneb(payload.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[superstruct(
|
||||
variants(V1, V2),
|
||||
variants(V1, V2, V3),
|
||||
variant_attributes(
|
||||
derive(Debug, PartialEq, Serialize, Deserialize),
|
||||
serde(bound = "T: EthSpec", rename_all = "camelCase")
|
||||
@@ -227,8 +293,14 @@ pub struct JsonGetPayloadResponse<T: EthSpec> {
|
||||
pub execution_payload: JsonExecutionPayloadV1<T>,
|
||||
#[superstruct(only(V2), partial_getter(rename = "execution_payload_v2"))]
|
||||
pub execution_payload: JsonExecutionPayloadV2<T>,
|
||||
#[superstruct(only(V3), partial_getter(rename = "execution_payload_v3"))]
|
||||
pub execution_payload: JsonExecutionPayloadV3<T>,
|
||||
#[serde(with = "serde_utils::u256_hex_be")]
|
||||
pub block_value: Uint256,
|
||||
#[superstruct(only(V3))]
|
||||
pub blobs_bundle: JsonBlobsBundleV1<T>,
|
||||
#[superstruct(only(V3))]
|
||||
pub should_override_builder: bool,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<JsonGetPayloadResponse<T>> for GetPayloadResponse<T> {
|
||||
@@ -246,6 +318,14 @@ impl<T: EthSpec> From<JsonGetPayloadResponse<T>> for GetPayloadResponse<T> {
|
||||
block_value: response.block_value,
|
||||
})
|
||||
}
|
||||
JsonGetPayloadResponse::V3(response) => {
|
||||
GetPayloadResponse::Deneb(GetPayloadResponseDeneb {
|
||||
execution_payload: response.execution_payload.into(),
|
||||
block_value: response.block_value,
|
||||
blobs_bundle: response.blobs_bundle.into(),
|
||||
should_override_builder: response.should_override_builder,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,7 +365,7 @@ impl From<JsonWithdrawal> for Withdrawal {
|
||||
}
|
||||
|
||||
#[superstruct(
|
||||
variants(V1, V2),
|
||||
variants(V1, V2, V3),
|
||||
variant_attributes(
|
||||
derive(Debug, Clone, PartialEq, Serialize, Deserialize),
|
||||
serde(rename_all = "camelCase")
|
||||
@@ -300,8 +380,10 @@ pub struct JsonPayloadAttributes {
|
||||
pub timestamp: u64,
|
||||
pub prev_randao: Hash256,
|
||||
pub suggested_fee_recipient: Address,
|
||||
#[superstruct(only(V2))]
|
||||
#[superstruct(only(V2, V3))]
|
||||
pub withdrawals: Vec<JsonWithdrawal>,
|
||||
#[superstruct(only(V3))]
|
||||
pub parent_beacon_block_root: Hash256,
|
||||
}
|
||||
|
||||
impl From<PayloadAttributes> for JsonPayloadAttributes {
|
||||
@@ -318,6 +400,13 @@ impl From<PayloadAttributes> for JsonPayloadAttributes {
|
||||
suggested_fee_recipient: pa.suggested_fee_recipient,
|
||||
withdrawals: pa.withdrawals.into_iter().map(Into::into).collect(),
|
||||
}),
|
||||
PayloadAttributes::V3(pa) => Self::V3(JsonPayloadAttributesV3 {
|
||||
timestamp: pa.timestamp,
|
||||
prev_randao: pa.prev_randao,
|
||||
suggested_fee_recipient: pa.suggested_fee_recipient,
|
||||
withdrawals: pa.withdrawals.into_iter().map(Into::into).collect(),
|
||||
parent_beacon_block_root: pa.parent_beacon_block_root,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,6 +425,41 @@ impl From<JsonPayloadAttributes> for PayloadAttributes {
|
||||
suggested_fee_recipient: jpa.suggested_fee_recipient,
|
||||
withdrawals: jpa.withdrawals.into_iter().map(Into::into).collect(),
|
||||
}),
|
||||
JsonPayloadAttributes::V3(jpa) => Self::V3(PayloadAttributesV3 {
|
||||
timestamp: jpa.timestamp,
|
||||
prev_randao: jpa.prev_randao,
|
||||
suggested_fee_recipient: jpa.suggested_fee_recipient,
|
||||
withdrawals: jpa.withdrawals.into_iter().map(Into::into).collect(),
|
||||
parent_beacon_block_root: jpa.parent_beacon_block_root,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(bound = "E: EthSpec", rename_all = "camelCase")]
|
||||
pub struct JsonBlobsBundleV1<E: EthSpec> {
|
||||
pub commitments: KzgCommitments<E>,
|
||||
pub proofs: KzgProofs<E>,
|
||||
#[serde(with = "ssz_types::serde_utils::list_of_hex_fixed_vec")]
|
||||
pub blobs: BlobsList<E>,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<BlobsBundle<E>> for JsonBlobsBundleV1<E> {
|
||||
fn from(blobs_bundle: BlobsBundle<E>) -> Self {
|
||||
Self {
|
||||
commitments: blobs_bundle.commitments,
|
||||
proofs: blobs_bundle.proofs,
|
||||
blobs: blobs_bundle.blobs,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<E: EthSpec> From<JsonBlobsBundleV1<E>> for BlobsBundle<E> {
|
||||
fn from(json_blobs_bundle: JsonBlobsBundleV1<E>) -> Self {
|
||||
Self {
|
||||
commitments: json_blobs_bundle.commitments,
|
||||
proofs: json_blobs_bundle.proofs,
|
||||
blobs: json_blobs_bundle.blobs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ pub use engine_api::*;
|
||||
pub use engine_api::{http, http::deposit_methods, http::HttpJsonRpc};
|
||||
use engines::{Engine, EngineError};
|
||||
pub use engines::{EngineState, ForkchoiceState};
|
||||
use eth2::types::builder_bid::SignedBuilderBid;
|
||||
use eth2::types::{builder_bid::SignedBuilderBid, BlobsBundle, ForkVersionedResponse};
|
||||
use eth2::types::{FullPayloadContents, SignedBlockContents};
|
||||
use ethers_core::types::Transaction as EthersTransaction;
|
||||
use fork_choice::ForkchoiceUpdateParameters;
|
||||
use lru::LruCache;
|
||||
use payload_status::process_payload_status;
|
||||
@@ -27,7 +29,6 @@ use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::io::Write;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
@@ -39,12 +40,15 @@ use tokio::{
|
||||
};
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
use tree_hash::TreeHash;
|
||||
use types::{AbstractExecPayload, BeaconStateError, ExecPayload};
|
||||
use types::beacon_block_body::KzgCommitments;
|
||||
use types::builder_bid::BuilderBid;
|
||||
use types::sidecar::{BlobItems, Sidecar};
|
||||
use types::KzgProofs;
|
||||
use types::{
|
||||
BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionPayloadCapella, ExecutionPayloadMerge,
|
||||
ForkVersionedResponse, ProposerPreparationData, PublicKeyBytes, Signature, SignedBeaconBlock,
|
||||
Slot,
|
||||
AbstractExecPayload, BeaconStateError, BlindedPayload, BlockType, ChainSpec, Epoch,
|
||||
ExecPayload, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge,
|
||||
};
|
||||
use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot, Transaction};
|
||||
|
||||
mod block_hash;
|
||||
mod engine_api;
|
||||
@@ -83,6 +87,40 @@ pub enum ProvenancedPayload<P> {
|
||||
Builder(P),
|
||||
}
|
||||
|
||||
impl<E: EthSpec, Payload: AbstractExecPayload<E>> TryFrom<BuilderBid<E>>
|
||||
for ProvenancedPayload<BlockProposalContents<E, Payload>>
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: BuilderBid<E>) -> Result<Self, Error> {
|
||||
let block_proposal_contents = match value {
|
||||
BuilderBid::Merge(builder_bid) => BlockProposalContents::Payload {
|
||||
payload: ExecutionPayloadHeader::Merge(builder_bid.header)
|
||||
.try_into()
|
||||
.map_err(|_| Error::InvalidPayloadConversion)?,
|
||||
block_value: builder_bid.value,
|
||||
},
|
||||
BuilderBid::Capella(builder_bid) => BlockProposalContents::Payload {
|
||||
payload: ExecutionPayloadHeader::Capella(builder_bid.header)
|
||||
.try_into()
|
||||
.map_err(|_| Error::InvalidPayloadConversion)?,
|
||||
block_value: builder_bid.value,
|
||||
},
|
||||
BuilderBid::Deneb(builder_bid) => BlockProposalContents::PayloadAndBlobs {
|
||||
payload: ExecutionPayloadHeader::Deneb(builder_bid.header)
|
||||
.try_into()
|
||||
.map_err(|_| Error::InvalidPayloadConversion)?,
|
||||
block_value: builder_bid.value,
|
||||
kzg_commitments: builder_bid.blinded_blobs_bundle.commitments,
|
||||
blobs: BlobItems::try_from_blob_roots(builder_bid.blinded_blobs_bundle.blob_roots)
|
||||
.map_err(Error::InvalidBlobConversion)?,
|
||||
proofs: builder_bid.blinded_blobs_bundle.proofs,
|
||||
},
|
||||
};
|
||||
Ok(ProvenancedPayload::Builder(block_proposal_contents))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
NoEngine,
|
||||
@@ -104,6 +142,8 @@ pub enum Error {
|
||||
InvalidJWTSecret(String),
|
||||
InvalidForkForPayload,
|
||||
InvalidPayloadBody(String),
|
||||
InvalidPayloadConversion,
|
||||
InvalidBlobConversion(String),
|
||||
BeaconStateError(BeaconStateError),
|
||||
}
|
||||
|
||||
@@ -123,37 +163,81 @@ pub enum BlockProposalContents<T: EthSpec, Payload: AbstractExecPayload<T>> {
|
||||
Payload {
|
||||
payload: Payload,
|
||||
block_value: Uint256,
|
||||
// TODO: remove for 4844, since it appears in PayloadAndBlobs
|
||||
_phantom: PhantomData<T>,
|
||||
},
|
||||
PayloadAndBlobs {
|
||||
payload: Payload,
|
||||
block_value: Uint256,
|
||||
kzg_commitments: KzgCommitments<T>,
|
||||
blobs: <Payload::Sidecar as Sidecar<T>>::BlobItems,
|
||||
proofs: KzgProofs<T>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<E: EthSpec, Payload: AbstractExecPayload<E>> TryFrom<GetPayloadResponse<E>>
|
||||
for BlockProposalContents<E, Payload>
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(response: GetPayloadResponse<E>) -> Result<Self, Error> {
|
||||
let (execution_payload, block_value, maybe_bundle) = response.into();
|
||||
match maybe_bundle {
|
||||
Some(bundle) => Ok(Self::PayloadAndBlobs {
|
||||
payload: execution_payload.into(),
|
||||
block_value,
|
||||
kzg_commitments: bundle.commitments,
|
||||
blobs: BlobItems::try_from_blobs(bundle.blobs)
|
||||
.map_err(Error::InvalidBlobConversion)?,
|
||||
proofs: bundle.proofs,
|
||||
}),
|
||||
None => Ok(Self::Payload {
|
||||
payload: execution_payload.into(),
|
||||
block_value,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Payload> {
|
||||
pub fn payload(&self) -> &Payload {
|
||||
pub fn deconstruct(
|
||||
self,
|
||||
) -> (
|
||||
Payload,
|
||||
Option<KzgCommitments<T>>,
|
||||
Option<<Payload::Sidecar as Sidecar<T>>::BlobItems>,
|
||||
Option<KzgProofs<T>>,
|
||||
) {
|
||||
match self {
|
||||
Self::Payload {
|
||||
payload,
|
||||
block_value: _,
|
||||
_phantom: _,
|
||||
} => payload,
|
||||
} => (payload, None, None, None),
|
||||
Self::PayloadAndBlobs {
|
||||
payload,
|
||||
block_value: _,
|
||||
kzg_commitments,
|
||||
blobs,
|
||||
proofs,
|
||||
} => (payload, Some(kzg_commitments), Some(blobs), Some(proofs)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn payload(&self) -> &Payload {
|
||||
match self {
|
||||
Self::Payload { payload, .. } => payload,
|
||||
Self::PayloadAndBlobs { payload, .. } => payload,
|
||||
}
|
||||
}
|
||||
pub fn to_payload(self) -> Payload {
|
||||
match self {
|
||||
Self::Payload {
|
||||
payload,
|
||||
block_value: _,
|
||||
_phantom: _,
|
||||
} => payload,
|
||||
Self::Payload { payload, .. } => payload,
|
||||
Self::PayloadAndBlobs { payload, .. } => payload,
|
||||
}
|
||||
}
|
||||
pub fn block_value(&self) -> &Uint256 {
|
||||
match self {
|
||||
Self::Payload {
|
||||
payload: _,
|
||||
block_value,
|
||||
_phantom: _,
|
||||
} => block_value,
|
||||
Self::Payload { block_value, .. } => block_value,
|
||||
Self::PayloadAndBlobs { block_value, .. } => block_value,
|
||||
}
|
||||
}
|
||||
pub fn default_at_fork(fork_name: ForkName) -> Result<Self, BeaconStateError> {
|
||||
@@ -162,9 +246,15 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
|
||||
BlockProposalContents::Payload {
|
||||
payload: Payload::default_at_fork(fork_name)?,
|
||||
block_value: Uint256::zero(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
ForkName::Deneb => BlockProposalContents::PayloadAndBlobs {
|
||||
payload: Payload::default_at_fork(fork_name)?,
|
||||
block_value: Uint256::zero(),
|
||||
blobs: Payload::default_blobs_at_fork(fork_name)?,
|
||||
kzg_commitments: VariableList::default(),
|
||||
proofs: VariableList::default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -208,6 +298,8 @@ pub enum FailedCondition {
|
||||
EpochsSinceFinalization,
|
||||
}
|
||||
|
||||
type PayloadContentsRefTuple<'a, T> = (ExecutionPayloadRef<'a, T>, Option<&'a BlobsBundle<T>>);
|
||||
|
||||
struct Inner<E: EthSpec> {
|
||||
engine: Arc<Engine>,
|
||||
builder: ArcSwapOption<BuilderHttpClient>,
|
||||
@@ -221,6 +313,7 @@ struct Inner<E: EthSpec> {
|
||||
builder_profit_threshold: Uint256,
|
||||
log: Logger,
|
||||
always_prefer_builder_payload: bool,
|
||||
ignore_builder_override_suggestion_threshold: f32,
|
||||
/// Track whether the last `newPayload` call errored.
|
||||
///
|
||||
/// This is used *only* in the informational sync status endpoint, so that a VC using this
|
||||
@@ -251,6 +344,7 @@ pub struct Config {
|
||||
pub builder_profit_threshold: u128,
|
||||
pub execution_timeout_multiplier: Option<u32>,
|
||||
pub always_prefer_builder_payload: bool,
|
||||
pub ignore_builder_override_suggestion_threshold: f32,
|
||||
}
|
||||
|
||||
/// Provides access to one execution engine and provides a neat interface for consumption by the
|
||||
@@ -260,6 +354,40 @@ pub struct ExecutionLayer<T: EthSpec> {
|
||||
inner: Arc<Inner<T>>,
|
||||
}
|
||||
|
||||
/// This function will return the percentage difference between 2 U256 values, using `base_value`
|
||||
/// as the denominator. It is accurate to 7 decimal places which is about the precision of
|
||||
/// an f32.
|
||||
///
|
||||
/// If some error is encountered in the calculation, None will be returned.
|
||||
fn percentage_difference_u256(base_value: Uint256, comparison_value: Uint256) -> Option<f32> {
|
||||
if base_value == Uint256::zero() {
|
||||
return None;
|
||||
}
|
||||
// this is the total supply of ETH in WEI
|
||||
let max_value = Uint256::from(12u8) * Uint256::exp10(25);
|
||||
if base_value > max_value || comparison_value > max_value {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Now we should be able to calculate the difference without division by zero or overflow
|
||||
const PRECISION: usize = 7;
|
||||
let precision_factor = Uint256::exp10(PRECISION);
|
||||
let scaled_difference = if base_value <= comparison_value {
|
||||
(comparison_value - base_value) * precision_factor
|
||||
} else {
|
||||
(base_value - comparison_value) * precision_factor
|
||||
};
|
||||
let scaled_proportion = scaled_difference / base_value;
|
||||
// max value of scaled difference is 1.2 * 10^33, well below the max value of a u128 / f64 / f32
|
||||
let percentage =
|
||||
100.0f64 * scaled_proportion.low_u128() as f64 / precision_factor.low_u128() as f64;
|
||||
if base_value <= comparison_value {
|
||||
Some(percentage as f32)
|
||||
} else {
|
||||
Some(-percentage as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> ExecutionLayer<T> {
|
||||
/// Instantiate `Self` with an Execution engine specified in `Config`, using JSON-RPC via HTTP.
|
||||
pub fn from_config(config: Config, executor: TaskExecutor, log: Logger) -> Result<Self, Error> {
|
||||
@@ -275,6 +403,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
builder_profit_threshold,
|
||||
execution_timeout_multiplier,
|
||||
always_prefer_builder_payload,
|
||||
ignore_builder_override_suggestion_threshold,
|
||||
} = config;
|
||||
|
||||
if urls.len() > 1 {
|
||||
@@ -338,6 +467,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
builder_profit_threshold: Uint256::from(builder_profit_threshold),
|
||||
log,
|
||||
always_prefer_builder_payload,
|
||||
ignore_builder_override_suggestion_threshold,
|
||||
last_new_payload_errored: RwLock::new(false),
|
||||
};
|
||||
|
||||
@@ -383,12 +513,28 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
}
|
||||
|
||||
/// Cache a full payload, keyed on the `tree_hash_root` of the payload
|
||||
fn cache_payload(&self, payload: ExecutionPayloadRef<T>) -> Option<ExecutionPayload<T>> {
|
||||
self.inner.payload_cache.put(payload.clone_from_ref())
|
||||
fn cache_payload(
|
||||
&self,
|
||||
payload_and_blobs: PayloadContentsRefTuple<T>,
|
||||
) -> Option<FullPayloadContents<T>> {
|
||||
let (payload_ref, maybe_json_blobs_bundle) = payload_and_blobs;
|
||||
|
||||
let payload = payload_ref.clone_from_ref();
|
||||
let maybe_blobs_bundle = maybe_json_blobs_bundle
|
||||
.cloned()
|
||||
.map(|blobs_bundle| BlobsBundle {
|
||||
commitments: blobs_bundle.commitments,
|
||||
proofs: blobs_bundle.proofs,
|
||||
blobs: blobs_bundle.blobs,
|
||||
});
|
||||
|
||||
self.inner
|
||||
.payload_cache
|
||||
.put(FullPayloadContents::new(payload, maybe_blobs_bundle))
|
||||
}
|
||||
|
||||
/// Attempt to retrieve a full payload from the payload cache by the payload root
|
||||
pub fn get_payload_by_root(&self, root: &Hash256) -> Option<ExecutionPayload<T>> {
|
||||
pub fn get_payload_by_root(&self, root: &Hash256) -> Option<FullPayloadContents<T>> {
|
||||
self.inner.payload_cache.get(root)
|
||||
}
|
||||
|
||||
@@ -686,6 +832,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
current_fork,
|
||||
)
|
||||
.await
|
||||
.and_then(GetPayloadResponse::try_into)
|
||||
.map(ProvenancedPayload::Local)
|
||||
}
|
||||
};
|
||||
@@ -751,11 +898,11 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
let ((relay_result, relay_duration), (local_result, local_duration)) = tokio::join!(
|
||||
timed_future(metrics::GET_BLINDED_PAYLOAD_BUILDER, async {
|
||||
builder
|
||||
.get_builder_header::<T, Payload>(slot, parent_hash, &pubkey)
|
||||
.get_builder_header::<T>(slot, parent_hash, &pubkey)
|
||||
.await
|
||||
}),
|
||||
timed_future(metrics::GET_BLINDED_PAYLOAD_LOCAL, async {
|
||||
self.get_full_payload_caching::<Payload>(
|
||||
self.get_full_payload_caching(
|
||||
parent_hash,
|
||||
payload_attributes,
|
||||
forkchoice_update_params,
|
||||
@@ -769,13 +916,13 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
self.log(),
|
||||
"Requested blinded execution payload";
|
||||
"relay_fee_recipient" => match &relay_result {
|
||||
Ok(Some(r)) => format!("{:?}", r.data.message.header.fee_recipient()),
|
||||
Ok(Some(r)) => format!("{:?}", r.data.message.header().fee_recipient()),
|
||||
Ok(None) => "empty response".to_string(),
|
||||
Err(_) => "request failed".to_string(),
|
||||
},
|
||||
"relay_response_ms" => relay_duration.as_millis(),
|
||||
"local_fee_recipient" => match &local_result {
|
||||
Ok(proposal_contents) => format!("{:?}", proposal_contents.payload().fee_recipient()),
|
||||
Ok(get_payload_response) => format!("{:?}", get_payload_response.fee_recipient()),
|
||||
Err(_) => "request failed".to_string()
|
||||
},
|
||||
"local_response_ms" => local_duration.as_millis(),
|
||||
@@ -789,43 +936,61 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
"Builder error when requesting payload";
|
||||
"info" => "falling back to local execution client",
|
||||
"relay_error" => ?e,
|
||||
"local_block_hash" => ?local.payload().block_hash(),
|
||||
"local_block_hash" => ?local.block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
Ok(ProvenancedPayload::Local(local))
|
||||
Ok(ProvenancedPayload::Local(local.try_into()?))
|
||||
}
|
||||
(Ok(None), Ok(local)) => {
|
||||
info!(
|
||||
self.log(),
|
||||
"Builder did not return a payload";
|
||||
"info" => "falling back to local execution client",
|
||||
"local_block_hash" => ?local.payload().block_hash(),
|
||||
"local_block_hash" => ?local.block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
Ok(ProvenancedPayload::Local(local))
|
||||
Ok(ProvenancedPayload::Local(local.try_into()?))
|
||||
}
|
||||
(Ok(Some(relay)), Ok(local)) => {
|
||||
let header = &relay.data.message.header;
|
||||
let header = &relay.data.message.header();
|
||||
|
||||
info!(
|
||||
self.log(),
|
||||
"Received local and builder payloads";
|
||||
"relay_block_hash" => ?header.block_hash(),
|
||||
"local_block_hash" => ?local.payload().block_hash(),
|
||||
"local_block_hash" => ?local.block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
|
||||
let relay_value = relay.data.message.value;
|
||||
let relay_value = relay.data.message.value();
|
||||
let local_value = *local.block_value();
|
||||
|
||||
if !self.inner.always_prefer_builder_payload {
|
||||
if local_value >= relay_value {
|
||||
if local_value >= *relay_value {
|
||||
info!(
|
||||
self.log(),
|
||||
"Local block is more profitable than relay block";
|
||||
"local_block_value" => %local_value,
|
||||
"relay_value" => %relay_value
|
||||
);
|
||||
return Ok(ProvenancedPayload::Local(local));
|
||||
return Ok(ProvenancedPayload::Local(local.try_into()?));
|
||||
} else if local.should_override_builder().unwrap_or(false) {
|
||||
let percentage_difference =
|
||||
percentage_difference_u256(local_value, *relay_value);
|
||||
if percentage_difference.map_or(false, |percentage| {
|
||||
percentage
|
||||
< self
|
||||
.inner
|
||||
.ignore_builder_override_suggestion_threshold
|
||||
}) {
|
||||
info!(
|
||||
self.log(),
|
||||
"Using local payload because execution engine suggested we ignore builder payload";
|
||||
"local_block_value" => %local_value,
|
||||
"relay_value" => %relay_value
|
||||
);
|
||||
return Ok(ProvenancedPayload::Local(local.try_into()?));
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
self.log(),
|
||||
@@ -840,18 +1005,12 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
&relay,
|
||||
parent_hash,
|
||||
payload_attributes,
|
||||
Some(local.payload().block_number()),
|
||||
Some(local.block_number()),
|
||||
self.inner.builder_profit_threshold,
|
||||
current_fork,
|
||||
spec,
|
||||
) {
|
||||
Ok(()) => Ok(ProvenancedPayload::Builder(
|
||||
BlockProposalContents::Payload {
|
||||
payload: relay.data.message.header,
|
||||
block_value: relay.data.message.value,
|
||||
_phantom: PhantomData,
|
||||
},
|
||||
)),
|
||||
Ok(()) => Ok(ProvenancedPayload::try_from(relay.data.message)?),
|
||||
Err(reason) if !reason.payload_invalid() => {
|
||||
info!(
|
||||
self.log(),
|
||||
@@ -861,7 +1020,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
"relay_block_hash" => ?header.block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
Ok(ProvenancedPayload::Local(local))
|
||||
Ok(ProvenancedPayload::Local(local.try_into()?))
|
||||
}
|
||||
Err(reason) => {
|
||||
metrics::inc_counter_vec(
|
||||
@@ -876,12 +1035,12 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
"relay_block_hash" => ?header.block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
Ok(ProvenancedPayload::Local(local))
|
||||
Ok(ProvenancedPayload::Local(local.try_into()?))
|
||||
}
|
||||
}
|
||||
}
|
||||
(Ok(Some(relay)), Err(local_error)) => {
|
||||
let header = &relay.data.message.header;
|
||||
let header = &relay.data.message.header();
|
||||
|
||||
info!(
|
||||
self.log(),
|
||||
@@ -900,22 +1059,12 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
current_fork,
|
||||
spec,
|
||||
) {
|
||||
Ok(()) => Ok(ProvenancedPayload::Builder(
|
||||
BlockProposalContents::Payload {
|
||||
payload: relay.data.message.header,
|
||||
block_value: relay.data.message.value,
|
||||
_phantom: PhantomData,
|
||||
},
|
||||
)),
|
||||
Ok(()) => Ok(ProvenancedPayload::try_from(relay.data.message)?),
|
||||
// If the payload is valid then use it. The local EE failed
|
||||
// to produce a payload so we have no alternative.
|
||||
Err(e) if !e.payload_invalid() => Ok(ProvenancedPayload::Builder(
|
||||
BlockProposalContents::Payload {
|
||||
payload: relay.data.message.header,
|
||||
block_value: relay.data.message.value,
|
||||
_phantom: PhantomData,
|
||||
},
|
||||
)),
|
||||
Err(e) if !e.payload_invalid() => {
|
||||
Ok(ProvenancedPayload::try_from(relay.data.message)?)
|
||||
}
|
||||
Err(reason) => {
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS,
|
||||
@@ -983,17 +1132,18 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
current_fork,
|
||||
)
|
||||
.await
|
||||
.and_then(GetPayloadResponse::try_into)
|
||||
.map(ProvenancedPayload::Local)
|
||||
}
|
||||
|
||||
/// Get a full payload without caching its result in the execution layer's payload cache.
|
||||
async fn get_full_payload<Payload: AbstractExecPayload<T>>(
|
||||
async fn get_full_payload(
|
||||
&self,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
forkchoice_update_params: ForkchoiceUpdateParameters,
|
||||
current_fork: ForkName,
|
||||
) -> Result<BlockProposalContents<T, Payload>, Error> {
|
||||
) -> Result<GetPayloadResponse<T>, Error> {
|
||||
self.get_full_payload_with(
|
||||
parent_hash,
|
||||
payload_attributes,
|
||||
@@ -1005,13 +1155,13 @@ 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: AbstractExecPayload<T>>(
|
||||
async fn get_full_payload_caching(
|
||||
&self,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
forkchoice_update_params: ForkchoiceUpdateParameters,
|
||||
current_fork: ForkName,
|
||||
) -> Result<BlockProposalContents<T, Payload>, Error> {
|
||||
) -> Result<GetPayloadResponse<T>, Error> {
|
||||
self.get_full_payload_with(
|
||||
parent_hash,
|
||||
payload_attributes,
|
||||
@@ -1022,14 +1172,17 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_full_payload_with<Payload: AbstractExecPayload<T>>(
|
||||
async fn get_full_payload_with(
|
||||
&self,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
forkchoice_update_params: ForkchoiceUpdateParameters,
|
||||
current_fork: ForkName,
|
||||
f: fn(&ExecutionLayer<T>, ExecutionPayloadRef<T>) -> Option<ExecutionPayload<T>>,
|
||||
) -> Result<BlockProposalContents<T, Payload>, Error> {
|
||||
cache_fn: fn(
|
||||
&ExecutionLayer<T>,
|
||||
PayloadContentsRefTuple<T>,
|
||||
) -> Option<FullPayloadContents<T>>,
|
||||
) -> Result<GetPayloadResponse<T>, Error> {
|
||||
self.engine()
|
||||
.request(move |engine| async move {
|
||||
let payload_id = if let Some(id) = engine
|
||||
@@ -1082,7 +1235,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
}
|
||||
};
|
||||
|
||||
let payload_fut = async {
|
||||
let payload_response = async {
|
||||
debug!(
|
||||
self.log(),
|
||||
"Issuing engine_getPayload";
|
||||
@@ -1092,36 +1245,30 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
engine.api.get_payload::<T>(current_fork, payload_id).await
|
||||
};
|
||||
let payload_response = payload_fut.await;
|
||||
let (execution_payload, block_value) = payload_response.map(|payload_response| {
|
||||
if payload_response.execution_payload_ref().fee_recipient() != payload_attributes.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" => ?payload_response.execution_payload_ref().fee_recipient(),
|
||||
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
|
||||
);
|
||||
}
|
||||
if f(self, payload_response.execution_payload_ref()).is_some() {
|
||||
warn!(
|
||||
self.log(),
|
||||
"Duplicate payload cached, this might indicate redundant proposal \
|
||||
attempts."
|
||||
);
|
||||
}
|
||||
payload_response.into()
|
||||
})?;
|
||||
Ok(BlockProposalContents::Payload {
|
||||
payload: execution_payload.into(),
|
||||
block_value,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}.await?;
|
||||
|
||||
if payload_response.execution_payload_ref().fee_recipient() != payload_attributes.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" => ?payload_response.execution_payload_ref().fee_recipient(),
|
||||
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
|
||||
);
|
||||
}
|
||||
if cache_fn(self, (payload_response.execution_payload_ref(), payload_response.blobs_bundle().ok())).is_some() {
|
||||
warn!(
|
||||
self.log(),
|
||||
"Duplicate payload cached, this might indicate redundant proposal \
|
||||
attempts."
|
||||
);
|
||||
}
|
||||
|
||||
Ok(payload_response)
|
||||
})
|
||||
.await
|
||||
.map_err(Box::new)
|
||||
@@ -1131,24 +1278,25 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
/// Maps to the `engine_newPayload` JSON-RPC call.
|
||||
pub async fn notify_new_payload(
|
||||
&self,
|
||||
execution_payload: &ExecutionPayload<T>,
|
||||
new_payload_request: NewPayloadRequest<T>,
|
||||
) -> Result<PayloadStatus, Error> {
|
||||
let _timer = metrics::start_timer_vec(
|
||||
&metrics::EXECUTION_LAYER_REQUEST_TIMES,
|
||||
&[metrics::NEW_PAYLOAD],
|
||||
);
|
||||
|
||||
let block_hash = new_payload_request.block_hash();
|
||||
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" => ?new_payload_request.parent_hash(),
|
||||
"block_hash" => ?block_hash,
|
||||
"block_number" => ?new_payload_request.block_number(),
|
||||
);
|
||||
|
||||
let result = self
|
||||
.engine()
|
||||
.request(|engine| engine.api.new_payload(execution_payload.clone()))
|
||||
.request(|engine| engine.api.new_payload(new_payload_request))
|
||||
.await;
|
||||
|
||||
if let Ok(status) = &result {
|
||||
@@ -1159,7 +1307,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
}
|
||||
*self.inner.last_new_payload_errored.write().await = result.is_err();
|
||||
|
||||
process_payload_status(execution_payload.block_hash(), result, self.log())
|
||||
process_payload_status(block_hash, result, self.log())
|
||||
.map_err(Box::new)
|
||||
.map_err(Error::EngineError)
|
||||
}
|
||||
@@ -1576,6 +1724,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
let payload = match fork {
|
||||
ForkName::Merge => ExecutionPayloadMerge::default().into(),
|
||||
ForkName::Capella => ExecutionPayloadCapella::default().into(),
|
||||
ForkName::Deneb => ExecutionPayloadDeneb::default().into(),
|
||||
ForkName::Base | ForkName::Altair => {
|
||||
return Err(Error::InvalidForkForPayload);
|
||||
}
|
||||
@@ -1643,6 +1792,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
return match fork {
|
||||
ForkName::Merge => Ok(Some(ExecutionPayloadMerge::default().into())),
|
||||
ForkName::Capella => Ok(Some(ExecutionPayloadCapella::default().into())),
|
||||
ForkName::Deneb => Ok(Some(ExecutionPayloadDeneb::default().into())),
|
||||
ForkName::Base | ForkName::Altair => Err(ApiError::UnsupportedForkVariant(
|
||||
format!("called get_payload_by_hash_from_engine with {}", fork),
|
||||
)),
|
||||
@@ -1659,15 +1809,15 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let transactions = VariableList::new(
|
||||
block
|
||||
.transactions()
|
||||
.iter()
|
||||
.map(|transaction| VariableList::new(transaction.rlp().to_vec()))
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(ApiError::DeserializeTransaction)?,
|
||||
)
|
||||
.map_err(ApiError::DeserializeTransactions)?;
|
||||
let convert_transactions = |transactions: Vec<EthersTransaction>| {
|
||||
VariableList::new(
|
||||
transactions
|
||||
.into_iter()
|
||||
.map(|tx| VariableList::new(tx.rlp().to_vec()))
|
||||
.collect::<Result<Vec<_>, ssz_types::Error>>()?,
|
||||
)
|
||||
.map_err(ApiError::SszError)
|
||||
};
|
||||
|
||||
let payload = match block {
|
||||
ExecutionBlockWithTransactions::Merge(merge_block) => {
|
||||
@@ -1685,7 +1835,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
extra_data: merge_block.extra_data,
|
||||
base_fee_per_gas: merge_block.base_fee_per_gas,
|
||||
block_hash: merge_block.block_hash,
|
||||
transactions,
|
||||
transactions: convert_transactions(merge_block.transactions)?,
|
||||
})
|
||||
}
|
||||
ExecutionBlockWithTransactions::Capella(capella_block) => {
|
||||
@@ -1711,10 +1861,39 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
extra_data: capella_block.extra_data,
|
||||
base_fee_per_gas: capella_block.base_fee_per_gas,
|
||||
block_hash: capella_block.block_hash,
|
||||
transactions,
|
||||
transactions: convert_transactions(capella_block.transactions)?,
|
||||
withdrawals,
|
||||
})
|
||||
}
|
||||
ExecutionBlockWithTransactions::Deneb(deneb_block) => {
|
||||
let withdrawals = VariableList::new(
|
||||
deneb_block
|
||||
.withdrawals
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
)
|
||||
.map_err(ApiError::DeserializeWithdrawals)?;
|
||||
ExecutionPayload::Deneb(ExecutionPayloadDeneb {
|
||||
parent_hash: deneb_block.parent_hash,
|
||||
fee_recipient: deneb_block.fee_recipient,
|
||||
state_root: deneb_block.state_root,
|
||||
receipts_root: deneb_block.receipts_root,
|
||||
logs_bloom: deneb_block.logs_bloom,
|
||||
prev_randao: deneb_block.prev_randao,
|
||||
block_number: deneb_block.block_number,
|
||||
gas_limit: deneb_block.gas_limit,
|
||||
gas_used: deneb_block.gas_used,
|
||||
timestamp: deneb_block.timestamp,
|
||||
extra_data: deneb_block.extra_data,
|
||||
base_fee_per_gas: deneb_block.base_fee_per_gas,
|
||||
block_hash: deneb_block.block_hash,
|
||||
transactions: convert_transactions(deneb_block.transactions)?,
|
||||
withdrawals,
|
||||
blob_gas_used: deneb_block.blob_gas_used,
|
||||
excess_blob_gas: deneb_block.excess_blob_gas,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(payload))
|
||||
@@ -1723,8 +1902,8 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
pub async fn propose_blinded_beacon_block(
|
||||
&self,
|
||||
block_root: Hash256,
|
||||
block: &SignedBeaconBlock<T, BlindedPayload<T>>,
|
||||
) -> Result<ExecutionPayload<T>, Error> {
|
||||
block: &SignedBlockContents<T, BlindedPayload<T>>,
|
||||
) -> Result<FullPayloadContents<T>, Error> {
|
||||
debug!(
|
||||
self.log(),
|
||||
"Sending block to builder";
|
||||
@@ -1743,11 +1922,12 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
.await;
|
||||
|
||||
match &payload_result {
|
||||
Ok(payload) => {
|
||||
Ok(unblinded_response) => {
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME,
|
||||
&[metrics::SUCCESS],
|
||||
);
|
||||
let payload = unblinded_response.payload_ref();
|
||||
info!(
|
||||
self.log(),
|
||||
"Builder successfully revealed payload";
|
||||
@@ -1771,6 +1951,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
"relay_response_ms" => duration.as_millis(),
|
||||
"block_root" => ?block_root,
|
||||
"parent_hash" => ?block
|
||||
.signed_block()
|
||||
.message()
|
||||
.execution_payload()
|
||||
.map(|payload| format!("{}", payload.parent_hash()))
|
||||
@@ -1889,8 +2070,8 @@ impl fmt::Display for InvalidBuilderPayload {
|
||||
}
|
||||
|
||||
/// Perform some cursory, non-exhaustive validation of the bid returned from the builder.
|
||||
fn verify_builder_bid<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
bid: &ForkVersionedResponse<SignedBuilderBid<T, Payload>>,
|
||||
fn verify_builder_bid<T: EthSpec>(
|
||||
bid: &ForkVersionedResponse<SignedBuilderBid<T>>,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
block_number: Option<u64>,
|
||||
@@ -1899,11 +2080,11 @@ fn verify_builder_bid<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Box<InvalidBuilderPayload>> {
|
||||
let is_signature_valid = bid.data.verify_signature(spec);
|
||||
let header = &bid.data.message.header;
|
||||
let payload_value = bid.data.message.value;
|
||||
let header = &bid.data.message.header();
|
||||
let payload_value = bid.data.message.value();
|
||||
|
||||
// Avoid logging values that we can't represent with our Prometheus library.
|
||||
let payload_value_gwei = bid.data.message.value / 1_000_000_000;
|
||||
let payload_value_gwei = bid.data.message.value() / 1_000_000_000;
|
||||
if payload_value_gwei <= Uint256::from(i64::max_value()) {
|
||||
metrics::set_gauge_vec(
|
||||
&metrics::EXECUTION_LAYER_PAYLOAD_BIDS,
|
||||
@@ -1917,12 +2098,12 @@ fn verify_builder_bid<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
.ok()
|
||||
.cloned()
|
||||
.map(|withdrawals| Withdrawals::<T>::from(withdrawals).tree_hash_root());
|
||||
let payload_withdrawals_root = header.withdrawals_root().ok();
|
||||
let payload_withdrawals_root = header.withdrawals_root().ok().copied();
|
||||
|
||||
if payload_value < profit_threshold {
|
||||
if *payload_value < profit_threshold {
|
||||
Err(Box::new(InvalidBuilderPayload::LowValue {
|
||||
profit_threshold,
|
||||
payload_value,
|
||||
payload_value: *payload_value,
|
||||
}))
|
||||
} else if header.parent_hash() != parent_hash {
|
||||
Err(Box::new(InvalidBuilderPayload::ParentHash {
|
||||
@@ -1952,7 +2133,7 @@ fn verify_builder_bid<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
} else if !is_signature_valid {
|
||||
Err(Box::new(InvalidBuilderPayload::Signature {
|
||||
signature: bid.data.signature.clone(),
|
||||
pubkey: bid.data.message.pubkey,
|
||||
pubkey: *bid.data.message.pubkey(),
|
||||
}))
|
||||
} else if payload_withdrawals_root != expected_withdrawals_root {
|
||||
Err(Box::new(InvalidBuilderPayload::WithdrawalsRoot {
|
||||
@@ -1973,13 +2154,6 @@ async fn timed_future<F: Future<Output = T>, T>(metric: &str, future: F) -> (T,
|
||||
(result, duration)
|
||||
}
|
||||
|
||||
fn noop<T: EthSpec>(
|
||||
_: &ExecutionLayer<T>,
|
||||
_: ExecutionPayloadRef<T>,
|
||||
) -> Option<ExecutionPayload<T>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Returns the duration since the unix epoch.
|
||||
fn timestamp_now() -> u64 {
|
||||
@@ -1989,6 +2163,38 @@ fn timestamp_now() -> u64 {
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
fn static_valid_tx<T: EthSpec>() -> Result<Transaction<T::MaxBytesPerTransaction>, String> {
|
||||
// This is a real transaction hex encoded, but we don't care about the contents of the transaction.
|
||||
let transaction: EthersTransaction = serde_json::from_str(
|
||||
r#"{
|
||||
"blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2",
|
||||
"blockNumber":"0x5daf3b",
|
||||
"from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d",
|
||||
"gas":"0xc350",
|
||||
"gasPrice":"0x4a817c800",
|
||||
"hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
|
||||
"input":"0x68656c6c6f21",
|
||||
"nonce":"0x15",
|
||||
"to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
|
||||
"transactionIndex":"0x41",
|
||||
"value":"0xf3dbb76162000",
|
||||
"v":"0x25",
|
||||
"r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea",
|
||||
"s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
VariableList::new(transaction.rlp().to_vec())
|
||||
.map_err(|e| format!("Failed to convert transaction to SSZ: {:?}", e))
|
||||
}
|
||||
|
||||
fn noop<T: EthSpec>(
|
||||
_: &ExecutionLayer<T>,
|
||||
_: PayloadContentsRefTuple<T>,
|
||||
) -> Option<FullPayloadContents<T>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@@ -2134,4 +2340,42 @@ mod test {
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn percentage_difference_u256_tests() {
|
||||
// ensure function returns `None` when base value is zero
|
||||
assert_eq!(percentage_difference_u256(0.into(), 1.into()), None);
|
||||
// ensure function returns `None` when either value is greater than 120 Million ETH
|
||||
let max_value = Uint256::from(12u8) * Uint256::exp10(25);
|
||||
assert_eq!(
|
||||
percentage_difference_u256(1u8.into(), max_value + Uint256::from(1u8)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
percentage_difference_u256(max_value + Uint256::from(1u8), 1u8.into()),
|
||||
None
|
||||
);
|
||||
// it should work up to max value
|
||||
assert_eq!(
|
||||
percentage_difference_u256(max_value, max_value / Uint256::from(2u8)),
|
||||
Some(-50f32)
|
||||
);
|
||||
// should work when base value is greater than comparison value
|
||||
assert_eq!(
|
||||
percentage_difference_u256(4u8.into(), 3u8.into()),
|
||||
Some(-25f32)
|
||||
);
|
||||
// should work when comparison value is greater than base value
|
||||
assert_eq!(
|
||||
percentage_difference_u256(4u8.into(), 5u8.into()),
|
||||
Some(25f32)
|
||||
);
|
||||
// should be accurate to 7 decimal places
|
||||
let result =
|
||||
percentage_difference_u256(Uint256::from(31415926u64), Uint256::from(13371337u64))
|
||||
.expect("should get percentage");
|
||||
// result = -57.4377116
|
||||
assert!(result > -57.43772);
|
||||
assert!(result <= -57.43771);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use eth2::types::FullPayloadContents;
|
||||
use lru::LruCache;
|
||||
use parking_lot::Mutex;
|
||||
use tree_hash::TreeHash;
|
||||
use types::{EthSpec, ExecutionPayload, Hash256};
|
||||
use types::{EthSpec, Hash256};
|
||||
|
||||
pub const DEFAULT_PAYLOAD_CACHE_SIZE: usize = 10;
|
||||
|
||||
/// A cache mapping execution payloads by tree hash roots.
|
||||
pub struct PayloadCache<T: EthSpec> {
|
||||
payloads: Mutex<LruCache<PayloadCacheId, ExecutionPayload<T>>>,
|
||||
payloads: Mutex<LruCache<PayloadCacheId, FullPayloadContents<T>>>,
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq)]
|
||||
@@ -22,16 +23,16 @@ impl<T: EthSpec> Default for PayloadCache<T> {
|
||||
}
|
||||
|
||||
impl<T: EthSpec> PayloadCache<T> {
|
||||
pub fn put(&self, payload: ExecutionPayload<T>) -> Option<ExecutionPayload<T>> {
|
||||
let root = payload.tree_hash_root();
|
||||
pub fn put(&self, payload: FullPayloadContents<T>) -> Option<FullPayloadContents<T>> {
|
||||
let root = payload.payload_ref().tree_hash_root();
|
||||
self.payloads.lock().put(PayloadCacheId(root), payload)
|
||||
}
|
||||
|
||||
pub fn pop(&self, root: &Hash256) -> Option<ExecutionPayload<T>> {
|
||||
pub fn pop(&self, root: &Hash256) -> Option<FullPayloadContents<T>> {
|
||||
self.payloads.lock().pop(&PayloadCacheId(*root))
|
||||
}
|
||||
|
||||
pub fn get(&self, hash: &Hash256) -> Option<ExecutionPayload<T>> {
|
||||
pub fn get(&self, hash: &Hash256) -> Option<FullPayloadContents<T>> {
|
||||
self.payloads.lock().get(&PayloadCacheId(*hash)).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,25 @@ use crate::{
|
||||
},
|
||||
ExecutionBlock, PayloadAttributes, PayloadId, PayloadStatusV1, PayloadStatusV1Status,
|
||||
},
|
||||
ExecutionBlockWithTransactions,
|
||||
static_valid_tx, ExecutionBlockWithTransactions,
|
||||
};
|
||||
use eth2::types::BlobsBundle;
|
||||
use kzg::Kzg;
|
||||
use parking_lot::Mutex;
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tree_hash::TreeHash;
|
||||
use tree_hash_derive::TreeHash;
|
||||
use types::{
|
||||
EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadMerge,
|
||||
ForkName, Hash256, Uint256,
|
||||
BlobSidecar, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella,
|
||||
ExecutionPayloadDeneb, ExecutionPayloadHeader, ExecutionPayloadMerge, ForkName, Hash256,
|
||||
Transactions, Uint256,
|
||||
};
|
||||
|
||||
use super::DEFAULT_TERMINAL_BLOCK;
|
||||
|
||||
const GAS_LIMIT: u64 = 16384;
|
||||
const GAS_USED: u64 = GAS_LIMIT - 1;
|
||||
|
||||
@@ -118,6 +126,19 @@ pub struct ExecutionBlockGenerator<T: EthSpec> {
|
||||
* Post-merge fork triggers
|
||||
*/
|
||||
pub shanghai_time: Option<u64>, // withdrawals
|
||||
pub cancun_time: Option<u64>, // deneb
|
||||
/*
|
||||
* deneb stuff
|
||||
*/
|
||||
pub blobs_bundles: HashMap<PayloadId, BlobsBundle<T>>,
|
||||
pub kzg: Option<Arc<Kzg<T::Kzg>>>,
|
||||
rng: Arc<Mutex<StdRng>>,
|
||||
}
|
||||
|
||||
fn make_rng() -> Arc<Mutex<StdRng>> {
|
||||
// Nondeterminism in tests is a highly undesirable thing. Seed the RNG to some arbitrary
|
||||
// but fixed value for reproducibility.
|
||||
Arc::new(Mutex::new(StdRng::seed_from_u64(0xDEADBEEF0BAD5EEDu64)))
|
||||
}
|
||||
|
||||
impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
@@ -126,6 +147,8 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
terminal_block_number: u64,
|
||||
terminal_block_hash: ExecutionBlockHash,
|
||||
shanghai_time: Option<u64>,
|
||||
cancun_time: Option<u64>,
|
||||
kzg: Option<Kzg<T::Kzg>>,
|
||||
) -> Self {
|
||||
let mut gen = Self {
|
||||
head_block: <_>::default(),
|
||||
@@ -139,6 +162,10 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
next_payload_id: 0,
|
||||
payload_ids: <_>::default(),
|
||||
shanghai_time,
|
||||
cancun_time,
|
||||
blobs_bundles: <_>::default(),
|
||||
kzg: kzg.map(Arc::new),
|
||||
rng: make_rng(),
|
||||
};
|
||||
|
||||
gen.insert_pow_block(0).unwrap();
|
||||
@@ -171,9 +198,12 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
}
|
||||
|
||||
pub fn get_fork_at_timestamp(&self, timestamp: u64) -> ForkName {
|
||||
match self.shanghai_time {
|
||||
Some(fork_time) if timestamp >= fork_time => ForkName::Capella,
|
||||
_ => ForkName::Merge,
|
||||
match self.cancun_time {
|
||||
Some(fork_time) if timestamp >= fork_time => ForkName::Deneb,
|
||||
_ => match self.shanghai_time {
|
||||
Some(fork_time) if timestamp >= fork_time => ForkName::Capella,
|
||||
_ => ForkName::Merge,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,10 +279,15 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
finalized_block_hash
|
||||
));
|
||||
}
|
||||
let parent_hash = if block_number == 0 {
|
||||
ExecutionBlockHash::zero()
|
||||
let block = if block_number == 0 {
|
||||
generate_genesis_block(self.terminal_total_difficulty, self.terminal_block_number)?
|
||||
} else if let Some(block) = self.block_by_number(block_number - 1) {
|
||||
block.block_hash()
|
||||
generate_pow_block(
|
||||
self.terminal_total_difficulty,
|
||||
self.terminal_block_number,
|
||||
block_number,
|
||||
block.block_hash(),
|
||||
)?
|
||||
} else {
|
||||
return Err(format!(
|
||||
"parent with block number {} not found",
|
||||
@@ -260,13 +295,6 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
));
|
||||
};
|
||||
|
||||
let block = generate_pow_block(
|
||||
self.terminal_total_difficulty,
|
||||
self.terminal_block_number,
|
||||
block_number,
|
||||
parent_hash,
|
||||
)?;
|
||||
|
||||
// Insert block into block tree
|
||||
self.insert_block(Block::PoW(block))?;
|
||||
|
||||
@@ -327,10 +355,10 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
// This does not reject duplicate blocks inserted. This lets us re-use the same execution
|
||||
// block generator for multiple beacon chains which is useful in testing.
|
||||
pub fn insert_block(&mut self, block: Block<T>) -> Result<ExecutionBlockHash, String> {
|
||||
if self.blocks.contains_key(&block.block_hash()) {
|
||||
return Err(format!("{:?} is already known", block.block_hash()));
|
||||
} else if block.parent_hash() != ExecutionBlockHash::zero()
|
||||
if block.parent_hash() != ExecutionBlockHash::zero()
|
||||
&& !self.blocks.contains_key(&block.parent_hash())
|
||||
{
|
||||
return Err(format!("parent block {:?} is unknown", block.parent_hash()));
|
||||
@@ -388,6 +416,10 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
self.payload_ids.get(id).cloned()
|
||||
}
|
||||
|
||||
pub fn get_blobs_bundle(&mut self, id: &PayloadId) -> Option<BlobsBundle<T>> {
|
||||
self.blobs_bundles.get(id).cloned()
|
||||
}
|
||||
|
||||
pub fn new_payload(&mut self, payload: ExecutionPayload<T>) -> PayloadStatusV1 {
|
||||
let parent = if let Some(parent) = self.blocks.get(&payload.parent_hash()) {
|
||||
parent
|
||||
@@ -424,14 +456,20 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
forkchoice_state: ForkchoiceState,
|
||||
payload_attributes: Option<PayloadAttributes>,
|
||||
) -> Result<JsonForkchoiceUpdatedV1Response, String> {
|
||||
if let Some(payload) = self
|
||||
.pending_payloads
|
||||
.remove(&forkchoice_state.head_block_hash)
|
||||
{
|
||||
// This is meant to cover starting post-merge transition at genesis. Useful for
|
||||
// testing Capella forks and later.
|
||||
let head_block_hash = forkchoice_state.head_block_hash;
|
||||
if let Some(genesis_pow_block) = self.block_by_number(0) {
|
||||
if genesis_pow_block.block_hash() == head_block_hash {
|
||||
self.terminal_block_hash = head_block_hash;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(payload) = self.pending_payloads.remove(&head_block_hash) {
|
||||
self.insert_block(Block::PoS(payload))?;
|
||||
}
|
||||
|
||||
let unknown_head_block_hash = !self.blocks.contains_key(&forkchoice_state.head_block_hash);
|
||||
let unknown_head_block_hash = !self.blocks.contains_key(&head_block_hash);
|
||||
let unknown_safe_block_hash = forkchoice_state.safe_block_hash
|
||||
!= ExecutionBlockHash::zero()
|
||||
&& !self.blocks.contains_key(&forkchoice_state.safe_block_hash);
|
||||
@@ -464,75 +502,15 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
|
||||
let parent = self
|
||||
.blocks
|
||||
.get(&forkchoice_state.head_block_hash)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"unknown parent block {:?}",
|
||||
forkchoice_state.head_block_hash
|
||||
)
|
||||
})?;
|
||||
.get(&head_block_hash)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("unknown parent block {head_block_hash:?}"))?;
|
||||
|
||||
let id = payload_id_from_u64(self.next_payload_id);
|
||||
self.next_payload_id += 1;
|
||||
|
||||
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) => match self.get_fork_at_timestamp(pa.timestamp) {
|
||||
ForkName::Merge => 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(),
|
||||
}),
|
||||
ForkName::Capella => 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.clone().into(),
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
};
|
||||
|
||||
*execution_payload.block_hash_mut() =
|
||||
ExecutionBlockHash::from_root(execution_payload.tree_hash_root());
|
||||
|
||||
let execution_payload =
|
||||
self.build_new_execution_payload(head_block_hash, &parent, id, &attributes)?;
|
||||
self.payload_ids.insert(id, execution_payload);
|
||||
|
||||
Some(id)
|
||||
@@ -559,12 +537,199 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
payload_id: id.map(Into::into),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_new_execution_payload(
|
||||
&mut self,
|
||||
head_block_hash: ExecutionBlockHash,
|
||||
parent: &Block<T>,
|
||||
id: PayloadId,
|
||||
attributes: &PayloadAttributes,
|
||||
) -> Result<ExecutionPayload<T>, String> {
|
||||
let mut execution_payload = match attributes {
|
||||
PayloadAttributes::V1(pa) => ExecutionPayload::Merge(ExecutionPayloadMerge {
|
||||
parent_hash: 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) => match self.get_fork_at_timestamp(pa.timestamp) {
|
||||
ForkName::Merge => ExecutionPayload::Merge(ExecutionPayloadMerge {
|
||||
parent_hash: 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(),
|
||||
}),
|
||||
ForkName::Capella => ExecutionPayload::Capella(ExecutionPayloadCapella {
|
||||
parent_hash: 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.clone().into(),
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
PayloadAttributes::V3(pa) => ExecutionPayload::Deneb(ExecutionPayloadDeneb {
|
||||
parent_hash: 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.clone().into(),
|
||||
blob_gas_used: 0,
|
||||
excess_blob_gas: 0,
|
||||
}),
|
||||
};
|
||||
|
||||
match execution_payload.fork_name() {
|
||||
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {}
|
||||
ForkName::Deneb => {
|
||||
// get random number between 0 and Max Blobs
|
||||
let mut rng = self.rng.lock();
|
||||
let num_blobs = rng.gen::<usize>() % (T::max_blobs_per_block() + 1);
|
||||
let kzg = self.kzg.as_ref().ok_or("kzg not initialized")?;
|
||||
let (bundle, transactions) = generate_random_blobs(num_blobs, kzg, &mut *rng)?;
|
||||
for tx in Vec::from(transactions) {
|
||||
execution_payload
|
||||
.transactions_mut()
|
||||
.push(tx)
|
||||
.map_err(|_| "transactions are full".to_string())?;
|
||||
}
|
||||
self.blobs_bundles.insert(id, bundle);
|
||||
}
|
||||
}
|
||||
|
||||
*execution_payload.block_hash_mut() =
|
||||
ExecutionBlockHash::from_root(execution_payload.tree_hash_root());
|
||||
Ok(execution_payload)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_random_blobs<T: EthSpec, R: Rng>(
|
||||
n_blobs: usize,
|
||||
kzg: &Kzg<T::Kzg>,
|
||||
rng: &mut R,
|
||||
) -> Result<(BlobsBundle<T>, Transactions<T>), String> {
|
||||
let mut bundle = BlobsBundle::<T>::default();
|
||||
let mut transactions = vec![];
|
||||
for blob_index in 0..n_blobs {
|
||||
let random_valid_sidecar = BlobSidecar::<T>::random_valid(rng, kzg)?;
|
||||
|
||||
let BlobSidecar {
|
||||
blob,
|
||||
kzg_commitment,
|
||||
kzg_proof,
|
||||
..
|
||||
} = random_valid_sidecar;
|
||||
|
||||
let tx = static_valid_tx::<T>()
|
||||
.map_err(|e| format!("error creating valid tx SSZ bytes: {:?}", e))?;
|
||||
|
||||
transactions.push(tx);
|
||||
bundle
|
||||
.blobs
|
||||
.push(blob)
|
||||
.map_err(|_| format!("blobs are full, blob index: {:?}", blob_index))?;
|
||||
bundle
|
||||
.commitments
|
||||
.push(kzg_commitment)
|
||||
.map_err(|_| format!("blobs are full, blob index: {:?}", blob_index))?;
|
||||
bundle
|
||||
.proofs
|
||||
.push(kzg_proof)
|
||||
.map_err(|_| format!("blobs are full, blob index: {:?}", blob_index))?;
|
||||
}
|
||||
|
||||
Ok((bundle, transactions.into()))
|
||||
}
|
||||
|
||||
fn payload_id_from_u64(n: u64) -> PayloadId {
|
||||
n.to_le_bytes()
|
||||
}
|
||||
|
||||
pub fn generate_genesis_header<T: EthSpec>(
|
||||
spec: &ChainSpec,
|
||||
post_transition_merge: bool,
|
||||
) -> Option<ExecutionPayloadHeader<T>> {
|
||||
let genesis_fork = spec.fork_name_at_slot::<T>(spec.genesis_slot);
|
||||
let genesis_block_hash =
|
||||
generate_genesis_block(spec.terminal_total_difficulty, DEFAULT_TERMINAL_BLOCK)
|
||||
.ok()
|
||||
.map(|block| block.block_hash);
|
||||
match genesis_fork {
|
||||
ForkName::Base | ForkName::Altair => None,
|
||||
ForkName::Merge => {
|
||||
if post_transition_merge {
|
||||
let mut header = ExecutionPayloadHeader::Merge(<_>::default());
|
||||
*header.block_hash_mut() = genesis_block_hash.unwrap_or_default();
|
||||
Some(header)
|
||||
} else {
|
||||
Some(ExecutionPayloadHeader::<T>::Merge(<_>::default()))
|
||||
}
|
||||
}
|
||||
ForkName::Capella => {
|
||||
let mut header = ExecutionPayloadHeader::Capella(<_>::default());
|
||||
*header.block_hash_mut() = genesis_block_hash.unwrap_or_default();
|
||||
Some(header)
|
||||
}
|
||||
ForkName::Deneb => {
|
||||
let mut header = ExecutionPayloadHeader::Capella(<_>::default());
|
||||
*header.block_hash_mut() = genesis_block_hash.unwrap_or_default();
|
||||
Some(header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_genesis_block(
|
||||
terminal_total_difficulty: Uint256,
|
||||
terminal_block_number: u64,
|
||||
) -> Result<PoWBlock, String> {
|
||||
generate_pow_block(
|
||||
terminal_total_difficulty,
|
||||
terminal_block_number,
|
||||
0,
|
||||
ExecutionBlockHash::zero(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_pow_block(
|
||||
terminal_total_difficulty: Uint256,
|
||||
terminal_block_number: u64,
|
||||
@@ -618,6 +783,8 @@ mod test {
|
||||
TERMINAL_BLOCK,
|
||||
ExecutionBlockHash::zero(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
for i in 0..=TERMINAL_BLOCK {
|
||||
|
||||
@@ -93,7 +93,7 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
ENGINE_NEW_PAYLOAD_V1 | ENGINE_NEW_PAYLOAD_V2 => {
|
||||
ENGINE_NEW_PAYLOAD_V1 | ENGINE_NEW_PAYLOAD_V2 | ENGINE_NEW_PAYLOAD_V3 => {
|
||||
let request = match method {
|
||||
ENGINE_NEW_PAYLOAD_V1 => JsonExecutionPayload::V1(
|
||||
get_param::<JsonExecutionPayloadV1<T>>(params, 0)
|
||||
@@ -106,7 +106,17 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
.map(|jep| JsonExecutionPayload::V1(jep))
|
||||
})
|
||||
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
|
||||
// TODO(4844) add that here..
|
||||
ENGINE_NEW_PAYLOAD_V3 => get_param::<JsonExecutionPayloadV3<T>>(params, 0)
|
||||
.map(|jep| JsonExecutionPayload::V3(jep))
|
||||
.or_else(|_| {
|
||||
get_param::<JsonExecutionPayloadV2<T>>(params, 0)
|
||||
.map(|jep| JsonExecutionPayload::V2(jep))
|
||||
.or_else(|_| {
|
||||
get_param::<JsonExecutionPayloadV1<T>>(params, 0)
|
||||
.map(|jep| JsonExecutionPayload::V1(jep))
|
||||
})
|
||||
})
|
||||
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
@@ -144,7 +154,32 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
));
|
||||
}
|
||||
}
|
||||
// TODO(4844) add 4844 error checking here
|
||||
ForkName::Deneb => {
|
||||
if method == ENGINE_NEW_PAYLOAD_V1 || method == ENGINE_NEW_PAYLOAD_V2 {
|
||||
return Err((
|
||||
format!("{} called after deneb fork!", method),
|
||||
GENERIC_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
if matches!(request, JsonExecutionPayload::V1(_)) {
|
||||
return Err((
|
||||
format!(
|
||||
"{} called with `ExecutionPayloadV1` after deneb fork!",
|
||||
method
|
||||
),
|
||||
GENERIC_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
if matches!(request, JsonExecutionPayload::V2(_)) {
|
||||
return Err((
|
||||
format!(
|
||||
"{} called with `ExecutionPayloadV2` after deneb fork!",
|
||||
method
|
||||
),
|
||||
GENERIC_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
@@ -180,7 +215,7 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
|
||||
Ok(serde_json::to_value(JsonPayloadStatusV1::from(response)).unwrap())
|
||||
}
|
||||
ENGINE_GET_PAYLOAD_V1 | ENGINE_GET_PAYLOAD_V2 => {
|
||||
ENGINE_GET_PAYLOAD_V1 | ENGINE_GET_PAYLOAD_V2 | ENGINE_GET_PAYLOAD_V3 => {
|
||||
let request: JsonPayloadIdRequest =
|
||||
get_param(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
||||
let id = request.into();
|
||||
@@ -196,6 +231,8 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
)
|
||||
})?;
|
||||
|
||||
let maybe_blobs = ctx.execution_block_generator.write().get_blobs_bundle(&id);
|
||||
|
||||
// validate method called correctly according to shanghai fork time
|
||||
if ctx
|
||||
.execution_block_generator
|
||||
@@ -209,7 +246,19 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
// TODO(4844) add 4844 error checking here
|
||||
// validate method called correctly according to deneb fork time
|
||||
if ctx
|
||||
.execution_block_generator
|
||||
.read()
|
||||
.get_fork_at_timestamp(response.timestamp())
|
||||
== ForkName::Deneb
|
||||
&& (method == ENGINE_GET_PAYLOAD_V1 || method == ENGINE_GET_PAYLOAD_V2)
|
||||
{
|
||||
return Err((
|
||||
format!("{} called after deneb fork!", method),
|
||||
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
|
||||
match method {
|
||||
ENGINE_GET_PAYLOAD_V1 => {
|
||||
@@ -230,11 +279,44 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}),
|
||||
ENGINE_GET_PAYLOAD_V3 => Ok(match JsonExecutionPayload::from(response) {
|
||||
JsonExecutionPayload::V1(execution_payload) => {
|
||||
serde_json::to_value(JsonGetPayloadResponseV1 {
|
||||
execution_payload,
|
||||
block_value: DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI.into(),
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
JsonExecutionPayload::V2(execution_payload) => {
|
||||
serde_json::to_value(JsonGetPayloadResponseV2 {
|
||||
execution_payload,
|
||||
block_value: DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI.into(),
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
JsonExecutionPayload::V3(execution_payload) => {
|
||||
serde_json::to_value(JsonGetPayloadResponseV3 {
|
||||
execution_payload,
|
||||
block_value: DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI.into(),
|
||||
blobs_bundle: maybe_blobs
|
||||
.ok_or((
|
||||
"No blobs returned despite V3 Payload".to_string(),
|
||||
GENERIC_ERROR_CODE,
|
||||
))?
|
||||
.into(),
|
||||
should_override_builder: false,
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
ENGINE_FORKCHOICE_UPDATED_V1 | ENGINE_FORKCHOICE_UPDATED_V2 => {
|
||||
ENGINE_FORKCHOICE_UPDATED_V1
|
||||
| ENGINE_FORKCHOICE_UPDATED_V2
|
||||
| ENGINE_FORKCHOICE_UPDATED_V3 => {
|
||||
let forkchoice_state: JsonForkchoiceStateV1 =
|
||||
get_param(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
||||
let payload_attributes = match method {
|
||||
@@ -260,7 +342,7 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
.map(|opt| opt.map(JsonPayloadAttributes::V1))
|
||||
.transpose()
|
||||
}
|
||||
ForkName::Capella => {
|
||||
ForkName::Capella | ForkName::Deneb => {
|
||||
get_param::<Option<JsonPayloadAttributesV2>>(params, 1)
|
||||
.map(|opt| opt.map(JsonPayloadAttributes::V2))
|
||||
.transpose()
|
||||
@@ -272,10 +354,15 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
})
|
||||
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
|
||||
}
|
||||
ENGINE_FORKCHOICE_UPDATED_V3 => {
|
||||
get_param::<Option<JsonPayloadAttributesV3>>(params, 1)
|
||||
.map(|opt| opt.map(JsonPayloadAttributes::V3))
|
||||
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// validate method called correctly according to shanghai fork time
|
||||
// validate method called correctly according to fork time
|
||||
if let Some(pa) = payload_attributes.as_ref() {
|
||||
match ctx
|
||||
.execution_block_generator
|
||||
@@ -300,6 +387,15 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
if method == ENGINE_FORKCHOICE_UPDATED_V3 {
|
||||
return Err((
|
||||
format!(
|
||||
"{} called with `JsonPayloadAttributesV3` before Deneb fork!",
|
||||
method
|
||||
),
|
||||
GENERIC_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
if matches!(pa, JsonPayloadAttributes::V1(_)) {
|
||||
return Err((
|
||||
format!(
|
||||
@@ -310,7 +406,20 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
));
|
||||
}
|
||||
}
|
||||
// TODO(4844) add 4844 error checking here
|
||||
ForkName::Deneb => {
|
||||
if method == ENGINE_FORKCHOICE_UPDATED_V1 {
|
||||
return Err((
|
||||
format!("{} called after Deneb fork!", method),
|
||||
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
if method == ENGINE_FORKCHOICE_UPDATED_V2 {
|
||||
return Err((
|
||||
format!("{} called after Deneb fork!", method),
|
||||
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::test_utils::{DEFAULT_BUILDER_PAYLOAD_VALUE_WEI, DEFAULT_JWT_SECRET};
|
||||
use crate::{Config, ExecutionLayer, PayloadAttributes};
|
||||
use async_trait::async_trait;
|
||||
use eth2::types::{BlockId, StateId, ValidatorId};
|
||||
use eth2::types::{BlobsBundle, BlockId, StateId, ValidatorId};
|
||||
use eth2::{BeaconNodeHttpClient, Timeouts};
|
||||
pub use ethereum_consensus::state_transition::Context;
|
||||
use ethereum_consensus::{
|
||||
@@ -18,6 +18,7 @@ use mev_rs::{
|
||||
BuilderBid as BuilderBidBellatrix, SignedBuilderBid as SignedBuilderBidBellatrix,
|
||||
},
|
||||
capella::{BuilderBid as BuilderBidCapella, SignedBuilderBid as SignedBuilderBidCapella},
|
||||
deneb::{BuilderBid as BuilderBidDeneb, SignedBuilderBid as SignedBuilderBidDeneb},
|
||||
BidRequest, BuilderBid, ExecutionPayload as ServerPayload, SignedBlindedBeaconBlock,
|
||||
SignedBuilderBid, SignedValidatorRegistration,
|
||||
},
|
||||
@@ -35,9 +36,10 @@ use std::time::Duration;
|
||||
use task_executor::TaskExecutor;
|
||||
use tempfile::NamedTempFile;
|
||||
use tree_hash::TreeHash;
|
||||
use types::builder_bid::BlindedBlobsBundle;
|
||||
use types::{
|
||||
Address, BeaconState, BlindedPayload, ChainSpec, EthSpec, ExecPayload, ForkName, Hash256, Slot,
|
||||
Uint256,
|
||||
Address, BeaconState, ChainSpec, EthSpec, ExecPayload, ExecutionPayload,
|
||||
ExecutionPayloadHeader, ForkName, ForkVersionedResponse, Hash256, Slot, Uint256,
|
||||
};
|
||||
|
||||
pub type MockBuilderServer = axum::Server<
|
||||
@@ -95,60 +97,50 @@ pub trait BidStuff {
|
||||
fn to_signed_bid(self, signature: BlsSignature) -> SignedBuilderBid;
|
||||
}
|
||||
|
||||
macro_rules! map_builder_bid {
|
||||
($self_ident:ident, |$var:ident| $expr:expr) => {
|
||||
match $self_ident {
|
||||
BuilderBid::Bellatrix($var) => $expr,
|
||||
BuilderBid::Capella($var) => $expr,
|
||||
BuilderBid::Deneb($var) => $expr,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl BidStuff for BuilderBid {
|
||||
fn fee_recipient_mut(&mut self) -> &mut ExecutionAddress {
|
||||
match self {
|
||||
Self::Bellatrix(bid) => &mut bid.header.fee_recipient,
|
||||
Self::Capella(bid) => &mut bid.header.fee_recipient,
|
||||
}
|
||||
map_builder_bid!(self, |bid| &mut bid.header.fee_recipient)
|
||||
}
|
||||
|
||||
fn gas_limit_mut(&mut self) -> &mut u64 {
|
||||
match self {
|
||||
Self::Bellatrix(bid) => &mut bid.header.gas_limit,
|
||||
Self::Capella(bid) => &mut bid.header.gas_limit,
|
||||
}
|
||||
map_builder_bid!(self, |bid| &mut bid.header.gas_limit)
|
||||
}
|
||||
|
||||
fn value_mut(&mut self) -> &mut U256 {
|
||||
match self {
|
||||
Self::Bellatrix(bid) => &mut bid.value,
|
||||
Self::Capella(bid) => &mut bid.value,
|
||||
}
|
||||
map_builder_bid!(self, |bid| &mut bid.value)
|
||||
}
|
||||
|
||||
fn parent_hash_mut(&mut self) -> &mut Hash32 {
|
||||
match self {
|
||||
Self::Bellatrix(bid) => &mut bid.header.parent_hash,
|
||||
Self::Capella(bid) => &mut bid.header.parent_hash,
|
||||
}
|
||||
map_builder_bid!(self, |bid| &mut bid.header.parent_hash)
|
||||
}
|
||||
|
||||
fn prev_randao_mut(&mut self) -> &mut Hash32 {
|
||||
match self {
|
||||
Self::Bellatrix(bid) => &mut bid.header.prev_randao,
|
||||
Self::Capella(bid) => &mut bid.header.prev_randao,
|
||||
}
|
||||
map_builder_bid!(self, |bid| &mut bid.header.prev_randao)
|
||||
}
|
||||
|
||||
fn block_number_mut(&mut self) -> &mut u64 {
|
||||
match self {
|
||||
Self::Bellatrix(bid) => &mut bid.header.block_number,
|
||||
Self::Capella(bid) => &mut bid.header.block_number,
|
||||
}
|
||||
map_builder_bid!(self, |bid| &mut bid.header.block_number)
|
||||
}
|
||||
|
||||
fn timestamp_mut(&mut self) -> &mut u64 {
|
||||
match self {
|
||||
Self::Bellatrix(bid) => &mut bid.header.timestamp,
|
||||
Self::Capella(bid) => &mut bid.header.timestamp,
|
||||
}
|
||||
map_builder_bid!(self, |bid| &mut bid.header.timestamp)
|
||||
}
|
||||
|
||||
fn withdrawals_root_mut(&mut self) -> Result<&mut Root, MevError> {
|
||||
match self {
|
||||
Self::Bellatrix(_) => Err(MevError::InvalidFork),
|
||||
Self::Capella(bid) => Ok(&mut bid.header.withdrawals_root),
|
||||
Self::Deneb(bid) => Ok(&mut bid.header.withdrawals_root),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,10 +149,11 @@ impl BidStuff for BuilderBid {
|
||||
signing_key: &SecretKey,
|
||||
context: &Context,
|
||||
) -> Result<Signature, Error> {
|
||||
match self {
|
||||
Self::Bellatrix(message) => sign_builder_message(message, signing_key, context),
|
||||
Self::Capella(message) => sign_builder_message(message, signing_key, context),
|
||||
}
|
||||
map_builder_bid!(self, |message| sign_builder_message(
|
||||
message,
|
||||
signing_key,
|
||||
context
|
||||
))
|
||||
}
|
||||
|
||||
fn to_signed_bid(self, signature: Signature) -> SignedBuilderBid {
|
||||
@@ -171,6 +164,9 @@ impl BidStuff for BuilderBid {
|
||||
Self::Capella(message) => {
|
||||
SignedBuilderBid::Capella(SignedBuilderBidCapella { message, signature })
|
||||
}
|
||||
Self::Deneb(message) => {
|
||||
SignedBuilderBid::Deneb(SignedBuilderBidDeneb { message, signature })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,6 +266,10 @@ impl<E: EthSpec> MockBuilder<E> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pubkey(&self) -> ethereum_consensus::crypto::PublicKey {
|
||||
self.builder_sk.public_key()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -387,13 +387,36 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
|
||||
let prev_randao = head_state
|
||||
.get_randao_mix(head_state.current_epoch())
|
||||
.map_err(convert_err)?;
|
||||
let expected_withdrawals = match fork {
|
||||
ForkName::Base | ForkName::Altair | ForkName::Merge => None,
|
||||
ForkName::Capella | ForkName::Deneb => Some(
|
||||
self.beacon_client
|
||||
.get_expected_withdrawals(&StateId::Head)
|
||||
.await
|
||||
.unwrap()
|
||||
.data,
|
||||
),
|
||||
};
|
||||
|
||||
let payload_attributes = match fork {
|
||||
ForkName::Merge => PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, None),
|
||||
// the withdrawals root is filled in by operations
|
||||
ForkName::Capella => {
|
||||
PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, Some(vec![]))
|
||||
}
|
||||
// the withdrawals root is filled in by operations, but we supply the valid withdrawals
|
||||
// first to avoid polluting the execution block generator with invalid payload attributes
|
||||
// NOTE: this was part of an effort to add payload attribute uniqueness checks,
|
||||
// which was abandoned because it broke too many tests in subtle ways.
|
||||
ForkName::Merge | ForkName::Capella => PayloadAttributes::new(
|
||||
timestamp,
|
||||
*prev_randao,
|
||||
fee_recipient,
|
||||
expected_withdrawals,
|
||||
None,
|
||||
),
|
||||
ForkName::Deneb => PayloadAttributes::new(
|
||||
timestamp,
|
||||
*prev_randao,
|
||||
fee_recipient,
|
||||
expected_withdrawals,
|
||||
Some(head_block_root),
|
||||
),
|
||||
ForkName::Base | ForkName::Altair => {
|
||||
return Err(MevError::InvalidFork);
|
||||
}
|
||||
@@ -410,9 +433,13 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
|
||||
finalized_hash: Some(finalized_execution_hash),
|
||||
};
|
||||
|
||||
let payload = self
|
||||
let (payload, _block_value, maybe_blobs_bundle): (
|
||||
ExecutionPayload<E>,
|
||||
Uint256,
|
||||
Option<BlobsBundle<E>>,
|
||||
) = self
|
||||
.el
|
||||
.get_full_payload_caching::<BlindedPayload<E>>(
|
||||
.get_full_payload_caching(
|
||||
head_execution_hash,
|
||||
&payload_attributes,
|
||||
forkchoice_update_params,
|
||||
@@ -420,18 +447,34 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
|
||||
)
|
||||
.await
|
||||
.map_err(convert_err)?
|
||||
.to_payload()
|
||||
.to_execution_payload_header();
|
||||
.into();
|
||||
|
||||
let header = match payload {
|
||||
ExecutionPayload::Merge(payload) => ExecutionPayloadHeader::Merge((&payload).into()),
|
||||
ExecutionPayload::Capella(payload) => {
|
||||
ExecutionPayloadHeader::Capella((&payload).into())
|
||||
}
|
||||
ExecutionPayload::Deneb(payload) => ExecutionPayloadHeader::Deneb((&payload).into()),
|
||||
};
|
||||
|
||||
let json_payload = serde_json::to_string(&payload).map_err(convert_err)?;
|
||||
let mut message = match fork {
|
||||
ForkName::Deneb => {
|
||||
let blinded_blobs: BlindedBlobsBundle<E> =
|
||||
maybe_blobs_bundle.map(Into::into).unwrap_or_default();
|
||||
BuilderBid::Deneb(BuilderBidDeneb {
|
||||
header: to_ssz_rs(&header)?,
|
||||
blinded_blobs_bundle: to_ssz_rs(&blinded_blobs)?,
|
||||
value: to_ssz_rs(&Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI))?,
|
||||
public_key: self.builder_sk.public_key(),
|
||||
})
|
||||
}
|
||||
ForkName::Capella => BuilderBid::Capella(BuilderBidCapella {
|
||||
header: serde_json::from_str(json_payload.as_str()).map_err(convert_err)?,
|
||||
header: to_ssz_rs(&header)?,
|
||||
value: to_ssz_rs(&Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI))?,
|
||||
public_key: self.builder_sk.public_key(),
|
||||
}),
|
||||
ForkName::Merge => BuilderBid::Bellatrix(BuilderBidBellatrix {
|
||||
header: serde_json::from_str(json_payload.as_str()).map_err(convert_err)?,
|
||||
header: to_ssz_rs(&header)?,
|
||||
value: to_ssz_rs(&Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI))?,
|
||||
public_key: self.builder_sk.public_key(),
|
||||
}),
|
||||
@@ -461,6 +504,12 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
|
||||
SignedBlindedBeaconBlock::Capella(block) => {
|
||||
block.message.body.execution_payload_header.hash_tree_root()
|
||||
}
|
||||
SignedBlindedBeaconBlock::Deneb(block_and_blobs) => block_and_blobs
|
||||
.signed_blinded_block
|
||||
.message
|
||||
.body
|
||||
.execution_payload_header
|
||||
.hash_tree_root(),
|
||||
}
|
||||
.map_err(convert_err)?;
|
||||
|
||||
@@ -469,7 +518,13 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
|
||||
.get_payload_by_root(&from_ssz_rs(&node)?)
|
||||
.ok_or_else(|| convert_err("missing payload for tx root"))?;
|
||||
|
||||
let json_payload = serde_json::to_string(&payload).map_err(convert_err)?;
|
||||
let fork = payload.payload_ref().fork_name();
|
||||
let resp = ForkVersionedResponse {
|
||||
version: Some(fork),
|
||||
data: payload,
|
||||
};
|
||||
|
||||
let json_payload = serde_json::to_string(&resp).map_err(convert_err)?;
|
||||
serde_json::from_str(json_payload.as_str()).map_err(convert_err)
|
||||
}
|
||||
}
|
||||
@@ -487,12 +542,12 @@ pub fn to_ssz_rs<T: Encode, U: SimpleSerialize>(ssz_data: &T) -> Result<U, MevEr
|
||||
ssz_rs::deserialize::<U>(&ssz_data.as_ssz_bytes()).map_err(convert_err)
|
||||
}
|
||||
|
||||
fn convert_err<E: Debug>(e: E) -> MevError {
|
||||
pub fn convert_err<E: Debug>(e: E) -> MevError {
|
||||
custom_err(format!("{e:?}"))
|
||||
}
|
||||
|
||||
// This is a bit of a hack since the `Custom` variant was removed from `mev_rs::Error`.
|
||||
fn custom_err(s: String) -> MevError {
|
||||
pub fn custom_err(s: String) -> MevError {
|
||||
MevError::Consensus(ethereum_consensus::state_transition::Error::Io(
|
||||
std::io::Error::new(std::io::ErrorKind::Other, s),
|
||||
))
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::{
|
||||
},
|
||||
Config, *,
|
||||
};
|
||||
use kzg::Kzg;
|
||||
use sensitive_url::SensitiveUrl;
|
||||
use task_executor::TaskExecutor;
|
||||
use tempfile::NamedTempFile;
|
||||
@@ -29,8 +30,10 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
DEFAULT_TERMINAL_BLOCK,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
|
||||
spec,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,9 +42,11 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
executor: TaskExecutor,
|
||||
terminal_block: u64,
|
||||
shanghai_time: Option<u64>,
|
||||
cancun_time: Option<u64>,
|
||||
builder_threshold: Option<u128>,
|
||||
jwt_key: Option<JwtKey>,
|
||||
spec: ChainSpec,
|
||||
kzg: Option<Kzg<T::Kzg>>,
|
||||
) -> Self {
|
||||
let handle = executor.handle().unwrap();
|
||||
|
||||
@@ -53,6 +58,8 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
terminal_block,
|
||||
spec.terminal_block_hash,
|
||||
shanghai_time,
|
||||
cancun_time,
|
||||
kzg,
|
||||
);
|
||||
|
||||
let url = SensitiveUrl::parse(&server.url()).unwrap();
|
||||
@@ -100,7 +107,8 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
Address::repeat_byte(42),
|
||||
// FIXME: think about how to handle different forks / withdrawals here..
|
||||
// FIXME: think about how to handle different forks here..
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
@@ -130,7 +138,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
};
|
||||
let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await;
|
||||
let payload_attributes =
|
||||
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None);
|
||||
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None);
|
||||
let payload: ExecutionPayload<T> = self
|
||||
.el
|
||||
.get_payload::<FullPayload<T>>(
|
||||
@@ -138,7 +146,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
&payload_attributes,
|
||||
forkchoice_update_params,
|
||||
builder_params,
|
||||
// FIXME: do we need to consider other forks somehow? What about withdrawals?
|
||||
// FIXME: do we need to consider other forks somehow?
|
||||
ForkName::Merge,
|
||||
&self.spec,
|
||||
)
|
||||
@@ -165,7 +173,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
};
|
||||
let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await;
|
||||
let payload_attributes =
|
||||
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None);
|
||||
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None);
|
||||
let payload_header = self
|
||||
.el
|
||||
.get_payload::<BlindedPayload<T>>(
|
||||
@@ -191,10 +199,15 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
assert_eq!(
|
||||
self.el
|
||||
.get_payload_by_root(&payload_header.tree_hash_root()),
|
||||
Some(payload.clone())
|
||||
Some(FullPayloadContents::Payload(payload.clone()))
|
||||
);
|
||||
|
||||
let status = self.el.notify_new_payload(&payload).await.unwrap();
|
||||
// TODO: again consider forks
|
||||
let status = self
|
||||
.el
|
||||
.notify_new_payload(payload.try_into().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(status, PayloadStatus::Valid);
|
||||
|
||||
// Use junk values for slot/head-root to ensure there is no payload supplied.
|
||||
|
||||
@@ -8,6 +8,7 @@ use bytes::Bytes;
|
||||
use environment::null_logger;
|
||||
use execution_block_generator::PoWBlock;
|
||||
use handle_rpc::handle_rpc;
|
||||
use kzg::Kzg;
|
||||
use parking_lot::{Mutex, RwLock, RwLockWriteGuard};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
@@ -23,9 +24,15 @@ use types::{EthSpec, ExecutionBlockHash, Uint256};
|
||||
use warp::{http::StatusCode, Filter, Rejection};
|
||||
|
||||
use crate::EngineCapabilities;
|
||||
pub use execution_block_generator::{generate_pow_block, Block, ExecutionBlockGenerator};
|
||||
pub use execution_block_generator::{
|
||||
generate_genesis_block, generate_genesis_header, generate_pow_block, generate_random_blobs,
|
||||
Block, ExecutionBlockGenerator,
|
||||
};
|
||||
pub use hook::Hook;
|
||||
pub use mock_builder::{Context as MockBuilderContext, MockBuilder, MockBuilderServer, Operation};
|
||||
pub use mock_builder::{
|
||||
convert_err, custom_err, from_ssz_rs, to_ssz_rs, Context as MockBuilderContext, MockBuilder,
|
||||
MockBuilderServer, Operation,
|
||||
};
|
||||
pub use mock_execution_layer::MockExecutionLayer;
|
||||
|
||||
pub const DEFAULT_TERMINAL_DIFFICULTY: u64 = 6400;
|
||||
@@ -37,12 +44,15 @@ pub const DEFAULT_BUILDER_PAYLOAD_VALUE_WEI: u128 = 20_000_000_000_000_000;
|
||||
pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities {
|
||||
new_payload_v1: true,
|
||||
new_payload_v2: true,
|
||||
new_payload_v3: true,
|
||||
forkchoice_updated_v1: true,
|
||||
forkchoice_updated_v2: true,
|
||||
forkchoice_updated_v3: true,
|
||||
get_payload_bodies_by_hash_v1: true,
|
||||
get_payload_bodies_by_range_v1: true,
|
||||
get_payload_v1: true,
|
||||
get_payload_v2: true,
|
||||
get_payload_v3: true,
|
||||
};
|
||||
|
||||
mod execution_block_generator;
|
||||
@@ -59,6 +69,7 @@ pub struct MockExecutionConfig {
|
||||
pub terminal_block: u64,
|
||||
pub terminal_block_hash: ExecutionBlockHash,
|
||||
pub shanghai_time: Option<u64>,
|
||||
pub cancun_time: Option<u64>,
|
||||
}
|
||||
|
||||
impl Default for MockExecutionConfig {
|
||||
@@ -70,6 +81,7 @@ impl Default for MockExecutionConfig {
|
||||
terminal_block_hash: ExecutionBlockHash::zero(),
|
||||
server_config: Config::default(),
|
||||
shanghai_time: None,
|
||||
cancun_time: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,10 +102,16 @@ impl<T: EthSpec> MockServer<T> {
|
||||
DEFAULT_TERMINAL_BLOCK,
|
||||
ExecutionBlockHash::zero(),
|
||||
None, // FIXME(capella): should this be the default?
|
||||
None, // FIXME(deneb): should this be the default?
|
||||
None, // FIXME(deneb): should this be the default?
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_config(handle: &runtime::Handle, config: MockExecutionConfig) -> Self {
|
||||
pub fn new_with_config(
|
||||
handle: &runtime::Handle,
|
||||
config: MockExecutionConfig,
|
||||
kzg: Option<Kzg<T::Kzg>>,
|
||||
) -> Self {
|
||||
let MockExecutionConfig {
|
||||
jwt_key,
|
||||
terminal_difficulty,
|
||||
@@ -101,6 +119,7 @@ impl<T: EthSpec> MockServer<T> {
|
||||
terminal_block_hash,
|
||||
server_config,
|
||||
shanghai_time,
|
||||
cancun_time,
|
||||
} = config;
|
||||
let last_echo_request = Arc::new(RwLock::new(None));
|
||||
let preloaded_responses = Arc::new(Mutex::new(vec![]));
|
||||
@@ -109,6 +128,8 @@ impl<T: EthSpec> MockServer<T> {
|
||||
terminal_block,
|
||||
terminal_block_hash,
|
||||
shanghai_time,
|
||||
cancun_time,
|
||||
kzg,
|
||||
);
|
||||
|
||||
let ctx: Arc<Context<T>> = Arc::new(Context {
|
||||
@@ -161,6 +182,7 @@ impl<T: EthSpec> MockServer<T> {
|
||||
*self.ctx.engine_capabilities.write() = engine_capabilities;
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
handle: &runtime::Handle,
|
||||
jwt_key: JwtKey,
|
||||
@@ -168,6 +190,8 @@ impl<T: EthSpec> MockServer<T> {
|
||||
terminal_block: u64,
|
||||
terminal_block_hash: ExecutionBlockHash,
|
||||
shanghai_time: Option<u64>,
|
||||
cancun_time: Option<u64>,
|
||||
kzg: Option<Kzg<T::Kzg>>,
|
||||
) -> Self {
|
||||
Self::new_with_config(
|
||||
handle,
|
||||
@@ -178,7 +202,9 @@ impl<T: EthSpec> MockServer<T> {
|
||||
terminal_block,
|
||||
terminal_block_hash,
|
||||
shanghai_time,
|
||||
cancun_time,
|
||||
},
|
||||
kzg,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user