Skip to content

merge queue: embarking unstable (2aae08a), #7778 and #7777 together #7800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
102 changes: 100 additions & 2 deletions beacon_node/builder_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ impl BuilderHttpClient {
}

/// `POST /eth/v1/builder/blinded_blocks` with SSZ serialized request body
pub async fn post_builder_blinded_blocks_ssz<E: EthSpec>(
pub async fn post_builder_blinded_blocks_v1_ssz<E: EthSpec>(
&self,
blinded_block: &SignedBlindedBeaconBlock<E>,
) -> Result<FullPayloadContents<E>, Error> {
Expand Down Expand Up @@ -340,8 +340,58 @@ impl BuilderHttpClient {
.map_err(Error::InvalidSsz)
}

/// `POST /eth/v2/builder/blinded_blocks` with SSZ serialized request body
pub async fn post_builder_blinded_blocks_v2_ssz<E: EthSpec>(
&self,
blinded_block: &SignedBlindedBeaconBlock<E>,
) -> Result<(), Error> {
let mut path = self.server.full.clone();

let body = blinded_block.as_ssz_bytes();

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("eth")
.push("v2")
.push("builder")
.push("blinded_blocks");

let mut headers = HeaderMap::new();
headers.insert(
CONSENSUS_VERSION_HEADER,
HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string())
.map_err(|e| Error::InvalidHeaders(format!("{}", e)))?,
);
headers.insert(
CONTENT_TYPE_HEADER,
HeaderValue::from_str(SSZ_CONTENT_TYPE_HEADER)
.map_err(|e| Error::InvalidHeaders(format!("{}", e)))?,
);
headers.insert(
ACCEPT,
HeaderValue::from_str(PREFERENCE_ACCEPT_VALUE)
.map_err(|e| Error::InvalidHeaders(format!("{}", e)))?,
);

let result = self
.post_ssz_with_raw_response(
path,
body,
headers,
Some(self.timeouts.post_blinded_blocks),
)
.await?;

if result.status() == StatusCode::ACCEPTED {
Ok(())
} else {
// ACCEPTED is the only valid status code response
Err(Error::StatusCode(result.status()))
}
}

/// `POST /eth/v1/builder/blinded_blocks`
pub async fn post_builder_blinded_blocks<E: EthSpec>(
pub async fn post_builder_blinded_blocks_v1<E: EthSpec>(
&self,
blinded_block: &SignedBlindedBeaconBlock<E>,
) -> Result<ForkVersionedResponse<FullPayloadContents<E>>, Error> {
Expand Down Expand Up @@ -383,6 +433,54 @@ impl BuilderHttpClient {
.await?)
}

/// `POST /eth/v2/builder/blinded_blocks`
pub async fn post_builder_blinded_blocks_v2<E: EthSpec>(
&self,
blinded_block: &SignedBlindedBeaconBlock<E>,
) -> Result<(), Error> {
let mut path = self.server.full.clone();

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("eth")
.push("v2")
.push("builder")
.push("blinded_blocks");

let mut headers = HeaderMap::new();
headers.insert(
CONSENSUS_VERSION_HEADER,
HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string())
.map_err(|e| Error::InvalidHeaders(format!("{}", e)))?,
);
headers.insert(
CONTENT_TYPE_HEADER,
HeaderValue::from_str(JSON_CONTENT_TYPE_HEADER)
.map_err(|e| Error::InvalidHeaders(format!("{}", e)))?,
);
headers.insert(
ACCEPT,
HeaderValue::from_str(JSON_ACCEPT_VALUE)
.map_err(|e| Error::InvalidHeaders(format!("{}", e)))?,
);

let result = self
.post_with_raw_response(
path,
&blinded_block,
headers,
Some(self.timeouts.post_blinded_blocks),
)
.await?;

if result.status() == StatusCode::ACCEPTED {
Ok(())
} else {
// ACCEPTED is the only valid status code response
Err(Error::StatusCode(result.status()))
}
}

/// `GET /eth/v1/builder/header`
pub async fn get_builder_header<E: EthSpec>(
&self,
Expand Down
89 changes: 85 additions & 4 deletions beacon_node/execution_layer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,11 @@ pub enum FailedCondition {
EpochsSinceFinalization,
}

pub enum SubmitBlindedBlockResponse<E: EthSpec> {
V1(Box<FullPayloadContents<E>>),
V2,
}

type PayloadContentsRefTuple<'a, E> = (ExecutionPayloadRef<'a, E>, Option<&'a BlobsBundle<E>>);

struct Inner<E: EthSpec> {
Expand Down Expand Up @@ -1893,26 +1898,42 @@ impl<E: EthSpec> ExecutionLayer<E> {
&self,
block_root: Hash256,
block: &SignedBlindedBeaconBlock<E>,
) -> Result<FullPayloadContents<E>, Error> {
spec: &ChainSpec,
) -> Result<SubmitBlindedBlockResponse<E>, Error> {
debug!(?block_root, "Sending block to builder");
if spec.is_fulu_scheduled() {
self.post_builder_blinded_blocks_v2(block_root, block)
.await
.map(|()| SubmitBlindedBlockResponse::V2)
} else {
self.post_builder_blinded_blocks_v1(block_root, block)
.await
.map(|full_payload| SubmitBlindedBlockResponse::V1(Box::new(full_payload)))
}
}

async fn post_builder_blinded_blocks_v1(
&self,
block_root: Hash256,
block: &SignedBlindedBeaconBlock<E>,
) -> Result<FullPayloadContents<E>, Error> {
if let Some(builder) = self.builder() {
let (payload_result, duration) =
timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async {
let ssz_enabled = builder.is_ssz_available();
debug!(
?block_root,
ssz = ssz_enabled,
"Calling submit_blinded_block on builder"
"Calling submit_blinded_block v1 on builder"
);
if ssz_enabled {
builder
.post_builder_blinded_blocks_ssz(block)
.post_builder_blinded_blocks_v1_ssz(block)
.await
.map_err(Error::Builder)
} else {
builder
.post_builder_blinded_blocks(block)
.post_builder_blinded_blocks_v1(block)
.await
.map_err(Error::Builder)
.map(|d| d.data)
Expand Down Expand Up @@ -1961,6 +1982,66 @@ impl<E: EthSpec> ExecutionLayer<E> {
Err(Error::NoPayloadBuilder)
}
}

async fn post_builder_blinded_blocks_v2(
&self,
block_root: Hash256,
block: &SignedBlindedBeaconBlock<E>,
) -> Result<(), Error> {
if let Some(builder) = self.builder() {
let (result, duration) = timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async {
let ssz_enabled = builder.is_ssz_available();
debug!(
?block_root,
ssz = ssz_enabled,
"Calling submit_blinded_block v2 on builder"
);
if ssz_enabled {
builder
.post_builder_blinded_blocks_v2_ssz(block)
.await
.map_err(Error::Builder)
} else {
builder
.post_builder_blinded_blocks_v2(block)
.await
.map_err(Error::Builder)
}
})
.await;

match result {
Ok(()) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME,
&[metrics::SUCCESS],
);
info!(
relay_response_ms = duration.as_millis(),
?block_root,
"Successfully submitted blinded block to the builder"
)
}
Err(e) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME,
&[metrics::FAILURE],
);
error!(
info = "this may result in a missed block proposal",
error = ?e,
relay_response_ms = duration.as_millis(),
?block_root,
"Failed to submit blinded block to the builder"
)
}
}

Ok(())
} else {
Err(Error::NoPayloadBuilder)
}
}
}

#[derive(AsRefStr)]
Expand Down
2 changes: 1 addition & 1 deletion beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2763,7 +2763,7 @@ pub fn serve<T: BeaconChainTypes>(
move |task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
task_spawner.blocking_json_task(Priority::P0, move || {
let config_and_preset =
ConfigAndPreset::from_chain_spec::<T::EthSpec>(&chain.spec, None);
ConfigAndPreset::from_chain_spec::<T::EthSpec>(&chain.spec);
Ok(api_types::GenericResponse::from(config_and_preset))
})
},
Expand Down
54 changes: 36 additions & 18 deletions beacon_node/http_api/src/publish_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use eth2::types::{
BlobsBundle, BroadcastValidation, ErrorMessage, ExecutionPayloadAndBlobs, FullPayloadContents,
PublishBlockRequest, SignedBlockContents,
};
use execution_layer::ProvenancedPayload;
use execution_layer::{ProvenancedPayload, SubmitBlindedBlockResponse};
use futures::TryFutureExt;
use lighthouse_network::{NetworkGlobals, PubsubMessage};
use network::NetworkMessage;
Expand Down Expand Up @@ -636,27 +636,37 @@ pub async fn publish_blinded_block<T: BeaconChainTypes>(
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
) -> Result<Response, Rejection> {
let block_root = blinded_block.canonical_root();
let full_block = reconstruct_block(chain.clone(), block_root, blinded_block).await?;
publish_block::<T, _>(
Some(block_root),
full_block,
chain,
network_tx,
validation_level,
duplicate_status_code,
network_globals,
)
.await
let full_block_opt = reconstruct_block(chain.clone(), block_root, blinded_block).await?;

if let Some(full_block) = full_block_opt {
publish_block::<T, _>(
Some(block_root),
full_block,
chain,
network_tx,
validation_level,
duplicate_status_code,
network_globals,
)
.await
} else {
// From the fulu fork, builders are responsible for publishing and
// will no longer return the full payload and blobs.
Ok(warp::reply().into_response())
}
}

/// Deconstruct the given blinded block, and construct a full block. This attempts to use the
/// execution layer's payload cache, and if that misses, attempts a blind block proposal to retrieve
/// the full payload.
///
/// From the Fulu fork, external builders no longer return the full payload and blobs, and this
/// function will always return `Ok(None)` on successful submission of blinded block.
pub async fn reconstruct_block<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
block_root: Hash256,
block: Arc<SignedBlindedBeaconBlock<T::EthSpec>>,
) -> Result<ProvenancedBlock<T, Arc<SignedBeaconBlock<T::EthSpec>>>, Rejection> {
) -> Result<Option<ProvenancedBlock<T, Arc<SignedBeaconBlock<T::EthSpec>>>>, Rejection> {
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())
Expand Down Expand Up @@ -696,17 +706,24 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
"builder",
);

let full_payload = el
.propose_blinded_beacon_block(block_root, &block)
match el
.propose_blinded_beacon_block(block_root, &block, &chain.spec)
.await
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"Blind block proposal failed: {:?}",
e
))
})?;
info!(block_hash = ?full_payload.block_hash(), "Successfully published a block to the builder network");
ProvenancedPayload::Builder(full_payload)
})? {
SubmitBlindedBlockResponse::V1(full_payload) => {
info!(block_root = ?block_root, "Successfully published a block to the builder network");
ProvenancedPayload::Builder(*full_payload)
}
SubmitBlindedBlockResponse::V2 => {
info!(block_root = ?block_root, "Successfully published a block to the builder network");
return Ok(None);
}
}
};

Some(full_payload_contents)
Expand Down Expand Up @@ -734,6 +751,7 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
.map(|(block, blobs)| ProvenancedBlock::builder(block, blobs))
}
}
.map(Some)
.map_err(|e| {
warp_utils::reject::custom_server_error(format!("Unable to add payload to block: {e:?}"))
})
Expand Down
7 changes: 5 additions & 2 deletions beacon_node/http_api/tests/broadcast_validation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1275,14 +1275,17 @@ pub async fn blinded_equivocation_consensus_late_equivocation() {
Arc::new(block_a),
)
.await
.unwrap();
.expect("failed to reconstruct block")
.expect("block expected");

let unblinded_block_b = reconstruct_block(
tester.harness.chain.clone(),
block_b.canonical_root(),
block_b.clone(),
)
.await
.unwrap();
.expect("failed to reconstruct block")
.expect("block expected");

let inner_block_a = match unblinded_block_a {
ProvenancedBlock::Local(a, _, _) => a,
Expand Down
Loading