mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 20:57:10 +00:00
Merge branch 'gloas-post-bid-api' into glamsterdam-devnet-4
This commit is contained in:
112
beacon_node/http_api/src/beacon/bid.rs
Normal file
112
beacon_node/http_api/src/beacon/bid.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
use crate::task_spawner::{Priority, TaskSpawner};
|
||||||
|
use crate::utils::{
|
||||||
|
ChainFilter, EthV1Filter, NetworkTxFilter, ResponseFilter, TaskSpawnerFilter,
|
||||||
|
publish_pubsub_message,
|
||||||
|
};
|
||||||
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use lighthouse_network::PubsubMessage;
|
||||||
|
use network::NetworkMessage;
|
||||||
|
use ssz::Decode;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
use tracing::{info, warn};
|
||||||
|
use types::SignedExecutionPayloadBid;
|
||||||
|
use warp::{Filter, Rejection, Reply, hyper::Body, hyper::Response};
|
||||||
|
|
||||||
|
// POST /eth/v1/beacon/execution_payload_bid (SSZ)
|
||||||
|
pub(crate) fn post_beacon_execution_payload_bid_ssz<T: BeaconChainTypes>(
|
||||||
|
eth_v1: EthV1Filter,
|
||||||
|
task_spawner_filter: TaskSpawnerFilter<T>,
|
||||||
|
chain_filter: ChainFilter<T>,
|
||||||
|
network_tx_filter: NetworkTxFilter<T>,
|
||||||
|
) -> ResponseFilter {
|
||||||
|
eth_v1
|
||||||
|
.and(warp::path("beacon"))
|
||||||
|
.and(warp::path("execution_payload_bid"))
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(warp::body::bytes())
|
||||||
|
.and(task_spawner_filter)
|
||||||
|
.and(chain_filter)
|
||||||
|
.and(network_tx_filter)
|
||||||
|
.then(
|
||||||
|
|body_bytes: Bytes,
|
||||||
|
task_spawner: TaskSpawner<T::EthSpec>,
|
||||||
|
chain: Arc<BeaconChain<T>>,
|
||||||
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
||||||
|
task_spawner.blocking_response_task(Priority::P0, move || {
|
||||||
|
let bid = SignedExecutionPayloadBid::<T::EthSpec>::from_ssz_bytes(&body_bytes)
|
||||||
|
.map_err(|e| {
|
||||||
|
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
|
||||||
|
})?;
|
||||||
|
publish_execution_payload_bid(bid, &chain, &network_tx)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /eth/v1/beacon/execution_payload_bid
|
||||||
|
pub(crate) fn post_beacon_execution_payload_bid<T: BeaconChainTypes>(
|
||||||
|
eth_v1: EthV1Filter,
|
||||||
|
task_spawner_filter: TaskSpawnerFilter<T>,
|
||||||
|
chain_filter: ChainFilter<T>,
|
||||||
|
network_tx_filter: NetworkTxFilter<T>,
|
||||||
|
) -> ResponseFilter {
|
||||||
|
eth_v1
|
||||||
|
.and(warp::path("beacon"))
|
||||||
|
.and(warp::path("execution_payload_bid"))
|
||||||
|
.and(warp::path::end())
|
||||||
|
.and(warp::body::json())
|
||||||
|
.and(task_spawner_filter)
|
||||||
|
.and(chain_filter)
|
||||||
|
.and(network_tx_filter)
|
||||||
|
.then(
|
||||||
|
|bid: SignedExecutionPayloadBid<T::EthSpec>,
|
||||||
|
task_spawner: TaskSpawner<T::EthSpec>,
|
||||||
|
chain: Arc<BeaconChain<T>>,
|
||||||
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
||||||
|
task_spawner.blocking_response_task(Priority::P0, move || {
|
||||||
|
publish_execution_payload_bid(bid, &chain, &network_tx)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn publish_execution_payload_bid<T: BeaconChainTypes>(
|
||||||
|
bid: SignedExecutionPayloadBid<T::EthSpec>,
|
||||||
|
chain: &Arc<BeaconChain<T>>,
|
||||||
|
network_tx: &UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||||
|
) -> Result<Response<Body>, Rejection> {
|
||||||
|
let slot = bid.slot();
|
||||||
|
let builder_index = bid.message.builder_index;
|
||||||
|
|
||||||
|
if !chain.spec.is_gloas_scheduled() {
|
||||||
|
return Err(warp_utils::reject::custom_bad_request(
|
||||||
|
"Execution payload bids are not supported before the Gloas fork".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
%slot,
|
||||||
|
builder_index,
|
||||||
|
"Publishing signed execution payload bid to network"
|
||||||
|
);
|
||||||
|
|
||||||
|
let gossip_verified_bid = chain
|
||||||
|
.verify_payload_bid_for_gossip(Arc::new(bid))
|
||||||
|
.map_err(|e| {
|
||||||
|
warn!(%slot, error = ?e, "Execution payload bid failed gossip verification");
|
||||||
|
warp_utils::reject::custom_bad_request(format!("bid failed gossip verification: {e}"))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let bid_for_gossip = gossip_verified_bid.signed_bid.as_ref().clone();
|
||||||
|
|
||||||
|
publish_pubsub_message(
|
||||||
|
network_tx,
|
||||||
|
PubsubMessage::ExecutionPayloadBid(Box::new(bid_for_gossip)),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(warp::reply().into_response())
|
||||||
|
}
|
||||||
@@ -11,7 +11,6 @@ use beacon_chain::payload_envelope_verification::EnvelopeError;
|
|||||||
use beacon_chain::{BeaconChain, BeaconChainTypes, NotifyExecutionLayer};
|
use beacon_chain::{BeaconChain, BeaconChainTypes, NotifyExecutionLayer};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use eth2::types as api_types;
|
use eth2::types as api_types;
|
||||||
use eth2::{CONTENT_TYPE_HEADER, SSZ_CONTENT_TYPE_HEADER};
|
|
||||||
use lighthouse_network::PubsubMessage;
|
use lighthouse_network::PubsubMessage;
|
||||||
use network::NetworkMessage;
|
use network::NetworkMessage;
|
||||||
use ssz::{Decode, Encode};
|
use ssz::{Decode, Encode};
|
||||||
@@ -36,10 +35,6 @@ pub(crate) fn post_beacon_execution_payload_envelope_ssz<T: BeaconChainTypes>(
|
|||||||
.and(warp::path("beacon"))
|
.and(warp::path("beacon"))
|
||||||
.and(warp::path("execution_payload_envelope"))
|
.and(warp::path("execution_payload_envelope"))
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp::header::exact(
|
|
||||||
CONTENT_TYPE_HEADER,
|
|
||||||
SSZ_CONTENT_TYPE_HEADER,
|
|
||||||
))
|
|
||||||
.and(warp::body::bytes())
|
.and(warp::body::bytes())
|
||||||
.and(task_spawner_filter)
|
.and(task_spawner_filter)
|
||||||
.and(chain_filter)
|
.and(chain_filter)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod bid;
|
||||||
pub mod execution_payload_envelope;
|
pub mod execution_payload_envelope;
|
||||||
pub mod pool;
|
pub mod pool;
|
||||||
pub mod states;
|
pub mod states;
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ mod validator_inclusion;
|
|||||||
mod validators;
|
mod validators;
|
||||||
mod version;
|
mod version;
|
||||||
|
|
||||||
|
use crate::beacon::bid::{
|
||||||
|
post_beacon_execution_payload_bid, post_beacon_execution_payload_bid_ssz,
|
||||||
|
};
|
||||||
use crate::beacon::execution_payload_envelope::{
|
use crate::beacon::execution_payload_envelope::{
|
||||||
get_beacon_execution_payload_envelope, post_beacon_execution_payload_envelope,
|
get_beacon_execution_payload_envelope, post_beacon_execution_payload_envelope,
|
||||||
post_beacon_execution_payload_envelope_ssz,
|
post_beacon_execution_payload_envelope_ssz,
|
||||||
@@ -1555,6 +1558,22 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
network_tx_filter.clone(),
|
network_tx_filter.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// POST beacon/execution_payload_bid
|
||||||
|
let post_beacon_execution_payload_bid = post_beacon_execution_payload_bid(
|
||||||
|
eth_v1.clone(),
|
||||||
|
task_spawner_filter.clone(),
|
||||||
|
chain_filter.clone(),
|
||||||
|
network_tx_filter.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// POST beacon/execution_payload_bid (SSZ)
|
||||||
|
let post_beacon_execution_payload_bid_ssz = post_beacon_execution_payload_bid_ssz(
|
||||||
|
eth_v1.clone(),
|
||||||
|
task_spawner_filter.clone(),
|
||||||
|
chain_filter.clone(),
|
||||||
|
network_tx_filter.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
// GET beacon/execution_payload_envelope/{block_id}
|
// GET beacon/execution_payload_envelope/{block_id}
|
||||||
let get_beacon_execution_payload_envelope = get_beacon_execution_payload_envelope(
|
let get_beacon_execution_payload_envelope = get_beacon_execution_payload_envelope(
|
||||||
eth_v1.clone(),
|
eth_v1.clone(),
|
||||||
@@ -3448,6 +3467,7 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
.uor(post_beacon_blinded_blocks_ssz)
|
.uor(post_beacon_blinded_blocks_ssz)
|
||||||
.uor(post_beacon_blinded_blocks_v2_ssz)
|
.uor(post_beacon_blinded_blocks_v2_ssz)
|
||||||
.uor(post_beacon_execution_payload_envelope_ssz)
|
.uor(post_beacon_execution_payload_envelope_ssz)
|
||||||
|
.uor(post_beacon_execution_payload_bid_ssz)
|
||||||
.uor(post_beacon_pool_payload_attestations_ssz)
|
.uor(post_beacon_pool_payload_attestations_ssz)
|
||||||
.uor(post_validator_proposer_preferences_ssz),
|
.uor(post_validator_proposer_preferences_ssz),
|
||||||
)
|
)
|
||||||
@@ -3464,6 +3484,7 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
.uor(post_beacon_pool_bls_to_execution_changes)
|
.uor(post_beacon_pool_bls_to_execution_changes)
|
||||||
.uor(post_validator_proposer_preferences)
|
.uor(post_validator_proposer_preferences)
|
||||||
.uor(post_beacon_execution_payload_envelope)
|
.uor(post_beacon_execution_payload_envelope)
|
||||||
|
.uor(post_beacon_execution_payload_bid)
|
||||||
.uor(post_beacon_state_validators)
|
.uor(post_beacon_state_validators)
|
||||||
.uor(post_beacon_state_validator_balances)
|
.uor(post_beacon_state_validator_balances)
|
||||||
.uor(post_beacon_state_validator_identities)
|
.uor(post_beacon_state_validator_identities)
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ use tokio::time::Duration;
|
|||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use types::ApplicationDomain;
|
use types::ApplicationDomain;
|
||||||
use types::{
|
use types::{
|
||||||
Address, Domain, EthSpec, ExecutionBlockHash, Hash256, MainnetEthSpec, ProposerPreferences,
|
Address, Domain, EthSpec, ExecutionBlockHash, ExecutionPayloadBid, Hash256, MainnetEthSpec,
|
||||||
RelativeEpoch, SelectionProof, SignedExecutionPayloadEnvelope, SignedProposerPreferences,
|
ProposerPreferences, RelativeEpoch, SelectionProof, SignedExecutionPayloadBid,
|
||||||
SignedRoot, SingleAttestation, Slot, attestation::AttestationBase,
|
SignedExecutionPayloadEnvelope, SignedProposerPreferences, SignedRoot, SingleAttestation, Slot,
|
||||||
consts::gloas::BUILDER_INDEX_SELF_BUILD,
|
attestation::AttestationBase, consts::gloas::BUILDER_INDEX_SELF_BUILD,
|
||||||
};
|
};
|
||||||
|
|
||||||
type E = MainnetEthSpec;
|
type E = MainnetEthSpec;
|
||||||
@@ -3055,6 +3055,87 @@ impl ApiTester {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a `SignedExecutionPayloadBid` that is structurally valid (correct fields, correct
|
||||||
|
/// fork name) but will fail gossip verification because no proposer preferences are cached.
|
||||||
|
fn make_structurally_valid_bid(&self) -> (SignedExecutionPayloadBid<E>, ForkName) {
|
||||||
|
let head = self.chain.canonical_head.cached_head();
|
||||||
|
let slot = self.chain.slot().unwrap();
|
||||||
|
let fork_name = self.chain.spec.fork_name_at_slot::<E>(slot);
|
||||||
|
|
||||||
|
let bid = ExecutionPayloadBid {
|
||||||
|
parent_block_hash: ExecutionBlockHash::zero(),
|
||||||
|
parent_block_root: head.head_block_root(),
|
||||||
|
block_hash: ExecutionBlockHash::zero(),
|
||||||
|
prev_randao: Hash256::zero(),
|
||||||
|
fee_recipient: Address::zero(),
|
||||||
|
gas_limit: 30_000_000,
|
||||||
|
builder_index: 0,
|
||||||
|
slot,
|
||||||
|
value: 100,
|
||||||
|
execution_payment: 0,
|
||||||
|
blob_kzg_commitments: Default::default(),
|
||||||
|
execution_requests_root: Hash256::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let signed = SignedExecutionPayloadBid {
|
||||||
|
message: bid,
|
||||||
|
signature: bls::Signature::empty(),
|
||||||
|
};
|
||||||
|
|
||||||
|
(signed, fork_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JSON bid with a valid structure reaches gossip verification and is rejected with 400.
|
||||||
|
pub async fn test_post_beacon_execution_payload_bid_json(self) -> Self {
|
||||||
|
let (bid, fork_name) = self.make_structurally_valid_bid();
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.client
|
||||||
|
.post_beacon_execution_payload_bid(&bid, fork_name)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"bid should be rejected by gossip verification"
|
||||||
|
);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SSZ bid with a valid structure reaches gossip verification and is rejected with 400.
|
||||||
|
pub async fn test_post_beacon_execution_payload_bid_ssz(self) -> Self {
|
||||||
|
let (bid, fork_name) = self.make_structurally_valid_bid();
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.client
|
||||||
|
.post_beacon_execution_payload_bid_ssz(&bid, fork_name)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"bid (SSZ) should be rejected by gossip verification"
|
||||||
|
);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SSZ bid with a garbage payload is rejected before reaching gossip verification.
|
||||||
|
pub async fn test_post_beacon_execution_payload_bid_invalid_ssz(self) -> Self {
|
||||||
|
let fork_name = self
|
||||||
|
.chain
|
||||||
|
.spec
|
||||||
|
.fork_name_at_slot::<E>(self.chain.slot().unwrap());
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.client
|
||||||
|
.post_beacon_execution_payload_bid_raw_ssz(&[0xde, 0xad, 0xbe, 0xef], fork_name)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_err(), "invalid SSZ bytes should be rejected");
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn test_get_config_fork_schedule(self) -> Self {
|
pub async fn test_get_config_fork_schedule(self) -> Self {
|
||||||
let result = self.client.get_config_fork_schedule().await.unwrap().data;
|
let result = self.client.get_config_fork_schedule().await.unwrap().data;
|
||||||
|
|
||||||
@@ -9410,3 +9491,18 @@ async fn post_validator_proposer_preferences() {
|
|||||||
.test_post_validator_proposer_preferences_duplicate()
|
.test_post_validator_proposer_preferences_duplicate()
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn post_beacon_execution_payload_bid() {
|
||||||
|
if !fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ApiTester::new_with_hard_forks()
|
||||||
|
.await
|
||||||
|
.test_post_beacon_execution_payload_bid_json()
|
||||||
|
.await
|
||||||
|
.test_post_beacon_execution_payload_bid_ssz()
|
||||||
|
.await
|
||||||
|
.test_post_beacon_execution_payload_bid_invalid_ssz()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ use ssz::{Decode, Encode};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use types::{PayloadAttestationData, PayloadAttestationMessage, SignedProposerPreferences};
|
use types::{
|
||||||
|
PayloadAttestationData, PayloadAttestationMessage, SignedExecutionPayloadBid,
|
||||||
|
SignedProposerPreferences,
|
||||||
|
};
|
||||||
|
|
||||||
pub const V1: EndpointVersion = EndpointVersion(1);
|
pub const V1: EndpointVersion = EndpointVersion(1);
|
||||||
pub const V2: EndpointVersion = EndpointVersion(2);
|
pub const V2: EndpointVersion = EndpointVersion(2);
|
||||||
@@ -2838,6 +2841,78 @@ impl BeaconNodeHttpClient {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `POST v1/beacon/execution_payload_bid`
|
||||||
|
pub async fn post_beacon_execution_payload_bid<E: EthSpec>(
|
||||||
|
&self,
|
||||||
|
bid: &SignedExecutionPayloadBid<E>,
|
||||||
|
fork_name: ForkName,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut path = self.eth_path(V1)?;
|
||||||
|
|
||||||
|
path.path_segments_mut()
|
||||||
|
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||||
|
.push("beacon")
|
||||||
|
.push("execution_payload_bid");
|
||||||
|
|
||||||
|
self.post_generic_with_consensus_version(
|
||||||
|
path,
|
||||||
|
bid,
|
||||||
|
Some(self.timeouts.proposal),
|
||||||
|
fork_name,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `POST v1/beacon/execution_payload_bid` with raw bytes (for testing invalid SSZ)
|
||||||
|
pub async fn post_beacon_execution_payload_bid_raw_ssz(
|
||||||
|
&self,
|
||||||
|
bytes: &[u8],
|
||||||
|
fork_name: ForkName,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut path = self.eth_path(V1)?;
|
||||||
|
|
||||||
|
path.path_segments_mut()
|
||||||
|
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||||
|
.push("beacon")
|
||||||
|
.push("execution_payload_bid");
|
||||||
|
|
||||||
|
self.post_generic_with_consensus_version_and_ssz_body(
|
||||||
|
path,
|
||||||
|
bytes.to_vec(),
|
||||||
|
Some(self.timeouts.proposal),
|
||||||
|
fork_name,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `POST v1/beacon/execution_payload_bid` in SSZ format
|
||||||
|
pub async fn post_beacon_execution_payload_bid_ssz<E: EthSpec>(
|
||||||
|
&self,
|
||||||
|
bid: &SignedExecutionPayloadBid<E>,
|
||||||
|
fork_name: ForkName,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut path = self.eth_path(V1)?;
|
||||||
|
|
||||||
|
path.path_segments_mut()
|
||||||
|
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||||
|
.push("beacon")
|
||||||
|
.push("execution_payload_bid");
|
||||||
|
|
||||||
|
self.post_generic_with_consensus_version_and_ssz_body(
|
||||||
|
path,
|
||||||
|
bid.as_ssz_bytes(),
|
||||||
|
Some(self.timeouts.proposal),
|
||||||
|
fork_name,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Path for `v1/beacon/execution_payload_envelope/{block_id}`
|
/// Path for `v1/beacon/execution_payload_envelope/{block_id}`
|
||||||
pub fn get_beacon_execution_payload_envelope_path(
|
pub fn get_beacon_execution_payload_envelope_path(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
Reference in New Issue
Block a user