Builder flow for Deneb & Blobs (#4428)

* Add Deneb builder flow types with generics

* Update validator client `get_blinded_blocks` call to support Deneb

* `produceBlindedBlock` endpoint updates:
- Handle new Deneb BuilderBid response from builder endpoint (new BlindedBlobsBundle type)
- Build BlockContents response (containing kzg_commitments, proof and blinded_blob_sidecars)

* Appease Clippy lint

* Partial implementation of submit blinded block & blobs. Refactor existing `BlobSidecar` related types to support blinded blobs.

* Add associated types for BlockProposal

* Rename `AbstractSidecar` to `Sidecar`

* Remove blob cache as it's no longer necessary

* Remove unnecessary enum variant

* Clean up

* Hanlde unblinded blobs and publish full block contents

* Fix tests

* Add local EL blobs caching in blinded flow

* Remove BlockProposal and move associated Sidecar trait to AbstractExecPayload to simplify changes

* add blob roots associated type

* move raw blobs associated type to sidecar trait

* Fix todos and improve error handling

* Consolidate BlobsBundle from `execution_layer` into `consensus/types`

* Rename RawBlobs, Blobs, and BlobRoots

* Use `BlobRoots` type alias

* Update error message.

Co-authored-by: realbigsean <seananderson33@GMAIL.com>

* update builder bid type

# Conflicts:
#	consensus/types/src/builder_bid.rs

* Fix lint

* remove generic from builder bid

---------

Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
Jimmy Chen
2023-08-10 09:32:49 -04:00
committed by GitHub
co-authored by realbigsean
parent fddd4e4c87
commit 0b7a426946
32 changed files with 1027 additions and 499 deletions
+55 -66
View File
@@ -7,7 +7,6 @@ use crate::attester_cache::{AttesterCache, AttesterCacheKey};
use crate::beacon_block_streamer::{BeaconBlockStreamer, CheckEarlyAttesterCache};
use crate::beacon_proposer_cache::compute_proposer_duties_from_head;
use crate::beacon_proposer_cache::BeaconProposerCache;
use crate::blob_cache::BlobCache;
use crate::blob_verification::{self, GossipBlobError, GossipVerifiedBlob};
use crate::block_times_cache::BlockTimesCache;
use crate::block_verification::POS_PANDA_BANNER;
@@ -67,8 +66,10 @@ use crate::validator_monitor::{
HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOCHS,
};
use crate::validator_pubkey_cache::ValidatorPubkeyCache;
use crate::{kzg_utils, AvailabilityPendingExecutedBlock};
use crate::{metrics, BeaconChainError, BeaconForkChoiceStore, BeaconSnapshot, CachedHead};
use crate::{
kzg_utils, metrics, AvailabilityPendingExecutedBlock, BeaconChainError, BeaconForkChoiceStore,
BeaconSnapshot, CachedHead,
};
use eth2::types::{EventKind, SseBlock, SseExtendedPayloadAttributes, SyncDuty};
use execution_layer::{
BlockProposalContents, BuilderParams, ChainHealth, ExecutionLayer, FailedCondition,
@@ -118,7 +119,7 @@ use task_executor::{ShutdownReason, TaskExecutor};
use tokio_stream::Stream;
use tree_hash::TreeHash;
use types::beacon_state::CloneConfig;
use types::blob_sidecar::{BlobSidecarList, FixedBlobSidecarList};
use types::blob_sidecar::{BlobItems, BlobSidecarList, FixedBlobSidecarList};
use types::consts::deneb::MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS;
use types::*;
@@ -473,12 +474,15 @@ pub struct BeaconChain<T: BeaconChainTypes> {
pub validator_monitor: RwLock<ValidatorMonitor<T::EthSpec>>,
/// The slot at which blocks are downloaded back to.
pub genesis_backfill_slot: Slot,
pub proposal_blob_cache: BlobCache<T::EthSpec>,
pub data_availability_checker: Arc<DataAvailabilityChecker<T>>,
pub kzg: Option<Arc<Kzg<<T::EthSpec as EthSpec>::Kzg>>>,
}
type BeaconBlockAndState<T, Payload> = (BeaconBlock<T, Payload>, BeaconState<T>);
type BeaconBlockAndState<T, Payload> = (
BeaconBlock<T, Payload>,
BeaconState<T>,
Option<SidecarList<T, <Payload as AbstractExecPayload<T>>::Sidecar>>,
);
impl FinalizationAndCanonicity {
pub fn is_finalized(self) -> bool {
@@ -4978,67 +4982,52 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let blobs_verification_timer =
metrics::start_timer(&metrics::BLOCK_PRODUCTION_BLOBS_VERIFICATION_TIMES);
if let (Some(blobs), Some(proofs)) = (blobs_opt, proofs_opt) {
let kzg = self
.kzg
.as_ref()
.ok_or(BlockProductionError::TrustedSetupNotInitialized)?;
let beacon_block_root = block.canonical_root();
let expected_kzg_commitments = block.body().blob_kzg_commitments().map_err(|_| {
BlockProductionError::InvalidBlockVariant(
"DENEB block does not contain kzg commitments".to_string(),
let maybe_sidecar_list = match (blobs_opt, proofs_opt) {
(Some(blobs_or_blobs_roots), Some(proofs)) => {
let expected_kzg_commitments =
block.body().blob_kzg_commitments().map_err(|_| {
BlockProductionError::InvalidBlockVariant(
"deneb block does not contain kzg commitments".to_string(),
)
})?;
if expected_kzg_commitments.len() != blobs_or_blobs_roots.len() {
return Err(BlockProductionError::MissingKzgCommitment(format!(
"Missing KZG commitment for slot {}. Expected {}, got: {}",
block.slot(),
blobs_or_blobs_roots.len(),
expected_kzg_commitments.len()
)));
}
let kzg_proofs = Vec::from(proofs);
if let Some(blobs) = blobs_or_blobs_roots.blobs() {
let kzg = self
.kzg
.as_ref()
.ok_or(BlockProductionError::TrustedSetupNotInitialized)?;
kzg_utils::validate_blobs::<T::EthSpec>(
kzg,
expected_kzg_commitments,
blobs,
&kzg_proofs,
)
.map_err(BlockProductionError::KzgError)?;
}
Some(
Sidecar::build_sidecar(
blobs_or_blobs_roots,
&block,
expected_kzg_commitments,
kzg_proofs,
)
.map_err(BlockProductionError::FailedToBuildBlobSidecars)?,
)
})?;
if expected_kzg_commitments.len() != blobs.len() {
return Err(BlockProductionError::MissingKzgCommitment(format!(
"Missing KZG commitment for slot {}. Expected {}, got: {}",
slot,
blobs.len(),
expected_kzg_commitments.len()
)));
}
let kzg_proofs = Vec::from(proofs);
kzg_utils::validate_blobs::<T::EthSpec>(
kzg.as_ref(),
expected_kzg_commitments,
&blobs,
&kzg_proofs,
)
.map_err(BlockProductionError::KzgError)?;
let blob_sidecars = BlobSidecarList::from(
blobs
.into_iter()
.enumerate()
.map(|(blob_index, blob)| {
let kzg_commitment = expected_kzg_commitments
.get(blob_index)
.expect("KZG commitment should exist for blob");
let kzg_proof = kzg_proofs
.get(blob_index)
.expect("KZG proof should exist for blob");
Ok(Arc::new(BlobSidecar {
block_root: beacon_block_root,
index: blob_index as u64,
slot,
block_parent_root: block.parent_root(),
proposer_index,
blob,
kzg_commitment: *kzg_commitment,
kzg_proof: *kzg_proof,
}))
})
.collect::<Result<Vec<_>, BlockProductionError>>()?,
);
self.proposal_blob_cache
.put(beacon_block_root, blob_sidecars);
}
_ => None,
};
drop(blobs_verification_timer);
@@ -5052,7 +5041,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
"slot" => block.slot()
);
Ok((block, state))
Ok((block, state, maybe_sidecar_list))
}
/// This method must be called whenever an execution engine indicates that a payload is
@@ -1,35 +0,0 @@
use lru::LruCache;
use parking_lot::Mutex;
use types::{BlobSidecarList, EthSpec, Hash256};
pub const DEFAULT_BLOB_CACHE_SIZE: usize = 10;
/// A cache blobs by beacon block root.
pub struct BlobCache<T: EthSpec> {
blobs: Mutex<LruCache<BlobCacheId, BlobSidecarList<T>>>,
}
#[derive(Hash, PartialEq, Eq)]
struct BlobCacheId(Hash256);
impl<T: EthSpec> Default for BlobCache<T> {
fn default() -> Self {
BlobCache {
blobs: Mutex::new(LruCache::new(DEFAULT_BLOB_CACHE_SIZE)),
}
}
}
impl<T: EthSpec> BlobCache<T> {
pub fn put(
&self,
beacon_block: Hash256,
blobs: BlobSidecarList<T>,
) -> Option<BlobSidecarList<T>> {
self.blobs.lock().put(BlobCacheId(beacon_block), blobs)
}
pub fn pop(&self, root: &Hash256) -> Option<BlobSidecarList<T>> {
self.blobs.lock().pop(&BlobCacheId(*root))
}
}
-2
View File
@@ -1,5 +1,4 @@
use crate::beacon_chain::{CanonicalHead, BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, OP_POOL_DB_KEY};
use crate::blob_cache::BlobCache;
use crate::data_availability_checker::DataAvailabilityChecker;
use crate::eth1_chain::{CachingEth1Backend, SszEth1};
use crate::eth1_finalization_cache::Eth1FinalizationCache;
@@ -889,7 +888,6 @@ where
DataAvailabilityChecker::new(slot_clock, kzg.clone(), store, self.spec)
.map_err(|e| format!("Error initializing DataAvailabiltyChecker: {:?}", e))?,
),
proposal_blob_cache: BlobCache::default(),
kzg,
};
+3 -1
View File
@@ -275,20 +275,22 @@ pub enum BlockProductionError {
blob_block_hash: ExecutionBlockHash,
payload_block_hash: ExecutionBlockHash,
},
NoBlobsCached,
FailedToReadFinalizedBlock(store::Error),
MissingFinalizedBlock(Hash256),
BlockTooLarge(usize),
ShuttingDown,
MissingBlobs,
MissingSyncAggregate,
MissingExecutionPayload,
MissingKzgCommitment(String),
MissingKzgProof(String),
TokioJoin(tokio::task::JoinError),
BeaconChain(BeaconChainError),
InvalidPayloadFork,
TrustedSetupNotInitialized,
InvalidBlockVariant(String),
KzgError(kzg::Error),
FailedToBuildBlobSidecars(String),
}
easy_from_to!(BlockProcessingError, BlockProductionError);
-1
View File
@@ -7,7 +7,6 @@ mod beacon_chain;
mod beacon_fork_choice_store;
pub mod beacon_proposer_cache;
mod beacon_snapshot;
pub mod blob_cache;
pub mod blob_verification;
pub mod block_reward;
mod block_times_cache;
+37 -25
View File
@@ -15,7 +15,7 @@ use crate::{
StateSkipConfig,
};
use bls::get_withdrawal_credentials;
use eth2::types::BlockContentsTuple;
use eth2::types::SignedBlockContentsTuple;
use execution_layer::test_utils::generate_genesis_header;
use execution_layer::{
auth::JwtKey,
@@ -50,6 +50,7 @@ use state_processing::{
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
@@ -817,9 +818,28 @@ where
&self,
state: BeaconState<E>,
slot: Slot,
) -> (BlockContentsTuple<E, BlindedPayload<E>>, BeaconState<E>) {
) -> (
SignedBlockContentsTuple<E, BlindedPayload<E>>,
BeaconState<E>,
) {
let (unblinded, new_state) = self.make_block(state, slot).await;
((unblinded.0.into(), unblinded.1), new_state)
let maybe_blinded_blob_sidecars = unblinded.1.map(|blob_sidecar_list| {
VariableList::new(
blob_sidecar_list
.into_iter()
.map(|blob_sidecar| {
let blinded_sidecar: BlindedBlobSidecar = blob_sidecar.message.into();
SignedSidecar {
message: Arc::new(blinded_sidecar),
signature: blob_sidecar.signature,
_phantom: PhantomData,
}
})
.collect(),
)
.unwrap()
});
((unblinded.0.into(), maybe_blinded_blob_sidecars), new_state)
}
/// Returns a newly created block, signed by the proposer for the given slot.
@@ -827,7 +847,7 @@ where
&self,
mut state: BeaconState<E>,
slot: Slot,
) -> (BlockContentsTuple<E, FullPayload<E>>, BeaconState<E>) {
) -> (SignedBlockContentsTuple<E, FullPayload<E>>, BeaconState<E>) {
assert_ne!(slot, 0, "can't produce a block at slot 0");
assert!(slot >= state.slot());
@@ -845,7 +865,7 @@ where
let randao_reveal = self.sign_randao_reveal(&state, proposer_index, slot);
let (block, state) = self
let (block, state, maybe_blob_sidecars) = self
.chain
.produce_block_on_state(
state,
@@ -865,18 +885,14 @@ where
&self.spec,
);
let block_contents: BlockContentsTuple<E, FullPayload<E>> = match &signed_block {
let block_contents: SignedBlockContentsTuple<E, FullPayload<E>> = match &signed_block {
SignedBeaconBlock::Base(_)
| SignedBeaconBlock::Altair(_)
| SignedBeaconBlock::Merge(_)
| SignedBeaconBlock::Capella(_) => (signed_block, None),
SignedBeaconBlock::Deneb(_) => {
if let Some(blobs) = self
.chain
.proposal_blob_cache
.pop(&signed_block.canonical_root())
{
let signed_blobs: SignedBlobSidecarList<E> = Vec::from(blobs)
if let Some(blobs) = maybe_blob_sidecars {
let signed_blobs: SignedSidecarList<E, BlobSidecar<E>> = Vec::from(blobs)
.into_iter()
.map(|blob| {
blob.sign(
@@ -911,7 +927,7 @@ where
&self,
mut state: BeaconState<E>,
slot: Slot,
) -> (BlockContentsTuple<E, FullPayload<E>>, BeaconState<E>) {
) -> (SignedBlockContentsTuple<E, FullPayload<E>>, BeaconState<E>) {
assert_ne!(slot, 0, "can't produce a block at slot 0");
assert!(slot >= state.slot());
@@ -931,7 +947,7 @@ where
let pre_state = state.clone();
let (block, state) = self
let (block, state, maybe_blob_sidecars) = self
.chain
.produce_block_on_state(
state,
@@ -951,18 +967,14 @@ where
&self.spec,
);
let block_contents: BlockContentsTuple<E, FullPayload<E>> = match &signed_block {
let block_contents: SignedBlockContentsTuple<E, FullPayload<E>> = match &signed_block {
SignedBeaconBlock::Base(_)
| SignedBeaconBlock::Altair(_)
| SignedBeaconBlock::Merge(_)
| SignedBeaconBlock::Capella(_) => (signed_block, None),
SignedBeaconBlock::Deneb(_) => {
if let Some(blobs) = self
.chain
.proposal_blob_cache
.pop(&signed_block.canonical_root())
{
let signed_blobs: SignedBlobSidecarList<E> = Vec::from(blobs)
if let Some(blobs) = maybe_blob_sidecars {
let signed_blobs: SignedSidecarList<E, BlobSidecar<E>> = Vec::from(blobs)
.into_iter()
.map(|blob| {
blob.sign(
@@ -1778,7 +1790,7 @@ where
state: BeaconState<E>,
slot: Slot,
block_modifier: impl FnOnce(&mut BeaconBlock<E>),
) -> (BlockContentsTuple<E, FullPayload<E>>, BeaconState<E>) {
) -> (SignedBlockContentsTuple<E, FullPayload<E>>, BeaconState<E>) {
assert_ne!(slot, 0, "can't produce a block at slot 0");
assert!(slot >= state.slot());
@@ -1876,7 +1888,7 @@ where
&self,
slot: Slot,
block_root: Hash256,
block_contents: BlockContentsTuple<E, FullPayload<E>>,
block_contents: SignedBlockContentsTuple<E, FullPayload<E>>,
) -> Result<SignedBeaconBlockHash, BlockError<E>> {
self.set_current_slot(slot);
let (block, blobs) = block_contents;
@@ -1906,7 +1918,7 @@ where
pub async fn process_block_result(
&self,
block_contents: BlockContentsTuple<E, FullPayload<E>>,
block_contents: SignedBlockContentsTuple<E, FullPayload<E>>,
) -> Result<SignedBeaconBlockHash, BlockError<E>> {
let (block, blobs) = block_contents;
// Note: we are just dropping signatures here and skipping signature verification.
@@ -1991,7 +2003,7 @@ where
) -> Result<
(
SignedBeaconBlockHash,
BlockContentsTuple<E, FullPayload<E>>,
SignedBlockContentsTuple<E, FullPayload<E>>,
BeaconState<E>,
),
BlockError<E>,
@@ -126,6 +126,7 @@ async fn get_chain_segment_with_signed_blobs() -> (
.get(&BlobSignatureKey::new(block_root, blob_index))
.unwrap()
.clone(),
_phantom: PhantomData,
}
})
.collect::<Vec<_>>();
+6 -6
View File
@@ -1,8 +1,8 @@
use eth2::types::builder_bid::SignedBuilderBid;
use eth2::types::payload::FullPayloadContents;
use eth2::types::{
AbstractExecPayload, BlindedPayload, EthSpec, ExecutionBlockHash, ExecutionPayload,
ForkVersionedResponse, PublicKeyBytes, SignedBlockContents, SignedValidatorRegistrationData,
Slot,
BlindedPayload, EthSpec, ExecutionBlockHash, ForkVersionedResponse, PublicKeyBytes,
SignedBlockContents, SignedValidatorRegistrationData, Slot,
};
pub use eth2::Error;
use eth2::{ok_or_error, StatusCode};
@@ -141,7 +141,7 @@ impl BuilderHttpClient {
pub async fn post_builder_blinded_blocks<E: EthSpec>(
&self,
blinded_block: &SignedBlockContents<E, BlindedPayload<E>>,
) -> Result<ForkVersionedResponse<ExecutionPayload<E>>, Error> {
) -> Result<ForkVersionedResponse<FullPayloadContents<E>>, Error> {
let mut path = self.server.full.clone();
path.path_segments_mut()
@@ -163,12 +163,12 @@ impl BuilderHttpClient {
}
/// `GET /eth/v1/builder/header`
pub async fn get_builder_header<E: EthSpec, Payload: AbstractExecPayload<E>>(
pub async fn get_builder_header<E: EthSpec>(
&self,
slot: Slot,
parent_hash: ExecutionBlockHash,
pubkey: &PublicKeyBytes,
) -> Result<Option<ForkVersionedResponse<SignedBuilderBid<E, Payload>>>, Error> {
) -> Result<Option<ForkVersionedResponse<SignedBuilderBid<E>>>, Error> {
let mut path = self.server.full.clone();
path.path_segments_mut()
+4 -14
View File
@@ -20,16 +20,14 @@ use state_processing::per_block_processing::deneb::deneb::kzg_commitment_to_vers
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, BeaconBlockRef, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader,
ExecutionPayloadRef, FixedVector, ForkName, Hash256, Transactions, Uint256, VariableList,
Withdrawal, Withdrawals,
};
use types::{
BeaconStateError, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge,
KzgProofs, VersionedHash,
BeaconStateError, BlobsBundle, ExecutionPayloadCapella, ExecutionPayloadDeneb,
ExecutionPayloadMerge, KzgProofs, VersionedHash,
};
pub mod auth;
@@ -64,7 +62,6 @@ pub enum Error {
IncorrectStateVariant,
RequiredMethodUnsupported(&'static str),
UnsupportedForkVariant(String),
BadConversion(String),
RlpDecoderError(rlp::DecoderError),
BlobTxConversionError(BlobTxConversionError),
}
@@ -416,7 +413,7 @@ pub struct GetPayloadResponse<T: EthSpec> {
pub execution_payload: ExecutionPayloadDeneb<T>,
pub block_value: Uint256,
#[superstruct(only(Deneb))]
pub blobs_bundle: BlobsBundleV1<T>,
pub blobs_bundle: BlobsBundle<T>,
#[superstruct(only(Deneb), partial_getter(copy))]
pub should_override_builder: bool,
}
@@ -452,7 +449,7 @@ impl<T: EthSpec> From<GetPayloadResponse<T>> for ExecutionPayload<T> {
}
impl<T: EthSpec> From<GetPayloadResponse<T>>
for (ExecutionPayload<T>, Uint256, Option<BlobsBundleV1<T>>)
for (ExecutionPayload<T>, Uint256, Option<BlobsBundle<T>>)
{
fn from(response: GetPayloadResponse<T>) -> Self {
match response {
@@ -575,13 +572,6 @@ 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>,
}
#[superstruct(
variants(Merge, Capella, Deneb),
variant_attributes(derive(Clone, Debug, PartialEq),),
@@ -3,10 +3,11 @@ use serde::{Deserialize, Serialize};
use strum::EnumString;
use superstruct::superstruct;
use types::beacon_block_body::KzgCommitments;
use types::blob_sidecar::Blobs;
use types::blob_sidecar::BlobsList;
use types::{
EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadDeneb,
ExecutionPayloadMerge, FixedVector, Transactions, Unsigned, VariableList, Withdrawal,
BlobsBundle, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella,
ExecutionPayloadDeneb, ExecutionPayloadMerge, FixedVector, Transactions, Unsigned,
VariableList, Withdrawal,
};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
@@ -441,11 +442,11 @@ 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<E>,
pub blobs: BlobsList<E>,
}
impl<E: EthSpec> From<BlobsBundleV1<E>> for JsonBlobsBundleV1<E> {
fn from(blobs_bundle: BlobsBundleV1<E>) -> Self {
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,
@@ -453,7 +454,7 @@ impl<E: EthSpec> From<BlobsBundleV1<E>> for JsonBlobsBundleV1<E> {
}
}
}
impl<E: EthSpec> From<JsonBlobsBundleV1<E>> for BlobsBundleV1<E> {
impl<E: EthSpec> From<JsonBlobsBundleV1<E>> for BlobsBundle<E> {
fn from(json_blobs_bundle: JsonBlobsBundleV1<E>) -> Self {
Self {
commitments: json_blobs_bundle.commitments,
+122 -98
View File
@@ -13,8 +13,8 @@ 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::SignedBlockContents;
use eth2::types::{builder_bid::SignedBuilderBid, ForkVersionedResponse};
use eth2::types::{builder_bid::SignedBuilderBid, BlobsBundle, ForkVersionedResponse};
use eth2::types::{FullPayloadContents, SignedBlockContents};
use ethers_core::abi::ethereum_types::FromStrRadixErr;
use ethers_core::types::Transaction as EthersTransaction;
use fork_choice::ForkchoiceUpdateParameters;
@@ -41,12 +41,13 @@ use tokio::{
use tokio_stream::wrappers::WatchStream;
use tree_hash::TreeHash;
use types::beacon_block_body::KzgCommitments;
use types::blob_sidecar::Blobs;
use types::KzgProofs;
use types::blob_sidecar::BlobItems;
use types::builder_bid::BuilderBid;
use types::{
AbstractExecPayload, BeaconStateError, BlindedPayload, BlockType, ChainSpec, Epoch,
ExecPayload, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge,
};
use types::{KzgProofs, Sidecar};
use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot, Transaction};
mod block_hash;
@@ -86,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,
@@ -107,6 +142,8 @@ pub enum Error {
InvalidJWTSecret(String),
InvalidForkForPayload,
InvalidPayloadBody(String),
InvalidPayloadConversion,
InvalidBlobConversion(String),
BeaconStateError(BeaconStateError),
}
@@ -131,28 +168,31 @@ pub enum BlockProposalContents<T: EthSpec, Payload: AbstractExecPayload<T>> {
payload: Payload,
block_value: Uint256,
kzg_commitments: KzgCommitments<T>,
blobs: Blobs<T>,
blobs: <Payload::Sidecar as Sidecar<T>>::BlobItems,
proofs: KzgProofs<T>,
},
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> From<GetPayloadResponse<E>>
impl<E: EthSpec, Payload: AbstractExecPayload<E>> TryFrom<GetPayloadResponse<E>>
for BlockProposalContents<E, Payload>
{
fn from(response: GetPayloadResponse<E>) -> Self {
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) => Self::PayloadAndBlobs {
Some(bundle) => Ok(Self::PayloadAndBlobs {
payload: execution_payload.into(),
block_value,
kzg_commitments: bundle.commitments,
blobs: bundle.blobs,
blobs: BlobItems::try_from_blobs(bundle.blobs)
.map_err(Error::InvalidBlobConversion)?,
proofs: bundle.proofs,
},
None => Self::Payload {
}),
None => Ok(Self::Payload {
payload: execution_payload.into(),
block_value,
},
}),
}
}
}
@@ -164,7 +204,7 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
) -> (
Payload,
Option<KzgCommitments<T>>,
Option<Blobs<T>>,
Option<<Payload::Sidecar as Sidecar<T>>::BlobItems>,
Option<KzgProofs<T>>,
) {
match self {
@@ -184,47 +224,20 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
pub fn payload(&self) -> &Payload {
match self {
Self::Payload {
payload,
block_value: _,
} => payload,
Self::PayloadAndBlobs {
payload,
block_value: _,
kzg_commitments: _,
blobs: _,
proofs: _,
} => payload,
Self::Payload { payload, .. } => payload,
Self::PayloadAndBlobs { payload, .. } => payload,
}
}
pub fn to_payload(self) -> Payload {
match self {
Self::Payload {
payload,
block_value: _,
} => payload,
Self::PayloadAndBlobs {
payload,
block_value: _,
kzg_commitments: _,
blobs: _,
proofs: _,
} => payload,
Self::Payload { payload, .. } => payload,
Self::PayloadAndBlobs { payload, .. } => payload,
}
}
pub fn block_value(&self) -> &Uint256 {
match self {
Self::Payload {
payload: _,
block_value,
} => block_value,
Self::PayloadAndBlobs {
payload: _,
block_value,
kzg_commitments: _,
blobs: _,
proofs: _,
} => 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> {
@@ -238,7 +251,7 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
ForkName::Deneb => BlockProposalContents::PayloadAndBlobs {
payload: Payload::default_at_fork(fork_name)?,
block_value: Uint256::zero(),
blobs: VariableList::default(),
blobs: Payload::default_blobs_at_fork(fork_name)?,
kzg_commitments: VariableList::default(),
proofs: VariableList::default(),
},
@@ -285,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: Option<BuilderHttpClient>,
@@ -488,12 +503,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)
}
@@ -791,7 +822,8 @@ impl<T: EthSpec> ExecutionLayer<T> {
current_fork,
)
.await
.map(|get_payload_response| ProvenancedPayload::Local(get_payload_response.into()))
.and_then(GetPayloadResponse::try_into)
.map(ProvenancedPayload::Local)
}
};
@@ -856,7 +888,7 @@ 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 {
@@ -874,7 +906,7 @@ 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(),
},
@@ -897,7 +929,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
"local_block_hash" => ?local.block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(ProvenancedPayload::Local(local.into()))
Ok(ProvenancedPayload::Local(local.try_into()?))
}
(Ok(None), Ok(local)) => {
info!(
@@ -907,10 +939,10 @@ impl<T: EthSpec> ExecutionLayer<T> {
"local_block_hash" => ?local.block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(ProvenancedPayload::Local(local.into()))
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(),
@@ -920,21 +952,21 @@ impl<T: EthSpec> ExecutionLayer<T> {
"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.into()));
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);
percentage_difference_u256(local_value, *relay_value);
if percentage_difference.map_or(false, |percentage| {
percentage
< self
@@ -947,7 +979,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
"local_block_value" => %local_value,
"relay_value" => %relay_value
);
return Ok(ProvenancedPayload::Local(local.into()));
return Ok(ProvenancedPayload::Local(local.try_into()?));
}
} else {
info!(
@@ -968,12 +1000,7 @@ 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,
},
)),
Ok(()) => Ok(ProvenancedPayload::try_from(relay.data.message)?),
Err(reason) if !reason.payload_invalid() => {
info!(
self.log(),
@@ -983,7 +1010,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
"relay_block_hash" => ?header.block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(ProvenancedPayload::Local(local.into()))
Ok(ProvenancedPayload::Local(local.try_into()?))
}
Err(reason) => {
metrics::inc_counter_vec(
@@ -998,12 +1025,12 @@ impl<T: EthSpec> ExecutionLayer<T> {
"relay_block_hash" => ?header.block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(ProvenancedPayload::Local(local.into()))
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(),
@@ -1022,20 +1049,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,
},
)),
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,
},
)),
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,
@@ -1103,7 +1122,8 @@ impl<T: EthSpec> ExecutionLayer<T> {
current_fork,
)
.await
.map(|get_payload_response| ProvenancedPayload::Local(get_payload_response.into()))
.and_then(GetPayloadResponse::try_into)
.map(ProvenancedPayload::Local)
}
/// Get a full payload without caching its result in the execution layer's payload cache.
@@ -1148,7 +1168,10 @@ impl<T: EthSpec> ExecutionLayer<T> {
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
current_fork: ForkName,
f: fn(&ExecutionLayer<T>, ExecutionPayloadRef<T>) -> Option<ExecutionPayload<T>>,
cache_fn: fn(
&ExecutionLayer<T>,
PayloadContentsRefTuple<T>,
) -> Option<FullPayloadContents<T>>,
) -> Result<GetPayloadResponse<T>, Error> {
self.engine()
.request(move |engine| async move {
@@ -1227,7 +1250,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
);
}
if f(self, payload_response.execution_payload_ref()).is_some() {
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 \
@@ -1859,7 +1882,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
&self,
block_root: Hash256,
block: &SignedBlockContents<T, BlindedPayload<T>>,
) -> Result<ExecutionPayload<T>, Error> {
) -> Result<FullPayloadContents<T>, Error> {
debug!(
self.log(),
"Sending block to builder";
@@ -1878,11 +1901,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";
@@ -2025,8 +2049,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>,
@@ -2035,11 +2059,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,
@@ -2053,12 +2077,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 {
@@ -2088,7 +2112,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 {
@@ -2197,8 +2221,8 @@ fn ethers_tx_to_ssz<T: EthSpec>(
fn noop<T: EthSpec>(
_: &ExecutionLayer<T>,
_: ExecutionPayloadRef<T>,
) -> Option<ExecutionPayload<T>> {
_: PayloadContentsRefTuple<T>,
) -> Option<FullPayloadContents<T>> {
None
}
@@ -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,7 +6,7 @@ use crate::{
},
ExecutionBlock, PayloadAttributes, PayloadId, PayloadStatusV1, PayloadStatusV1Status,
},
random_valid_tx, BlobsBundleV1, ExecutionBlockWithTransactions,
random_valid_tx, ExecutionBlockWithTransactions,
};
use kzg::Kzg;
use rand::thread_rng;
@@ -16,9 +16,9 @@ use std::sync::Arc;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
use types::{
BlobSidecar, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella,
ExecutionPayloadDeneb, ExecutionPayloadHeader, ExecutionPayloadMerge, ForkName, Hash256,
Transactions, Uint256,
BlobSidecar, BlobsBundle, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload,
ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadHeader, ExecutionPayloadMerge,
ForkName, Hash256, Transactions, Uint256,
};
use super::DEFAULT_TERMINAL_BLOCK;
@@ -128,7 +128,7 @@ pub struct ExecutionBlockGenerator<T: EthSpec> {
/*
* deneb stuff
*/
pub blobs_bundles: HashMap<PayloadId, BlobsBundleV1<T>>,
pub blobs_bundles: HashMap<PayloadId, BlobsBundle<T>>,
pub kzg: Option<Arc<Kzg<T::Kzg>>>,
}
@@ -406,7 +406,7 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
self.payload_ids.get(id).cloned()
}
pub fn get_blobs_bundle(&mut self, id: &PayloadId) -> Option<BlobsBundleV1<T>> {
pub fn get_blobs_bundle(&mut self, id: &PayloadId) -> Option<BlobsBundle<T>> {
self.blobs_bundles.get(id).cloned()
}
@@ -630,8 +630,8 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
pub fn generate_random_blobs<T: EthSpec>(
n_blobs: usize,
kzg: &Kzg<T::Kzg>,
) -> Result<(BlobsBundleV1<T>, Transactions<T>), String> {
let mut bundle = BlobsBundleV1::<T>::default();
) -> 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(&mut thread_rng(), kzg)?;
@@ -202,7 +202,7 @@ 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()))
);
// TODO: again consider forks
@@ -1,22 +1,25 @@
use beacon_chain::{BeaconChain, BeaconChainTypes, BlockProductionError};
use eth2::types::{BeaconBlockAndBlobSidecars, BlockContents};
use std::sync::Arc;
use types::{AbstractExecPayload, BeaconBlock, ForkName};
use beacon_chain::BlockProductionError;
use eth2::types::{BeaconBlockAndBlobSidecars, BlindedBeaconBlockAndBlobSidecars, BlockContents};
use types::{
BeaconBlock, BlindedBlobSidecarList, BlindedPayload, BlobSidecarList, EthSpec, ForkName,
FullPayload,
};
type Error = warp::reject::Rejection;
type FullBlockContents<E> = BlockContents<E, FullPayload<E>>;
type BlindedBlockContents<E> = BlockContents<E, BlindedPayload<E>>;
pub fn build_block_contents<T: BeaconChainTypes, Payload: AbstractExecPayload<T::EthSpec>>(
pub fn build_block_contents<E: EthSpec>(
fork_name: ForkName,
chain: Arc<BeaconChain<T>>,
block: BeaconBlock<T::EthSpec, Payload>,
) -> Result<BlockContents<T::EthSpec, Payload>, Error> {
block: BeaconBlock<E, FullPayload<E>>,
maybe_blobs: Option<BlobSidecarList<E>>,
) -> Result<FullBlockContents<E>, Error> {
match fork_name {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
Ok(BlockContents::Block(block))
}
ForkName::Deneb => {
let block_root = &block.canonical_root();
if let Some(blob_sidecars) = chain.proposal_blob_cache.pop(block_root) {
if let Some(blob_sidecars) = maybe_blobs {
let block_and_blobs = BeaconBlockAndBlobSidecars {
block,
blob_sidecars,
@@ -25,7 +28,33 @@ pub fn build_block_contents<T: BeaconChainTypes, Payload: AbstractExecPayload<T:
Ok(BlockContents::BlockAndBlobSidecars(block_and_blobs))
} else {
Err(warp_utils::reject::block_production_error(
BlockProductionError::NoBlobsCached,
BlockProductionError::MissingBlobs,
))
}
}
}
}
pub fn build_blinded_block_contents<E: EthSpec>(
fork_name: ForkName,
block: BeaconBlock<E, BlindedPayload<E>>,
maybe_blobs: Option<BlindedBlobSidecarList<E>>,
) -> Result<BlindedBlockContents<E>, Error> {
match fork_name {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
Ok(BlockContents::Block(block))
}
ForkName::Deneb => {
if let Some(blinded_blob_sidecars) = maybe_blobs {
let block_and_blobs = BlindedBeaconBlockAndBlobSidecars {
blinded_block: block,
blinded_blob_sidecars,
};
Ok(BlockContents::BlindedBlockAndBlobSidecars(block_and_blobs))
} else {
Err(warp_utils::reject::block_production_error(
BlockProductionError::MissingBlobs,
))
}
}
+13 -7
View File
@@ -1440,14 +1440,14 @@ pub fn serve<T: BeaconChainTypes>(
.and(network_tx_filter.clone())
.and(log_filter.clone())
.and_then(
|block: SignedBlockContents<T::EthSpec, BlindedPayload<_>>,
|block_contents: SignedBlockContents<T::EthSpec, BlindedPayload<_>>,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>,
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
log: Logger| {
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
publish_blocks::publish_blinded_block(
block,
block_contents,
chain,
&network_tx,
log,
@@ -3065,7 +3065,7 @@ pub fn serve<T: BeaconChainTypes>(
ProduceBlockVerification::VerifyRandao
};
let (block, _) = chain
let (block, _, maybe_blobs) = chain
.produce_block_with_verification::<FullPayload<T::EthSpec>>(
randao_reveal,
slot,
@@ -3080,9 +3080,9 @@ pub fn serve<T: BeaconChainTypes>(
.map_err(inconsistent_fork_rejection)?;
let block_contents =
build_block_contents::build_block_contents(fork_name, chain, block);
build_block_contents::build_block_contents(fork_name, block, maybe_blobs)?;
fork_versioned_response(endpoint_version, fork_name, block_contents?)
fork_versioned_response(endpoint_version, fork_name, block_contents)
.map(|response| warp::reply::json(&response).into_response())
.map(|res| add_consensus_version_header(res, fork_name))
})
@@ -3129,7 +3129,7 @@ pub fn serve<T: BeaconChainTypes>(
ProduceBlockVerification::VerifyRandao
};
let (block, _) = chain
let (block, _, maybe_blobs) = chain
.produce_block_with_verification::<BlindedPayload<T::EthSpec>>(
randao_reveal,
slot,
@@ -3143,8 +3143,14 @@ pub fn serve<T: BeaconChainTypes>(
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;
let block_contents = build_block_contents::build_blinded_block_contents(
fork_name,
block,
maybe_blobs,
)?;
// Pose as a V2 endpoint so we return the fork `version`.
fork_versioned_response(V2, fork_name, block)
fork_versioned_response(V2, fork_name, block_contents)
.map(|response| warp::reply::json(&response).into_response())
.map(|res| add_consensus_version_header(res, fork_name))
})
+21 -34
View File
@@ -7,7 +7,7 @@ use beacon_chain::{
IntoGossipVerifiedBlockContents, NotifyExecutionLayer,
};
use eth2::types::BroadcastValidation;
use eth2::types::SignedBlockContents;
use eth2::types::{FullPayloadContents, SignedBlockContents};
use execution_layer::ProvenancedPayload;
use lighthouse_network::PubsubMessage;
use network::NetworkMessage;
@@ -267,15 +267,15 @@ pub async fn publish_block<T: BeaconChainTypes, B: IntoGossipVerifiedBlockConten
/// Handles a request from the HTTP API for blinded blocks. This converts blinded blocks into full
/// blocks before publishing.
pub async fn publish_blinded_block<T: BeaconChainTypes>(
block: SignedBlockContents<T::EthSpec, BlindedPayload<T::EthSpec>>,
block_contents: SignedBlockContents<T::EthSpec, BlindedPayload<T::EthSpec>>,
chain: Arc<BeaconChain<T>>,
network_tx: &UnboundedSender<NetworkMessage<T::EthSpec>>,
log: Logger,
validation_level: BroadcastValidation,
) -> Result<(), Rejection> {
let block_root = block.signed_block().canonical_root();
let block_root = block_contents.signed_block().canonical_root();
let full_block: ProvenancedBlock<T, SignedBlockContents<T::EthSpec>> =
reconstruct_block(chain.clone(), block_root, block, log.clone()).await?;
reconstruct_block(chain.clone(), block_root, block_contents, log.clone()).await?;
publish_block::<T, _>(
Some(block_root),
full_block,
@@ -293,25 +293,21 @@ pub async fn publish_blinded_block<T: BeaconChainTypes>(
pub async fn reconstruct_block<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
block_root: Hash256,
block: SignedBlockContents<T::EthSpec, BlindedPayload<T::EthSpec>>,
block_contents: SignedBlockContents<T::EthSpec, BlindedPayload<T::EthSpec>>,
log: Logger,
) -> Result<ProvenancedBlock<T, SignedBlockContents<T::EthSpec>>, Rejection> {
let full_payload_opt = if let Ok(payload_header) =
block.signed_block().message().body().execution_payload()
{
let block = block_contents.signed_block();
let full_payload_opt = if let Ok(payload_header) = block.message().body().execution_payload() {
let el = chain.execution_layer.as_ref().ok_or_else(|| {
warp_utils::reject::custom_server_error("Missing execution layer".to_string())
})?;
// If the execution block hash is zero, use an empty payload.
let full_payload = if payload_header.block_hash() == ExecutionBlockHash::zero() {
let full_payload_contents = if payload_header.block_hash() == ExecutionBlockHash::zero() {
let payload = FullPayload::default_at_fork(
chain.spec.fork_name_at_epoch(
block
.signed_block()
.slot()
.epoch(T::EthSpec::slots_per_epoch()),
),
chain
.spec
.fork_name_at_epoch(block.slot().epoch(T::EthSpec::slots_per_epoch())),
)
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
@@ -319,7 +315,7 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
))
})?
.into();
ProvenancedPayload::Local(payload)
ProvenancedPayload::Local(FullPayloadContents::Payload(payload))
// If we already have an execution payload with this transactions root cached, use it.
} else if let Some(cached_payload) =
el.get_payload_by_root(&payload_header.tree_hash_root())
@@ -336,14 +332,14 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
late_block_logging(
&chain,
timestamp_now(),
block.signed_block().message(),
block.message(),
block_root,
"builder",
&log,
);
let full_payload = el
.propose_blinded_beacon_block(block_root, &block)
.propose_blinded_beacon_block(block_root, &block_contents)
.await
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
@@ -355,7 +351,7 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
ProvenancedPayload::Builder(full_payload)
};
Some(full_payload)
Some(full_payload_contents)
} else {
None
};
@@ -363,23 +359,14 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
match full_payload_opt {
// A block without a payload is pre-merge and we consider it locally
// built.
None => block
.deconstruct()
.0
.try_into_full_block(None)
.map(SignedBlockContents::Block)
None => block_contents
.try_into_full_block_and_blobs(None)
.map(ProvenancedBlock::local),
Some(ProvenancedPayload::Local(full_payload)) => block
.deconstruct()
.0
.try_into_full_block(Some(full_payload))
.map(SignedBlockContents::Block)
Some(ProvenancedPayload::Local(full_payload_contents)) => block_contents
.try_into_full_block_and_blobs(Some(full_payload_contents))
.map(ProvenancedBlock::local),
Some(ProvenancedPayload::Builder(full_payload)) => block
.deconstruct()
.0
.try_into_full_block(Some(full_payload))
.map(SignedBlockContents::Block)
Some(ProvenancedPayload::Builder(full_payload_contents)) => block_contents
.try_into_full_block_and_blobs(Some(full_payload_contents))
.map(ProvenancedBlock::builder),
}
.ok_or_else(|| {
+31 -5
View File
@@ -2576,12 +2576,16 @@ impl ApiTester {
.get_validator_blinded_blocks::<E, Payload>(slot, &randao_reveal, None)
.await
.unwrap()
.data;
.data
.deconstruct()
.0;
let signed_block = block.sign(&sk, &fork, genesis_validators_root, &self.chain.spec);
let signed_block_contents =
SignedBlockContents::<E, Payload>::Block(signed_block.clone());
self.client
.post_beacon_blinded_blocks(&signed_block)
.post_beacon_blinded_blocks(&signed_block_contents)
.await
.unwrap();
@@ -2636,7 +2640,9 @@ impl ApiTester {
.get_validator_blinded_blocks::<E, Payload>(slot, &randao_reveal, None)
.await
.unwrap()
.data;
.data
.deconstruct()
.0;
let signed_block = block.sign(&sk, &fork, genesis_validators_root, &self.chain.spec);
@@ -2659,7 +2665,7 @@ impl ApiTester {
for _ in 0..E::slots_per_epoch() {
let slot = self.chain.slot().unwrap();
let block = self
let block_contents = self
.client
.get_validator_blinded_blocks_modular::<E, Payload>(
slot,
@@ -2670,7 +2676,7 @@ impl ApiTester {
.await
.unwrap()
.data;
assert_eq!(block.slot(), slot);
assert_eq!(block_contents.block().slot(), slot);
self.chain.slot_clock.set_slot(slot.as_u64() + 1);
}
@@ -3206,6 +3212,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3246,6 +3253,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3289,6 +3297,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3338,6 +3347,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3386,6 +3396,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3433,6 +3444,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3479,6 +3491,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3515,6 +3528,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3552,6 +3566,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3595,6 +3610,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3624,6 +3640,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3673,6 +3690,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3712,6 +3730,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3755,6 +3774,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3796,6 +3816,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3833,6 +3854,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3870,6 +3892,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3907,6 +3930,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3957,6 +3981,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -3999,6 +4024,7 @@ impl ApiTester {
.await
.unwrap()
.data
.block()
.body()
.execution_payload()
.unwrap()
@@ -12,7 +12,6 @@ use beacon_chain::builder::Witness;
use beacon_chain::eth1_chain::CachingEth1Backend;
use beacon_chain::test_utils::{build_log, BeaconChainHarness, EphemeralHarnessType};
use beacon_processor::WorkEvent;
use execution_layer::BlobsBundleV1;
use lighthouse_network::rpc::RPCResponseErrorCode;
use lighthouse_network::{NetworkGlobals, Request};
use slot_clock::{ManualSlotClock, SlotClock, TestingSlotClock};
@@ -21,8 +20,8 @@ use tokio::sync::mpsc;
use types::{
map_fork_name, map_fork_name_with,
test_utils::{SeedableRng, TestRandom, XorShiftRng},
BeaconBlock, BlobSidecar, EthSpec, ForkName, FullPayloadDeneb, MinimalEthSpec as E,
SignedBeaconBlock,
BeaconBlock, BlobSidecar, BlobsBundle, EthSpec, ForkName, FullPayloadDeneb,
MinimalEthSpec as E, SignedBeaconBlock,
};
type T = Witness<ManualSlotClock, CachingEth1Backend<E>, E, MemoryStore<E>, MemoryStore<E>>;
@@ -126,7 +125,7 @@ impl TestRig {
}
message.body.blob_kzg_commitments = bundle.commitments.clone();
let BlobsBundleV1 {
let BlobsBundle {
commitments,
proofs,
blobs,