Merge remote-tracking branch 'origin/deneb-free-blobs' into tree-states

This commit is contained in:
Michael Sproul
2023-09-29 16:34:29 +10:00
253 changed files with 21791 additions and 3122 deletions

View File

@@ -1,10 +1,11 @@
use crate::{state_id::checkpoint_slot_and_execution_optimistic, ExecutionOptimistic};
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped};
use eth2::types::BlobIndicesQuery;
use eth2::types::BlockId as CoreBlockId;
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;
use types::{EthSpec, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot};
use types::{BlobSidecarList, EthSpec, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot};
/// Wraps `eth2::types::BlockId` and provides a simple way to obtain a block or root for a given
/// `BlockId`.
@@ -250,6 +251,37 @@ impl BlockId {
}
}
}
/// Return the `BlobSidecarList` identified by `self`.
pub async fn blob_sidecar_list<T: BeaconChainTypes>(
&self,
chain: &BeaconChain<T>,
) -> Result<BlobSidecarList<T::EthSpec>, warp::Rejection> {
let root = self.root(chain)?.0;
chain
.get_blobs(&root)
.map_err(warp_utils::reject::beacon_chain_error)
}
pub async fn blob_sidecar_list_filtered<T: BeaconChainTypes>(
&self,
indices: BlobIndicesQuery,
chain: &BeaconChain<T>,
) -> Result<BlobSidecarList<T::EthSpec>, warp::Rejection> {
let blob_sidecar_list = self.blob_sidecar_list(chain).await?;
let blob_sidecar_list_filtered = match indices.indices {
Some(vec) => {
let list = blob_sidecar_list
.into_iter()
.filter(|blob_sidecar| vec.contains(&blob_sidecar.index))
.collect();
BlobSidecarList::new(list)
.map_err(|e| warp_utils::reject::custom_server_error(format!("{:?}", e)))?
}
None => blob_sidecar_list,
};
Ok(blob_sidecar_list_filtered)
}
}
impl FromStr for BlockId {

View File

@@ -0,0 +1,62 @@
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<E: EthSpec>(
fork_name: ForkName,
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 => {
if let Some(blob_sidecars) = maybe_blobs {
let block_and_blobs = BeaconBlockAndBlobSidecars {
block,
blob_sidecars,
};
Ok(BlockContents::BlockAndBlobSidecars(block_and_blobs))
} else {
Err(warp_utils::reject::block_production_error(
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,
))
}
}
}
}

View File

@@ -1,8 +1,7 @@
use beacon_chain::store::{metadata::CURRENT_SCHEMA_VERSION, AnchorInfo};
use beacon_chain::store::metadata::CURRENT_SCHEMA_VERSION;
use beacon_chain::{BeaconChain, BeaconChainTypes};
use eth2::lighthouse::DatabaseInfo;
use std::sync::Arc;
use types::SignedBlindedBeaconBlock;
pub fn info<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
@@ -11,25 +10,13 @@ pub fn info<T: BeaconChainTypes>(
let split = store.get_split_info();
let config = store.get_config().clone();
let anchor = store.get_anchor_info();
let blob_info = store.get_blob_info();
Ok(DatabaseInfo {
schema_version: CURRENT_SCHEMA_VERSION.as_u64(),
config,
split,
anchor,
blob_info,
})
}
pub fn historical_blocks<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
blocks: Vec<Arc<SignedBlindedBeaconBlock<T::EthSpec>>>,
) -> Result<AnchorInfo, warp::Rejection> {
chain
.import_historical_block_batch(blocks)
.map_err(warp_utils::reject::beacon_chain_error)?;
let anchor = chain.store.get_anchor_info().ok_or_else(|| {
warp_utils::reject::custom_bad_request("node is not checkpoint synced".to_string())
})?;
Ok(anchor)
}

View File

@@ -10,6 +10,7 @@ mod attester_duties;
mod block_id;
mod block_packing_efficiency;
mod block_rewards;
mod build_block_contents;
mod builder_states;
mod database;
mod metrics;
@@ -38,7 +39,8 @@ use bytes::Bytes;
use directory::DEFAULT_ROOT_DIR;
use eth2::types::{
self as api_types, BroadcastValidation, EndpointVersion, ForkChoice, ForkChoiceNode,
SkipRandaoVerification, ValidatorId, ValidatorStatus,
SignedBlindedBlockContents, SignedBlockContents, SkipRandaoVerification, ValidatorId,
ValidatorStatus,
};
use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage};
use lighthouse_version::version_with_platform;
@@ -74,9 +76,8 @@ use types::{
Attestation, AttestationData, AttestationShufflingId, AttesterSlashing, BeaconStateError,
BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload,
ProposerPreparationData, ProposerSlashing, RelativeEpoch, SignedAggregateAndProof,
SignedBeaconBlock, SignedBlindedBeaconBlock, SignedBlsToExecutionChange,
SignedContributionAndProof, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot,
SyncCommitteeMessage, SyncContributionData,
SignedBlsToExecutionChange, SignedContributionAndProof, SignedValidatorRegistrationData,
SignedVoluntaryExit, Slot, SyncCommitteeMessage, SyncContributionData,
};
use validator::pubkey_to_validator_index;
use version::{
@@ -1287,7 +1288,7 @@ pub fn serve<T: BeaconChainTypes>(
.and(network_tx_filter.clone())
.and(log_filter.clone())
.then(
move |block: Arc<SignedBeaconBlock<T::EthSpec>>,
move |block_contents: SignedBlockContents<T::EthSpec>,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>,
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
@@ -1295,7 +1296,7 @@ pub fn serve<T: BeaconChainTypes>(
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
publish_blocks::publish_block(
None,
ProvenancedBlock::local(block),
ProvenancedBlock::local(block_contents),
chain,
&network_tx,
log,
@@ -1323,16 +1324,16 @@ pub fn serve<T: BeaconChainTypes>(
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
log: Logger| {
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
let block =
SignedBeaconBlock::<T::EthSpec>::from_ssz_bytes(&block_bytes, &chain.spec)
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"invalid SSZ: {e:?}"
))
})?;
let block_contents = SignedBlockContents::<T::EthSpec>::from_ssz_bytes(
&block_bytes,
&chain.spec,
)
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
})?;
publish_blocks::publish_block(
None,
ProvenancedBlock::local(Arc::new(block)),
ProvenancedBlock::local(block_contents),
chain,
&network_tx,
log,
@@ -1356,7 +1357,7 @@ pub fn serve<T: BeaconChainTypes>(
.and(log_filter.clone())
.then(
move |validation_level: api_types::BroadcastValidationQuery,
block: Arc<SignedBeaconBlock<T::EthSpec>>,
block_contents: SignedBlockContents<T::EthSpec>,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>,
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
@@ -1364,7 +1365,7 @@ pub fn serve<T: BeaconChainTypes>(
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
publish_blocks::publish_block(
None,
ProvenancedBlock::local(block),
ProvenancedBlock::local(block_contents),
chain,
&network_tx,
log,
@@ -1394,16 +1395,16 @@ pub fn serve<T: BeaconChainTypes>(
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
log: Logger| {
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
let block =
SignedBeaconBlock::<T::EthSpec>::from_ssz_bytes(&block_bytes, &chain.spec)
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"invalid SSZ: {e:?}"
))
})?;
let block_contents = SignedBlockContents::<T::EthSpec>::from_ssz_bytes(
&block_bytes,
&chain.spec,
)
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
})?;
publish_blocks::publish_block(
None,
ProvenancedBlock::local(Arc::new(block)),
ProvenancedBlock::local(block_contents),
chain,
&network_tx,
log,
@@ -1430,14 +1431,14 @@ pub fn serve<T: BeaconChainTypes>(
.and(network_tx_filter.clone())
.and(log_filter.clone())
.then(
move |block: SignedBlindedBeaconBlock<T::EthSpec>,
move |block_contents: SignedBlindedBlockContents<T::EthSpec>,
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,
@@ -1466,13 +1467,14 @@ pub fn serve<T: BeaconChainTypes>(
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
log: Logger| {
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
let block = SignedBlindedBeaconBlock::<T::EthSpec>::from_ssz_bytes(
&block_bytes,
&chain.spec,
)
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
})?;
let block =
SignedBlockContents::<T::EthSpec, BlindedPayload<_>>::from_ssz_bytes(
&block_bytes,
&chain.spec,
)
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
})?;
publish_blocks::publish_blinded_block(
block,
chain,
@@ -1498,14 +1500,14 @@ pub fn serve<T: BeaconChainTypes>(
.and(log_filter.clone())
.then(
move |validation_level: api_types::BroadcastValidationQuery,
block: SignedBlindedBeaconBlock<T::EthSpec>,
block_contents: SignedBlindedBlockContents<T::EthSpec>,
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,
@@ -1535,13 +1537,14 @@ pub fn serve<T: BeaconChainTypes>(
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
log: Logger| {
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
let block = SignedBlindedBeaconBlock::<T::EthSpec>::from_ssz_bytes(
&block_bytes,
&chain.spec,
)
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
})?;
let block =
SignedBlockContents::<T::EthSpec, BlindedPayload<_>>::from_ssz_bytes(
&block_bytes,
&chain.spec,
)
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
})?;
publish_blocks::publish_blinded_block(
block,
chain,
@@ -1707,6 +1710,47 @@ pub fn serve<T: BeaconChainTypes>(
},
);
/*
* beacon/blob_sidecars
*/
// GET beacon/blob_sidecars/{block_id}
let get_blobs = eth_v1
.and(warp::path("beacon"))
.and(warp::path("blob_sidecars"))
.and(block_id_or_err)
.and(warp::query::<api_types::BlobIndicesQuery>())
.and(warp::path::end())
.and(chain_filter.clone())
.and(warp::header::optional::<api_types::Accept>("accept"))
.and_then(
|block_id: BlockId,
indices: api_types::BlobIndicesQuery,
chain: Arc<BeaconChain<T>>,
accept_header: Option<api_types::Accept>| {
async move {
let blob_sidecar_list_filtered =
block_id.blob_sidecar_list_filtered(indices, &chain).await?;
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.header("Content-Type", "application/octet-stream")
.body(blob_sidecar_list_filtered.as_ssz_bytes().into())
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"failed to create response: {}",
e
))
}),
_ => Ok(warp::reply::json(&api_types::GenericResponse::from(
blob_sidecar_list_filtered,
))
.into_response()),
}
}
},
);
/*
* beacon/pool
*/
@@ -3030,16 +3074,16 @@ pub fn serve<T: BeaconChainTypes>(
if query.skip_randao_verification == SkipRandaoVerification::Yes {
if !randao_reveal.is_infinity() {
return Err(warp_utils::reject::custom_bad_request(
"randao_reveal must be point-at-infinity if verification is skipped"
.into(),
));
"randao_reveal must be point-at-infinity if verification is skipped"
.into(),
));
}
ProduceBlockVerification::NoVerification
} else {
ProduceBlockVerification::VerifyRandao
};
let (block, _) = chain
let (block, _, maybe_blobs) = chain
.produce_block_with_verification::<FullPayload<T::EthSpec>>(
randao_reveal,
slot,
@@ -3053,11 +3097,14 @@ pub fn serve<T: BeaconChainTypes>(
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;
let block_contents =
build_block_contents::build_block_contents(fork_name, block, maybe_blobs)?;
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.header("Content-Type", "application/octet-stream")
.body(block.as_ssz_bytes().into())
.body(block_contents.as_ssz_bytes().into())
.map(|res: Response<Bytes>| {
add_consensus_version_header(res, fork_name)
})
@@ -3067,7 +3114,7 @@ pub fn serve<T: BeaconChainTypes>(
e
))
}),
_ => fork_versioned_response(endpoint_version, fork_name, block)
_ => 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)),
}
@@ -3117,7 +3164,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,
@@ -3131,11 +3178,17 @@ 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,
)?;
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.header("Content-Type", "application/octet-stream")
.body(block.as_ssz_bytes().into())
.body(block_contents.as_ssz_bytes().into())
.map(|res: Response<Bytes>| {
add_consensus_version_header(res, fork_name)
})
@@ -3146,7 +3199,7 @@ pub fn serve<T: BeaconChainTypes>(
))
}),
// 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)),
}
@@ -4260,31 +4313,6 @@ pub fn serve<T: BeaconChainTypes>(
},
);
// POST lighthouse/database/historical_blocks
let post_lighthouse_database_historical_blocks = database_path
.and(warp::path("historical_blocks"))
.and(warp::path::end())
.and(warp::body::json())
.and(task_spawner_filter.clone())
.and(chain_filter.clone())
.and(log_filter.clone())
.then(
|blocks: Vec<Arc<SignedBlindedBeaconBlock<T::EthSpec>>>,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>,
log: Logger| {
info!(
log,
"Importing historical blocks";
"count" => blocks.len(),
"source" => "http_api"
);
task_spawner.blocking_json_task(Priority::P1, move || {
database::historical_blocks(chain, blocks)
})
},
);
// GET lighthouse/analysis/block_rewards
let get_lighthouse_block_rewards = warp::path("lighthouse")
.and(warp::path("analysis"))
@@ -4518,6 +4546,7 @@ pub fn serve<T: BeaconChainTypes>(
.uor(get_beacon_block_attestations)
.uor(get_beacon_blinded_block)
.uor(get_beacon_block_root)
.uor(get_blobs)
.uor(get_beacon_pool_attestations)
.uor(get_beacon_pool_attester_slashings)
.uor(get_beacon_pool_proposer_slashings)
@@ -4603,7 +4632,6 @@ pub fn serve<T: BeaconChainTypes>(
.uor(post_validator_liveness_epoch)
.uor(post_lighthouse_liveness)
.uor(post_lighthouse_database_reconstruct)
.uor(post_lighthouse_database_historical_blocks)
.uor(post_lighthouse_block_rewards)
.uor(post_lighthouse_ui_validator_metrics)
.uor(post_lighthouse_ui_validator_info)

View File

@@ -1,10 +1,13 @@
use crate::metrics;
use beacon_chain::block_verification_types::{AsBlock, BlockContentsError};
use beacon_chain::validator_monitor::{get_block_delay_ms, timestamp_now};
use beacon_chain::{
BeaconChain, BeaconChainError, BeaconChainTypes, BlockError, IntoGossipVerifiedBlock,
NotifyExecutionLayer,
AvailabilityProcessingStatus, BeaconChain, BeaconChainError, BeaconChainTypes, BlockError,
IntoGossipVerifiedBlockContents, NotifyExecutionLayer,
};
use eth2::types::{BroadcastValidation, ErrorMessage};
use eth2::types::{FullPayloadContents, SignedBlockContents};
use execution_layer::ProvenancedPayload;
use lighthouse_network::PubsubMessage;
use network::NetworkMessage;
@@ -17,12 +20,12 @@ use tokio::sync::mpsc::UnboundedSender;
use tree_hash::TreeHash;
use types::{
AbstractExecPayload, BeaconBlockRef, BlindedPayload, EthSpec, ExecPayload, ExecutionBlockHash,
FullPayload, Hash256, SignedBeaconBlock,
ForkName, FullPayload, FullPayloadMerge, Hash256, SignedBeaconBlock, SignedBlobSidecarList,
};
use warp::http::StatusCode;
use warp::{reply::Response, Rejection, Reply};
pub enum ProvenancedBlock<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>> {
pub enum ProvenancedBlock<T: BeaconChainTypes, B: IntoGossipVerifiedBlockContents<T>> {
/// The payload was built using a local EE.
Local(B, PhantomData<T>),
/// The payload was build using a remote builder (e.g., via a mev-boost
@@ -30,7 +33,7 @@ pub enum ProvenancedBlock<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>> {
Builder(B, PhantomData<T>),
}
impl<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>> ProvenancedBlock<T, B> {
impl<T: BeaconChainTypes, B: IntoGossipVerifiedBlockContents<T>> ProvenancedBlock<T, B> {
pub fn local(block: B) -> Self {
Self::Local(block, PhantomData)
}
@@ -41,7 +44,7 @@ impl<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>> ProvenancedBlock<T, B>
}
/// Handles a request from the HTTP API for full blocks.
pub async fn publish_block<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>>(
pub async fn publish_block<T: BeaconChainTypes, B: IntoGossipVerifiedBlockContents<T>>(
block_root: Option<Hash256>,
provenanced_block: ProvenancedBlock<T, B>,
chain: Arc<BeaconChain<T>>,
@@ -51,16 +54,18 @@ pub async fn publish_block<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>>(
duplicate_status_code: StatusCode,
) -> Result<Response, Rejection> {
let seen_timestamp = timestamp_now();
let (block, is_locally_built_block) = match provenanced_block {
ProvenancedBlock::Local(block, _) => (block, true),
ProvenancedBlock::Builder(block, _) => (block, false),
let (block_contents, is_locally_built_block) = match provenanced_block {
ProvenancedBlock::Local(block_contents, _) => (block_contents, true),
ProvenancedBlock::Builder(block_contents, _) => (block_contents, false),
};
let beacon_block = block.inner();
let delay = get_block_delay_ms(seen_timestamp, beacon_block.message(), &chain.slot_clock);
debug!(log, "Signed block received in HTTP API"; "slot" => beacon_block.slot());
let block = block_contents.inner_block();
let delay = get_block_delay_ms(seen_timestamp, block.message(), &chain.slot_clock);
debug!(log, "Signed block received in HTTP API"; "slot" => block.slot());
/* actually publish a block */
let publish_block = move |block: Arc<SignedBeaconBlock<T::EthSpec>>,
blobs_opt: Option<SignedBlobSidecarList<T::EthSpec>>,
sender,
log,
seen_timestamp| {
@@ -70,61 +75,101 @@ pub async fn publish_block<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>>(
.unwrap_or_else(|| Duration::from_secs(0));
info!(log, "Signed block published to network via HTTP API"; "slot" => block.slot(), "publish_delay" => ?publish_delay);
let message = PubsubMessage::BeaconBlock(block);
crate::publish_pubsub_message(&sender, message)
.map_err(|_| BeaconChainError::UnableToPublish.into())
// Send the block, regardless of whether or not it is valid. The API
// specification is very clear that this is the desired behaviour.
match block.as_ref() {
SignedBeaconBlock::Base(_)
| SignedBeaconBlock::Altair(_)
| SignedBeaconBlock::Merge(_)
| SignedBeaconBlock::Capella(_) => {
crate::publish_pubsub_message(&sender, PubsubMessage::BeaconBlock(block.clone()))
.map_err(|_| BlockError::BeaconChainError(BeaconChainError::UnableToPublish))?;
}
SignedBeaconBlock::Deneb(_) => {
crate::publish_pubsub_message(&sender, PubsubMessage::BeaconBlock(block.clone()))
.map_err(|_| BlockError::BeaconChainError(BeaconChainError::UnableToPublish))?;
if let Some(signed_blobs) = blobs_opt {
for (blob_index, blob) in signed_blobs.into_iter().enumerate() {
crate::publish_pubsub_message(
&sender,
PubsubMessage::BlobSidecar(Box::new((blob_index as u64, blob))),
)
.map_err(|_| {
BlockError::BeaconChainError(BeaconChainError::UnableToPublish)
})?;
}
}
}
};
Ok(())
};
/* only publish if gossip- and consensus-valid and equivocation-free */
let chain_clone = chain.clone();
let slot = block.message().slot();
let proposer_index = block.message().proposer_index();
let sender_clone = network_tx.clone();
let log_clone = log.clone();
// We can clone this because the blobs are `Arc`'d in `BlockContents`, but the block is not,
// so we avoid cloning the block at this point.
let blobs_opt = block_contents.inner_blobs();
/* if we can form a `GossipVerifiedBlock`, we've passed our basic gossip checks */
let gossip_verified_block = match block.into_gossip_verified_block(&chain) {
Ok(b) => b,
Err(BlockError::BlockIsAlreadyKnown) => {
// Allow the status code for duplicate blocks to be overridden based on config.
return Ok(warp::reply::with_status(
warp::reply::json(&ErrorMessage {
code: duplicate_status_code.as_u16(),
message: "duplicate block".to_string(),
stacktraces: vec![],
}),
duplicate_status_code,
)
.into_response());
}
Err(e) => {
warn!(
log,
"Not publishing block - not gossip verified";
"slot" => beacon_block.slot(),
"error" => ?e
);
return Err(warp_utils::reject::custom_bad_request(e.to_string()));
}
};
let (gossip_verified_block, gossip_verified_blobs) =
match block_contents.into_gossip_verified_block(&chain) {
Ok(b) => b,
Err(BlockContentsError::BlockError(BlockError::BlockIsAlreadyKnown)) => {
// Allow the status code for duplicate blocks to be overridden based on config.
return Ok(warp::reply::with_status(
warp::reply::json(&ErrorMessage {
code: duplicate_status_code.as_u16(),
message: "duplicate block".to_string(),
stacktraces: vec![],
}),
duplicate_status_code,
)
.into_response());
}
Err(e) => {
warn!(
log,
"Not publishing block - not gossip verified";
"slot" => slot,
"error" => ?e
);
return Err(warp_utils::reject::custom_bad_request(e.to_string()));
}
};
// Clone here, so we can take advantage of the `Arc`. The block in `BlockContents` is not,
// `Arc`'d but blobs are.
let block = gossip_verified_block.block.block_cloned();
let block_root = block_root.unwrap_or(gossip_verified_block.block_root);
if let BroadcastValidation::Gossip = validation_level {
publish_block(
beacon_block.clone(),
network_tx.clone(),
block.clone(),
blobs_opt.clone(),
sender_clone.clone(),
log.clone(),
seen_timestamp,
)
.map_err(|_| warp_utils::reject::custom_server_error("unable to publish".into()))?;
}
/* only publish if gossip- and consensus-valid and equivocation-free */
let chain_clone = chain.clone();
let block_clone = beacon_block.clone();
let log_clone = log.clone();
let sender_clone = network_tx.clone();
let block_clone = block.clone();
let publish_fn = move || match validation_level {
BroadcastValidation::Gossip => Ok(()),
BroadcastValidation::Consensus => {
publish_block(block_clone, sender_clone, log_clone, seen_timestamp)
}
BroadcastValidation::Consensus => publish_block(
block_clone,
blobs_opt,
sender_clone,
log_clone,
seen_timestamp,
),
BroadcastValidation::ConsensusAndEquivocation => {
if chain_clone
.observed_block_producers
@@ -140,11 +185,27 @@ pub async fn publish_block<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>>(
);
Err(BlockError::Slashable)
} else {
publish_block(block_clone, sender_clone, log_clone, seen_timestamp)
publish_block(
block_clone,
blobs_opt,
sender_clone,
log_clone,
seen_timestamp,
)
}
}
};
if let Some(gossip_verified_blobs) = gossip_verified_blobs {
for blob in gossip_verified_blobs {
if let Err(e) = chain.process_blob(blob).await {
return Err(warp_utils::reject::custom_bad_request(format!(
"Invalid blob: {e}"
)));
}
}
}
match chain
.process_block(
block_root,
@@ -154,20 +215,20 @@ pub async fn publish_block<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>>(
)
.await
{
Ok(root) => {
Ok(AvailabilityProcessingStatus::Imported(root)) => {
info!(
log,
"Valid block from HTTP API";
"block_delay" => ?delay,
"root" => format!("{}", root),
"proposer_index" => beacon_block.message().proposer_index(),
"slot" => beacon_block.slot(),
"proposer_index" => proposer_index,
"slot" =>slot,
);
// Notify the validator monitor.
chain.validator_monitor.read().register_api_block(
seen_timestamp,
beacon_block.message(),
block.message(),
root,
&chain.slot_clock,
);
@@ -180,17 +241,23 @@ pub async fn publish_block<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>>(
// blocks built with builders we consider the broadcast time to be
// when the blinded block is published to the builder.
if is_locally_built_block {
late_block_logging(
&chain,
seen_timestamp,
beacon_block.message(),
root,
"local",
&log,
)
late_block_logging(&chain, seen_timestamp, block.message(), root, "local", &log)
}
Ok(warp::reply().into_response())
}
Ok(AvailabilityProcessingStatus::MissingComponents(_, block_root)) => {
let msg = format!("Missing parts of block with root {:?}", block_root);
if let BroadcastValidation::Gossip = validation_level {
Err(warp_utils::reject::broadcast_without_import(msg))
} else {
error!(
log,
"Invalid block provided to HTTP API";
"reason" => &msg
);
Err(warp_utils::reject::custom_bad_request(msg))
}
}
Err(BlockError::BeaconChainError(BeaconChainError::UnableToPublish)) => {
Err(warp_utils::reject::custom_server_error(
"unable to publish to network channel".to_string(),
@@ -220,16 +287,16 @@ pub async fn publish_block<T: BeaconChainTypes, B: IntoGossipVerifiedBlock<T>>(
/// 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: SignedBeaconBlock<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,
duplicate_status_code: StatusCode,
) -> Result<Response, Rejection> {
let block_root = block.canonical_root();
let full_block: ProvenancedBlock<T, Arc<SignedBeaconBlock<T::EthSpec>>> =
reconstruct_block(chain.clone(), block_root, block, log.clone()).await?;
let block_root = block_contents.signed_block().canonical_root();
let full_block: ProvenancedBlock<T, SignedBlockContents<T::EthSpec>> =
reconstruct_block(chain.clone(), block_root, block_contents, log.clone()).await?;
publish_block::<T, _>(
Some(block_root),
full_block,
@@ -248,28 +315,28 @@ pub async fn publish_blinded_block<T: BeaconChainTypes>(
pub async fn reconstruct_block<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
block_root: Hash256,
block: SignedBeaconBlock<T::EthSpec, BlindedPayload<T::EthSpec>>,
block_contents: SignedBlockContents<T::EthSpec, BlindedPayload<T::EthSpec>>,
log: Logger,
) -> Result<ProvenancedBlock<T, Arc<SignedBeaconBlock<T::EthSpec>>>, Rejection> {
) -> Result<ProvenancedBlock<T, SignedBlockContents<T::EthSpec>>, Rejection> {
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 payload = FullPayload::default_at_fork(
chain
.spec
.fork_name_at_epoch(block.slot().epoch(T::EthSpec::slots_per_epoch())),
)
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"Default payload construction error: {e:?}"
))
})?
.into();
ProvenancedPayload::Local(payload)
let full_payload_contents = if payload_header.block_hash() == ExecutionBlockHash::zero() {
let fork_name = chain
.spec
.fork_name_at_epoch(block.slot().epoch(T::EthSpec::slots_per_epoch()));
if fork_name == ForkName::Merge {
let payload: FullPayload<T::EthSpec> = FullPayloadMerge::default().into();
ProvenancedPayload::Local(FullPayloadContents::Payload(payload.into()))
} else {
Err(warp_utils::reject::custom_server_error(
"Failed to construct full payload - block hash must be non-zero after Bellatrix.".to_string()
))?
}
// 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())
@@ -293,7 +360,7 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
);
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!(
@@ -305,7 +372,7 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
ProvenancedPayload::Builder(full_payload)
};
Some(full_payload)
Some(full_payload_contents)
} else {
None
};
@@ -313,21 +380,18 @@ 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
.try_into_full_block(None)
.map(Arc::new)
None => block_contents
.try_into_full_block_and_blobs(None)
.map(ProvenancedBlock::local),
Some(ProvenancedPayload::Local(full_payload)) => block
.try_into_full_block(Some(full_payload))
.map(Arc::new)
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
.try_into_full_block(Some(full_payload))
.map(Arc::new)
Some(ProvenancedPayload::Builder(full_payload_contents)) => block_contents
.try_into_full_block_and_blobs(Some(full_payload_contents))
.map(ProvenancedBlock::builder),
}
.ok_or_else(|| {
warp_utils::reject::custom_server_error("Unable to add payload to block".to_string())
.map_err(|e| {
warp_utils::reject::custom_server_error(format!("Unable to add payload to block: {e:?}"))
})
}

View File

@@ -1,8 +1,6 @@
use crate::{Config, Context};
use beacon_chain::{
test_utils::{
BeaconChainHarness, BoxedMutator, Builder as HarnessBuilder, EphemeralHarnessType,
},
test_utils::{BeaconChainHarness, BoxedMutator, Builder, EphemeralHarnessType},
BeaconChain, BeaconChainTypes,
};
use beacon_processor::{BeaconProcessor, BeaconProcessorChannels, BeaconProcessorConfig};
@@ -53,9 +51,8 @@ pub struct ApiServer<E: EthSpec, SFut: Future<Output = ()>> {
pub external_peer_id: PeerId,
}
type Initializer<E> = Box<
dyn FnOnce(HarnessBuilder<EphemeralHarnessType<E>>) -> HarnessBuilder<EphemeralHarnessType<E>>,
>;
type HarnessBuilder<E> = Builder<EphemeralHarnessType<E>>;
type Initializer<E> = Box<dyn FnOnce(HarnessBuilder<E>) -> HarnessBuilder<E>>;
type Mutator<E> = BoxedMutator<E, MemoryStore<E>, MemoryStore<E>>;
impl<E: EthSpec> InteractiveTester<E> {