mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-08 09:16:00 +00:00
Add builder blinded_blocks v2 (#7778)
Partially addresses https://github.com/sigp/lighthouse/issues/7381 Add blinded_blocks v2 method specified in https://github.com/ethereum/builder-specs/pull/123/
This commit is contained in:
@@ -293,7 +293,7 @@ impl BuilderHttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `POST /eth/v1/builder/blinded_blocks` with SSZ serialized request body
|
/// `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,
|
&self,
|
||||||
blinded_block: &SignedBlindedBeaconBlock<E>,
|
blinded_block: &SignedBlindedBeaconBlock<E>,
|
||||||
) -> Result<FullPayloadContents<E>, Error> {
|
) -> Result<FullPayloadContents<E>, Error> {
|
||||||
@@ -340,8 +340,58 @@ impl BuilderHttpClient {
|
|||||||
.map_err(Error::InvalidSsz)
|
.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`
|
/// `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,
|
&self,
|
||||||
blinded_block: &SignedBlindedBeaconBlock<E>,
|
blinded_block: &SignedBlindedBeaconBlock<E>,
|
||||||
) -> Result<ForkVersionedResponse<FullPayloadContents<E>>, Error> {
|
) -> Result<ForkVersionedResponse<FullPayloadContents<E>>, Error> {
|
||||||
@@ -383,6 +433,54 @@ impl BuilderHttpClient {
|
|||||||
.await?)
|
.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`
|
/// `GET /eth/v1/builder/header`
|
||||||
pub async fn get_builder_header<E: EthSpec>(
|
pub async fn get_builder_header<E: EthSpec>(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -411,6 +411,11 @@ pub enum FailedCondition {
|
|||||||
EpochsSinceFinalization,
|
EpochsSinceFinalization,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum SubmitBlindedBlockResponse<E: EthSpec> {
|
||||||
|
V1(Box<FullPayloadContents<E>>),
|
||||||
|
V2,
|
||||||
|
}
|
||||||
|
|
||||||
type PayloadContentsRefTuple<'a, E> = (ExecutionPayloadRef<'a, E>, Option<&'a BlobsBundle<E>>);
|
type PayloadContentsRefTuple<'a, E> = (ExecutionPayloadRef<'a, E>, Option<&'a BlobsBundle<E>>);
|
||||||
|
|
||||||
struct Inner<E: EthSpec> {
|
struct Inner<E: EthSpec> {
|
||||||
@@ -1893,9 +1898,25 @@ impl<E: EthSpec> ExecutionLayer<E> {
|
|||||||
&self,
|
&self,
|
||||||
block_root: Hash256,
|
block_root: Hash256,
|
||||||
block: &SignedBlindedBeaconBlock<E>,
|
block: &SignedBlindedBeaconBlock<E>,
|
||||||
) -> Result<FullPayloadContents<E>, Error> {
|
spec: &ChainSpec,
|
||||||
|
) -> Result<SubmitBlindedBlockResponse<E>, Error> {
|
||||||
debug!(?block_root, "Sending block to builder");
|
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() {
|
if let Some(builder) = self.builder() {
|
||||||
let (payload_result, duration) =
|
let (payload_result, duration) =
|
||||||
timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async {
|
timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async {
|
||||||
@@ -1903,16 +1924,16 @@ impl<E: EthSpec> ExecutionLayer<E> {
|
|||||||
debug!(
|
debug!(
|
||||||
?block_root,
|
?block_root,
|
||||||
ssz = ssz_enabled,
|
ssz = ssz_enabled,
|
||||||
"Calling submit_blinded_block on builder"
|
"Calling submit_blinded_block v1 on builder"
|
||||||
);
|
);
|
||||||
if ssz_enabled {
|
if ssz_enabled {
|
||||||
builder
|
builder
|
||||||
.post_builder_blinded_blocks_ssz(block)
|
.post_builder_blinded_blocks_v1_ssz(block)
|
||||||
.await
|
.await
|
||||||
.map_err(Error::Builder)
|
.map_err(Error::Builder)
|
||||||
} else {
|
} else {
|
||||||
builder
|
builder
|
||||||
.post_builder_blinded_blocks(block)
|
.post_builder_blinded_blocks_v1(block)
|
||||||
.await
|
.await
|
||||||
.map_err(Error::Builder)
|
.map_err(Error::Builder)
|
||||||
.map(|d| d.data)
|
.map(|d| d.data)
|
||||||
@@ -1961,6 +1982,66 @@ impl<E: EthSpec> ExecutionLayer<E> {
|
|||||||
Err(Error::NoPayloadBuilder)
|
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)]
|
#[derive(AsRefStr)]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use eth2::types::{
|
|||||||
BlobsBundle, BroadcastValidation, ErrorMessage, ExecutionPayloadAndBlobs, FullPayloadContents,
|
BlobsBundle, BroadcastValidation, ErrorMessage, ExecutionPayloadAndBlobs, FullPayloadContents,
|
||||||
PublishBlockRequest, SignedBlockContents,
|
PublishBlockRequest, SignedBlockContents,
|
||||||
};
|
};
|
||||||
use execution_layer::ProvenancedPayload;
|
use execution_layer::{ProvenancedPayload, SubmitBlindedBlockResponse};
|
||||||
use futures::TryFutureExt;
|
use futures::TryFutureExt;
|
||||||
use lighthouse_network::{NetworkGlobals, PubsubMessage};
|
use lighthouse_network::{NetworkGlobals, PubsubMessage};
|
||||||
use network::NetworkMessage;
|
use network::NetworkMessage;
|
||||||
@@ -636,27 +636,37 @@ pub async fn publish_blinded_block<T: BeaconChainTypes>(
|
|||||||
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
||||||
) -> Result<Response, Rejection> {
|
) -> Result<Response, Rejection> {
|
||||||
let block_root = blinded_block.canonical_root();
|
let block_root = blinded_block.canonical_root();
|
||||||
let full_block = reconstruct_block(chain.clone(), block_root, blinded_block).await?;
|
let full_block_opt = reconstruct_block(chain.clone(), block_root, blinded_block).await?;
|
||||||
publish_block::<T, _>(
|
|
||||||
Some(block_root),
|
if let Some(full_block) = full_block_opt {
|
||||||
full_block,
|
publish_block::<T, _>(
|
||||||
chain,
|
Some(block_root),
|
||||||
network_tx,
|
full_block,
|
||||||
validation_level,
|
chain,
|
||||||
duplicate_status_code,
|
network_tx,
|
||||||
network_globals,
|
validation_level,
|
||||||
)
|
duplicate_status_code,
|
||||||
.await
|
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
|
/// 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
|
/// execution layer's payload cache, and if that misses, attempts a blind block proposal to retrieve
|
||||||
/// the full payload.
|
/// 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>(
|
pub async fn reconstruct_block<T: BeaconChainTypes>(
|
||||||
chain: Arc<BeaconChain<T>>,
|
chain: Arc<BeaconChain<T>>,
|
||||||
block_root: Hash256,
|
block_root: Hash256,
|
||||||
block: Arc<SignedBlindedBeaconBlock<T::EthSpec>>,
|
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 full_payload_opt = if let Ok(payload_header) = block.message().body().execution_payload() {
|
||||||
let el = chain.execution_layer.as_ref().ok_or_else(|| {
|
let el = chain.execution_layer.as_ref().ok_or_else(|| {
|
||||||
warp_utils::reject::custom_server_error("Missing execution layer".to_string())
|
warp_utils::reject::custom_server_error("Missing execution layer".to_string())
|
||||||
@@ -696,17 +706,24 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
|
|||||||
"builder",
|
"builder",
|
||||||
);
|
);
|
||||||
|
|
||||||
let full_payload = el
|
match el
|
||||||
.propose_blinded_beacon_block(block_root, &block)
|
.propose_blinded_beacon_block(block_root, &block, &chain.spec)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
warp_utils::reject::custom_server_error(format!(
|
warp_utils::reject::custom_server_error(format!(
|
||||||
"Blind block proposal failed: {:?}",
|
"Blind block proposal failed: {:?}",
|
||||||
e
|
e
|
||||||
))
|
))
|
||||||
})?;
|
})? {
|
||||||
info!(block_hash = ?full_payload.block_hash(), "Successfully published a block to the builder network");
|
SubmitBlindedBlockResponse::V1(full_payload) => {
|
||||||
ProvenancedPayload::Builder(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)
|
Some(full_payload_contents)
|
||||||
@@ -734,6 +751,7 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
|
|||||||
.map(|(block, blobs)| ProvenancedBlock::builder(block, blobs))
|
.map(|(block, blobs)| ProvenancedBlock::builder(block, blobs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.map(Some)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
warp_utils::reject::custom_server_error(format!("Unable to add payload to block: {e:?}"))
|
warp_utils::reject::custom_server_error(format!("Unable to add payload to block: {e:?}"))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1275,14 +1275,17 @@ pub async fn blinded_equivocation_consensus_late_equivocation() {
|
|||||||
Arc::new(block_a),
|
Arc::new(block_a),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.expect("failed to reconstruct block")
|
||||||
|
.expect("block expected");
|
||||||
|
|
||||||
let unblinded_block_b = reconstruct_block(
|
let unblinded_block_b = reconstruct_block(
|
||||||
tester.harness.chain.clone(),
|
tester.harness.chain.clone(),
|
||||||
block_b.canonical_root(),
|
block_b.canonical_root(),
|
||||||
block_b.clone(),
|
block_b.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.expect("failed to reconstruct block")
|
||||||
|
.expect("block expected");
|
||||||
|
|
||||||
let inner_block_a = match unblinded_block_a {
|
let inner_block_a = match unblinded_block_a {
|
||||||
ProvenancedBlock::Local(a, _, _) => a,
|
ProvenancedBlock::Local(a, _, _) => a,
|
||||||
|
|||||||
Reference in New Issue
Block a user