Update engine_api to latest version (#4223)

* Update Engine API to Latest

* Get Mock EE Working

* Fix Mock EE

* Update Engine API Again

* Rip out get_blobs_bundle Stuff

* Fix Test Harness

* Fix Clippy Complaints

* Fix Beacon Chain Tests
This commit is contained in:
ethDreamer
2023-04-27 13:18:21 -05:00
committed by GitHub
parent aa34339298
commit c1d47da02d
24 changed files with 449 additions and 159 deletions

View File

@@ -25,6 +25,7 @@ hex = "0.4.2"
eth2_ssz = "0.4.1"
eth2_ssz_types = "0.2.2"
eth2 = { path = "../../common/eth2" }
kzg = { path = "../../crypto/kzg" }
state_processing = { path = "../../consensus/state_processing" }
superstruct = "0.6.0"
lru = "0.7.1"

View File

@@ -16,12 +16,14 @@ use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use strum::IntoStaticStr;
use superstruct::superstruct;
use types::beacon_block_body::KzgCommitments;
use types::blob_sidecar::Blobs;
pub use types::{
Address, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader,
ExecutionPayloadRef, FixedVector, ForkName, Hash256, Transactions, Uint256, VariableList,
Withdrawal, Withdrawals,
};
use types::{ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge};
use types::{ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge, KzgProofs};
pub mod auth;
pub mod http;
@@ -377,6 +379,8 @@ pub struct GetPayloadResponse<T: EthSpec> {
#[superstruct(only(Deneb), partial_getter(rename = "execution_payload_deneb"))]
pub execution_payload: ExecutionPayloadDeneb<T>,
pub block_value: Uint256,
#[superstruct(only(Deneb))]
pub blobs_bundle: BlobsBundleV1<T>,
}
impl<'a, T: EthSpec> From<GetPayloadResponseRef<'a, T>> for ExecutionPayloadRef<'a, T> {
@@ -395,20 +399,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<BlobsBundleV1<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),
),
}
}
@@ -513,6 +522,13 @@ impl<E: EthSpec> ExecutionPayloadBodyV1<E> {
}
}
#[derive(Clone, Default, Debug, PartialEq)]
pub struct BlobsBundleV1<E: EthSpec> {
pub commitments: KzgCommitments<E>,
pub proofs: KzgProofs<E>,
pub blobs: Blobs<E>,
}
#[derive(Clone, Copy, Debug)]
pub struct EngineCapabilities {
pub new_payload_v1: bool,

View File

@@ -40,9 +40,6 @@ 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_GET_BLOBS_BUNDLE_V1: &str = "engine_getBlobsBundleV1";
pub const ENGINE_GET_BLOBS_BUNDLE_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_TIMEOUT: Duration = Duration::from_secs(8);
@@ -927,23 +924,6 @@ impl HttpJsonRpc {
}
}
pub async fn get_blobs_bundle_v1<T: EthSpec>(
&self,
payload_id: PayloadId,
) -> Result<JsonBlobsBundle<T>, Error> {
let params = json!([JsonPayloadIdRequest::from(payload_id)]);
let response: JsonBlobsBundle<T> = self
.rpc_request(
ENGINE_GET_BLOBS_BUNDLE_V1,
params,
ENGINE_GET_BLOBS_BUNDLE_TIMEOUT,
)
.await?;
Ok(response)
}
pub async fn forkchoice_updated_v1(
&self,
forkchoice_state: ForkchoiceState,

View File

@@ -291,6 +291,8 @@ pub struct JsonGetPayloadResponse<T: EthSpec> {
pub execution_payload: JsonExecutionPayloadV3<T>,
#[serde(with = "eth2_serde_utils::u256_hex_be")]
pub block_value: Uint256,
#[superstruct(only(V3))]
pub blobs_bundle: JsonBlobsBundleV1<T>,
}
impl<T: EthSpec> From<JsonGetPayloadResponse<T>> for GetPayloadResponse<T> {
@@ -312,6 +314,7 @@ impl<T: EthSpec> From<JsonGetPayloadResponse<T>> for GetPayloadResponse<T> {
GetPayloadResponse::Deneb(GetPayloadResponseDeneb {
execution_payload: response.execution_payload.into(),
block_value: response.block_value,
blobs_bundle: response.blobs_bundle.into(),
})
}
}
@@ -409,12 +412,31 @@ impl From<JsonPayloadAttributes> for PayloadAttributes {
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(bound = "T: EthSpec", rename_all = "camelCase")]
pub struct JsonBlobsBundle<T: EthSpec> {
pub block_hash: ExecutionBlockHash,
pub kzgs: KzgCommitments<T>,
#[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: Blobs<T>,
pub blobs: Blobs<E>,
}
impl<E: EthSpec> From<BlobsBundleV1<E>> for JsonBlobsBundleV1<E> {
fn from(blobs_bundle: BlobsBundleV1<E>) -> Self {
Self {
commitments: blobs_bundle.commitments,
proofs: blobs_bundle.proofs,
blobs: blobs_bundle.blobs,
}
}
}
impl<E: EthSpec> From<JsonBlobsBundleV1<E>> for BlobsBundleV1<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,
}
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]

View File

@@ -45,12 +45,12 @@ use types::beacon_block_body::KzgCommitments;
use types::blob_sidecar::Blobs;
use types::consts::deneb::BLOB_TX_TYPE;
use types::transaction::{AccessTuple, BlobTransaction, EcdsaSignature, SignedBlobTransaction};
use types::Withdrawals;
use types::{AbstractExecPayload, BeaconStateError, ExecPayload, VersionedHash};
use types::{
BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionBlockHash, ExecutionPayload,
ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge, ForkName,
};
use types::{KzgProofs, Withdrawals};
use types::{
ProposerPreparationData, PublicKeyBytes, Signature, SignedBeaconBlock, Slot, Transaction,
Uint256,
@@ -141,22 +141,53 @@ pub enum BlockProposalContents<T: EthSpec, Payload: AbstractExecPayload<T>> {
block_value: Uint256,
kzg_commitments: KzgCommitments<T>,
blobs: Blobs<T>,
proofs: KzgProofs<T>,
},
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> From<GetPayloadResponse<E>>
for BlockProposalContents<E, Payload>
{
fn from(response: GetPayloadResponse<E>) -> Self {
let (execution_payload, block_value, maybe_bundle) = response.into();
match maybe_bundle {
Some(bundle) => Self::PayloadAndBlobs {
payload: execution_payload.into(),
block_value,
kzg_commitments: bundle.commitments,
blobs: bundle.blobs,
proofs: bundle.proofs,
},
None => Self::Payload {
payload: execution_payload.into(),
block_value,
},
}
}
}
#[allow(clippy::type_complexity)]
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Payload> {
pub fn deconstruct(self) -> (Payload, Option<KzgCommitments<T>>, Option<Blobs<T>>) {
pub fn deconstruct(
self,
) -> (
Payload,
Option<KzgCommitments<T>>,
Option<Blobs<T>>,
Option<KzgProofs<T>>,
) {
match self {
Self::Payload {
payload,
block_value: _,
} => (payload, None, None),
} => (payload, None, None, None),
Self::PayloadAndBlobs {
payload,
block_value: _,
kzg_commitments,
blobs,
} => (payload, Some(kzg_commitments), Some(blobs)),
proofs,
} => (payload, Some(kzg_commitments), Some(blobs), Some(proofs)),
}
}
@@ -171,6 +202,7 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
block_value: _,
kzg_commitments: _,
blobs: _,
proofs: _,
} => payload,
}
}
@@ -185,6 +217,7 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
block_value: _,
kzg_commitments: _,
blobs: _,
proofs: _,
} => payload,
}
}
@@ -199,6 +232,7 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
block_value,
kzg_commitments: _,
blobs: _,
proofs: _,
} => block_value,
}
}
@@ -215,6 +249,7 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
block_value: Uint256::zero(),
blobs: VariableList::default(),
kzg_commitments: VariableList::default(),
proofs: VariableList::default(),
},
})
}
@@ -1116,25 +1151,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
}
};
let blob_fut = async {
match current_fork {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
None
}
ForkName::Deneb => {
debug!(
self.log(),
"Issuing engine_getBlobsBundle";
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
"prev_randao" => ?payload_attributes.prev_randao(),
"timestamp" => payload_attributes.timestamp(),
"parent_hash" => ?parent_hash,
);
Some(engine.api.get_blobs_bundle_v1::<T>(payload_id).await)
}
}
};
let payload_fut = async {
let payload_response = async {
debug!(
self.log(),
"Issuing engine_getPayload";
@@ -1144,45 +1161,30 @@ impl<T: EthSpec> ExecutionLayer<T> {
"parent_hash" => ?parent_hash,
);
engine.api.get_payload::<T>(current_fork, payload_id).await
};
let (blob, payload_response) = tokio::join!(blob_fut, payload_fut);
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()
})?;
if let Some(blob) = blob.transpose()? {
// FIXME(sean) cache blobs
Ok(BlockProposalContents::PayloadAndBlobs {
payload: execution_payload.into(),
block_value,
blobs: blob.blobs,
kzg_commitments: blob.kzgs,
})
} else {
Ok(BlockProposalContents::Payload {
payload: execution_payload.into(),
block_value,
})
}.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 f(self, payload_response.execution_payload_ref()).is_some() {
warn!(
self.log(),
"Duplicate payload cached, this might indicate redundant proposal \
attempts."
);
}
Ok(payload_response.into())
})
.await
.map_err(Box::new)

View File

@@ -6,15 +6,21 @@ use crate::{
},
ExecutionBlock, PayloadAttributes, PayloadId, PayloadStatusV1, PayloadStatusV1Status,
},
ExecutionBlockWithTransactions,
BlobsBundleV1, ExecutionBlockWithTransactions,
};
use kzg::{Kzg, BYTES_PER_BLOB, BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB};
use rand::RngCore;
use serde::{Deserialize, Serialize};
use ssz::Encode;
use std::collections::HashMap;
use std::sync::Arc;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
use types::transaction::{BlobTransaction, EcdsaSignature, SignedBlobTransaction};
use types::{
EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadDeneb,
ExecutionPayloadMerge, ForkName, Hash256, Uint256,
Blob, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella,
ExecutionPayloadDeneb, ExecutionPayloadMerge, ForkName, Hash256, Transaction, Transactions,
Uint256,
};
const GAS_LIMIT: u64 = 16384;
@@ -119,6 +125,11 @@ pub struct ExecutionBlockGenerator<T: EthSpec> {
*/
pub shanghai_time: Option<u64>, // withdrawals
pub deneb_time: Option<u64>, // 4844
/*
* deneb stuff
*/
pub blobs_bundles: HashMap<PayloadId, BlobsBundleV1<T>>,
pub kzg: Option<Arc<Kzg>>,
}
impl<T: EthSpec> ExecutionBlockGenerator<T> {
@@ -128,6 +139,7 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
terminal_block_hash: ExecutionBlockHash,
shanghai_time: Option<u64>,
deneb_time: Option<u64>,
kzg: Option<Kzg>,
) -> Self {
let mut gen = Self {
head_block: <_>::default(),
@@ -142,6 +154,8 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
payload_ids: <_>::default(),
shanghai_time,
deneb_time,
blobs_bundles: <_>::default(),
kzg: kzg.map(Arc::new),
};
gen.insert_pow_block(0).unwrap();
@@ -394,6 +408,11 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
self.payload_ids.get(id).cloned()
}
pub fn get_blobs_bundle(&mut self, id: &PayloadId) -> Option<BlobsBundleV1<T>> {
// remove it to free memory
self.blobs_bundles.remove(id)
}
pub fn new_payload(&mut self, payload: ExecutionPayload<T>) -> PayloadStatusV1 {
let parent = if let Some(parent) = self.blocks.get(&payload.parent_hash()) {
parent
@@ -561,6 +580,22 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
}
};
match execution_payload.fork_name() {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {}
ForkName::Deneb => {
// get random number between 0 and Max Blobs
let num_blobs = rand::random::<usize>() % T::max_blobs_per_block();
let (bundle, transactions) = self.generate_random_blobs(num_blobs)?;
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());
@@ -590,6 +625,88 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
payload_id: id.map(Into::into),
})
}
fn generate_random_blobs(
&self,
n_blobs: usize,
) -> Result<(BlobsBundleV1<T>, Transactions<T>), String> {
let mut bundle = BlobsBundleV1::<T>::default();
let mut transactions = vec![];
for blob_index in 0..n_blobs {
// fill a vector with random bytes
let mut blob_bytes = [0u8; BYTES_PER_BLOB];
rand::thread_rng().fill_bytes(&mut blob_bytes);
// Ensure that the blob is canonical by ensuring that
// each field element contained in the blob is < BLS_MODULUS
for i in 0..FIELD_ELEMENTS_PER_BLOB {
blob_bytes[i * BYTES_PER_FIELD_ELEMENT + BYTES_PER_FIELD_ELEMENT - 1] = 0;
}
let blob = Blob::<T>::new(Vec::from(blob_bytes))
.map_err(|e| format!("error constructing random blob: {:?}", e))?;
let commitment = self
.kzg
.as_ref()
.ok_or("kzg not initialized")?
.blob_to_kzg_commitment(blob_bytes.into())
.map_err(|e| format!("error computing kzg commitment: {:?}", e))?;
let proof = self
.kzg
.as_ref()
.ok_or("kzg not initialized")?
.compute_blob_kzg_proof(blob_bytes.into(), commitment)
.map_err(|e| format!("error computing kzg proof: {:?}", e))?;
let versioned_hash = commitment.calculate_versioned_hash();
let blob_transaction = BlobTransaction {
chain_id: Default::default(),
nonce: 0,
max_priority_fee_per_gas: Default::default(),
max_fee_per_gas: Default::default(),
gas: 100000,
to: None,
value: Default::default(),
data: Default::default(),
access_list: Default::default(),
max_fee_per_data_gas: Default::default(),
versioned_hashes: vec![versioned_hash].into(),
};
let bad_signature = EcdsaSignature {
y_parity: false,
r: Uint256::from(0),
s: Uint256::from(0),
};
let signed_blob_transaction = SignedBlobTransaction {
message: blob_transaction,
signature: bad_signature,
};
// calculate transaction bytes
let tx_bytes = [0x05u8]
.into_iter()
.chain(signed_blob_transaction.as_ssz_bytes().into_iter())
.collect::<Vec<_>>();
let tx = Transaction::<T::MaxBytesPerTransaction>::from(tx_bytes);
transactions.push(tx);
bundle
.blobs
.push(blob)
.map_err(|_| format!("blobs are full, blob index: {:?}", blob_index))?;
bundle
.commitments
.push(commitment)
.map_err(|_| format!("blobs are full, blob index: {:?}", blob_index))?;
bundle
.proofs
.push(proof)
.map_err(|_| format!("blobs are full, blob index: {:?}", blob_index))?;
}
Ok((bundle, transactions.into()))
}
}
fn payload_id_from_u64(n: u64) -> PayloadId {
@@ -650,6 +767,7 @@ mod test {
ExecutionBlockHash::zero(),
None,
None,
None,
);
for i in 0..=TERMINAL_BLOCK {

View File

@@ -224,6 +224,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
@@ -291,6 +293,12 @@ pub async fn handle_rpc<T: EthSpec>(
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(),
})
.unwrap()
}
@@ -324,7 +332,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()

View File

@@ -5,6 +5,7 @@ use crate::{
},
Config, *,
};
use kzg::Kzg;
use sensitive_url::SensitiveUrl;
use task_executor::TaskExecutor;
use tempfile::NamedTempFile;
@@ -33,6 +34,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
spec,
None,
None,
)
}
@@ -46,6 +48,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
jwt_key: Option<JwtKey>,
spec: ChainSpec,
builder_url: Option<SensitiveUrl>,
kzg: Option<Kzg>,
) -> Self {
let handle = executor.handle().unwrap();
@@ -58,6 +61,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
spec.terminal_block_hash,
shanghai_time,
deneb_time,
kzg,
);
let url = SensitiveUrl::parse(&server.url()).unwrap();

View File

@@ -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;
@@ -96,10 +97,15 @@ impl<T: EthSpec> MockServer<T> {
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>,
) -> Self {
let MockExecutionConfig {
jwt_key,
terminal_difficulty,
@@ -117,6 +123,7 @@ impl<T: EthSpec> MockServer<T> {
terminal_block_hash,
shanghai_time,
deneb_time,
kzg,
);
let ctx: Arc<Context<T>> = Arc::new(Context {
@@ -168,6 +175,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,
@@ -176,6 +184,7 @@ impl<T: EthSpec> MockServer<T> {
terminal_block_hash: ExecutionBlockHash,
shanghai_time: Option<u64>,
deneb_time: Option<u64>,
kzg: Option<Kzg>,
) -> Self {
Self::new_with_config(
handle,
@@ -188,6 +197,7 @@ impl<T: EthSpec> MockServer<T> {
shanghai_time,
deneb_time,
},
kzg,
)
}