mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-29 20:27:14 +00:00
Add API for posting bid
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 bytes::Bytes;
|
||||
use eth2::types as api_types;
|
||||
use eth2::{CONTENT_TYPE_HEADER, SSZ_CONTENT_TYPE_HEADER};
|
||||
use lighthouse_network::PubsubMessage;
|
||||
use network::NetworkMessage;
|
||||
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("execution_payload_envelope"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::header::exact(
|
||||
CONTENT_TYPE_HEADER,
|
||||
SSZ_CONTENT_TYPE_HEADER,
|
||||
))
|
||||
.and(warp::body::bytes())
|
||||
.and(task_spawner_filter)
|
||||
.and(chain_filter)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod bid;
|
||||
pub mod execution_payload_envelope;
|
||||
pub mod pool;
|
||||
pub mod states;
|
||||
|
||||
@@ -36,6 +36,9 @@ mod validator_inclusion;
|
||||
mod validators;
|
||||
mod version;
|
||||
|
||||
use crate::beacon::bid::{
|
||||
post_beacon_execution_payload_bid, post_beacon_execution_payload_bid_ssz,
|
||||
};
|
||||
use crate::beacon::execution_payload_envelope::{
|
||||
get_beacon_execution_payload_envelope, post_beacon_execution_payload_envelope,
|
||||
post_beacon_execution_payload_envelope_ssz,
|
||||
@@ -1555,6 +1558,22 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
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}
|
||||
let get_beacon_execution_payload_envelope = get_beacon_execution_payload_envelope(
|
||||
eth_v1.clone(),
|
||||
@@ -3445,6 +3464,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.uor(post_beacon_blinded_blocks_ssz)
|
||||
.uor(post_beacon_blinded_blocks_v2_ssz)
|
||||
.uor(post_beacon_execution_payload_envelope_ssz)
|
||||
.uor(post_beacon_execution_payload_bid_ssz)
|
||||
.uor(post_beacon_pool_payload_attestations_ssz)
|
||||
.uor(post_validator_proposer_preferences_ssz),
|
||||
)
|
||||
@@ -3461,6 +3481,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.uor(post_beacon_pool_bls_to_execution_changes)
|
||||
.uor(post_validator_proposer_preferences)
|
||||
.uor(post_beacon_execution_payload_envelope)
|
||||
.uor(post_beacon_execution_payload_bid)
|
||||
.uor(post_beacon_state_validators)
|
||||
.uor(post_beacon_state_validator_balances)
|
||||
.uor(post_beacon_state_validator_identities)
|
||||
|
||||
@@ -48,10 +48,10 @@ use tokio::time::Duration;
|
||||
use tree_hash::TreeHash;
|
||||
use types::ApplicationDomain;
|
||||
use types::{
|
||||
Address, Domain, EthSpec, ExecutionBlockHash, Hash256, MainnetEthSpec, ProposerPreferences,
|
||||
RelativeEpoch, SelectionProof, SignedExecutionPayloadEnvelope, SignedProposerPreferences,
|
||||
SignedRoot, SingleAttestation, Slot, attestation::AttestationBase,
|
||||
consts::gloas::BUILDER_INDEX_SELF_BUILD,
|
||||
Address, Domain, EthSpec, ExecutionBlockHash, ExecutionPayloadBid, Hash256, MainnetEthSpec,
|
||||
ProposerPreferences, RelativeEpoch, SelectionProof, SignedExecutionPayloadBid,
|
||||
SignedExecutionPayloadEnvelope, SignedProposerPreferences, SignedRoot, SingleAttestation, Slot,
|
||||
attestation::AttestationBase, consts::gloas::BUILDER_INDEX_SELF_BUILD,
|
||||
};
|
||||
|
||||
type E = MainnetEthSpec;
|
||||
@@ -3055,6 +3055,87 @@ impl ApiTester {
|
||||
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 {
|
||||
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()
|
||||
.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::future::Future;
|
||||
use std::time::Duration;
|
||||
use types::{PayloadAttestationData, PayloadAttestationMessage, SignedProposerPreferences};
|
||||
use types::{
|
||||
PayloadAttestationData, PayloadAttestationMessage, SignedExecutionPayloadBid,
|
||||
SignedProposerPreferences,
|
||||
};
|
||||
|
||||
pub const V1: EndpointVersion = EndpointVersion(1);
|
||||
pub const V2: EndpointVersion = EndpointVersion(2);
|
||||
@@ -2838,6 +2841,78 @@ impl BeaconNodeHttpClient {
|
||||
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}`
|
||||
pub fn get_beacon_execution_payload_envelope_path(
|
||||
&self,
|
||||
|
||||
Reference in New Issue
Block a user