Respect gas_limit from validator registration

This commit is contained in:
Pawan Dhananjay
2025-01-17 11:47:49 -08:00
parent a8286ab0a3
commit 1f7b4a327e
2 changed files with 133 additions and 63 deletions

View File

@@ -664,7 +664,7 @@ pub struct BeaconChainHarness<T: BeaconChainTypes> {
pub runtime: TestRuntime,
pub mock_execution_layer: Option<MockExecutionLayer<T::EthSpec>>,
pub mock_builder: Option<Arc<MockBuilder<T::EthSpec>>>,
pub mock_builder: Option<Arc<MockBuilder<T::EthSpec, T::SlotClock>>>,
pub rng: Mutex<StdRng>,
}
@@ -722,6 +722,7 @@ where
mock_el_url,
beacon_url,
self.spec.clone(),
self.chain.slot_clock.clone(),
self.runtime.task_executor.clone(),
);

View File

@@ -10,6 +10,7 @@ use fork_choice::ForkchoiceUpdateParameters;
use parking_lot::RwLock;
use sensitive_url::SensitiveUrl;
use slog::{debug, error, info, warn, Logger};
use slot_clock::SlotClock;
use std::collections::HashMap;
use std::fmt::Debug;
use std::future::Future;
@@ -27,7 +28,7 @@ use types::builder_bid::{
use types::{
Address, BeaconState, ChainSpec, Epoch, EthSpec, ExecPayload, ExecutionPayload,
ExecutionPayloadHeaderRefMut, ExecutionRequests, ForkName, ForkVersionedResponse, Hash256,
PublicKeyBytes, Signature, SignedBlindedBeaconBlock, SignedRoot,
ProposerPreparationData, PublicKeyBytes, Signature, SignedBlindedBeaconBlock, SignedRoot,
SignedValidatorRegistrationData, Slot, Uint256,
};
use types::{ExecutionBlockHash, SecretKey};
@@ -282,7 +283,7 @@ pub struct PayloadParametersCloned {
}
#[derive(Clone)]
pub struct MockBuilder<E: EthSpec> {
pub struct MockBuilder<E: EthSpec, S: SlotClock> {
el: ExecutionLayer<E>,
beacon_client: BeaconNodeHttpClient,
spec: Arc<ChainSpec>,
@@ -290,7 +291,6 @@ pub struct MockBuilder<E: EthSpec> {
builder_sk: SecretKey,
operations: Arc<RwLock<Vec<Operation>>>,
invalidate_signatures: Arc<RwLock<bool>>,
genesis_time: Option<u64>,
/// Only returns bids for registered validators if set to true. `true` by default.
validate_pubkey: bool,
/// Do not apply any operations if set to `false`.
@@ -303,14 +303,17 @@ pub struct MockBuilder<E: EthSpec> {
max_bid: bool,
/// A cache that stores the proposers index for a given epoch
proposers_cache: Arc<RwLock<HashMap<Epoch, Vec<ProposerData>>>>,
pubkey_cache: Arc<RwLock<HashMap<PublicKeyBytes, u64>>>,
slot_clock: S,
log: Logger,
}
impl<E: EthSpec> MockBuilder<E> {
impl<E: EthSpec, S: SlotClock + 'static> MockBuilder<E, S> {
pub fn new_for_testing(
mock_el_url: SensitiveUrl,
beacon_url: SensitiveUrl,
spec: Arc<ChainSpec>,
slot_clock: S,
executor: TaskExecutor,
) -> (Self, (SocketAddr, impl Future<Output = ()>)) {
let file = NamedTempFile::new().unwrap();
@@ -336,6 +339,7 @@ impl<E: EthSpec> MockBuilder<E> {
false,
spec,
None,
slot_clock,
executor.log().clone(),
);
let host: Ipv4Addr = Ipv4Addr::LOCALHOST;
@@ -353,6 +357,7 @@ impl<E: EthSpec> MockBuilder<E> {
max_bid: bool,
spec: Arc<ChainSpec>,
sk: Option<&[u8]>,
slot_clock: S,
log: Logger,
) -> Self {
let builder_sk = if let Some(sk_bytes) = sk {
@@ -381,9 +386,10 @@ impl<E: EthSpec> MockBuilder<E> {
invalidate_signatures: Arc::new(RwLock::new(false)),
payload_id_cache: Arc::new(RwLock::new(HashMap::new())),
proposers_cache: Arc::new(RwLock::new(HashMap::new())),
pubkey_cache: Arc::new(RwLock::new(HashMap::new())),
apply_operations,
max_bid,
genesis_time: None,
slot_clock,
log,
}
}
@@ -424,6 +430,13 @@ impl<E: EthSpec> MockBuilder<E> {
"Registering validators";
"count" => registrations.len(),
);
let mut preparation_data = Vec::with_capacity(registrations.len());
let current_epoch = self
.slot_clock
.now()
.ok_or("Failed to get current epoch".to_string())?
.epoch(E::slots_per_epoch());
for registration in registrations {
if !registration.verify_signature(&self.spec) {
error!(
@@ -432,12 +445,77 @@ impl<E: EthSpec> MockBuilder<E> {
"error" => "invalid signature",
"validator" => %registration.message.pubkey
);
return Err("invalid signature".to_string());
continue;
}
let pubkey = registration.message.pubkey.clone();
// First try to get the validator index from cache
let validator_index = {
let pubkey_cache = self.pubkey_cache.write();
pubkey_cache.get(&pubkey).copied()
};
// Get or fetch the validator index
let validator_index = if let Some(validator_index) = validator_index {
validator_index
} else {
// Do the async fetch without holding any locks
let validator_index = self
.beacon_client
.get_beacon_states_validator_id(
StateId::Head,
&ValidatorId::PublicKey(pubkey.clone()),
)
.await
.map_err(|e| format!("Failed to get validator index: {:?}", e))?
.ok_or("Beacon node returned 404".to_string())?
.data
.index;
// Update cache after the fetch
// Note: Doing this to avoid locking between await points
{
let mut pubkey_cache = self.pubkey_cache.write();
pubkey_cache.insert(pubkey.clone(), validator_index);
}
validator_index
};
let prep_data = (
ProposerPreparationData {
validator_index,
fee_recipient: registration.message.fee_recipient,
},
Some(registration.message.gas_limit),
);
info!(
self.log,
"Proposer prep data for {} {:?}", pubkey, prep_data,
);
preparation_data.push((
ProposerPreparationData {
validator_index,
fee_recipient: registration.message.fee_recipient,
},
Some(registration.message.gas_limit),
));
self.val_registration_cache
.write()
.insert(registration.message.pubkey, registration);
}
info!(
self.log,
"Updating proposer preparation for {} validators",
preparation_data.len(),
);
self.el
.update_proposer_preparation(
current_epoch,
preparation_data
.iter()
.map(|(data, gas_limit)| (data, gas_limit)),
)
.await;
Ok(())
}
@@ -815,16 +893,7 @@ impl<E: EthSpec> MockBuilder<E> {
};
let slots_since_genesis = slot.as_u64() - self.spec.genesis_slot.as_u64();
let genesis_time = if let Some(genesis_time) = self.genesis_time {
genesis_time
} else {
self.beacon_client
.get_beacon_genesis()
.await
.map_err(|_| "couldn't get beacon genesis".to_string())?
.data
.genesis_time
};
let genesis_time = self.slot_clock.genesis_duration().as_secs();
let timestamp = (slots_since_genesis * self.spec.seconds_per_slot) + genesis_time;
let head_state: BeaconState<E> = self
@@ -926,10 +995,10 @@ impl<E: EthSpec> MockBuilder<E> {
/// the requests.
///
/// We should eventually move this to axum when we move everything else.
pub fn serve<E: EthSpec>(
pub fn serve<E: EthSpec, S: SlotClock + 'static>(
listen_addr: Ipv4Addr,
listen_port: u16,
builder: MockBuilder<E>,
builder: MockBuilder<E, S>,
) -> Result<(SocketAddr, impl Future<Output = ()>), crate::test_utils::Error> {
let inner_ctx = builder.clone();
let ctx_filter = warp::any().map(move || inner_ctx.clone());
@@ -938,57 +1007,57 @@ pub fn serve<E: EthSpec>(
.and(warp::path("v1"))
.and(warp::path("builder"));
let validators = prefix
.and(warp::path("validators"))
.and(warp::body::json())
.and(warp::path::end())
.and(ctx_filter.clone())
.and_then(
|registrations: Vec<SignedValidatorRegistrationData>,
builder: MockBuilder<E>| async move {
builder
.register_validators(registrations)
.await
.map_err(|e| warp::reject::custom(Custom(e)))?;
Ok::<_, Rejection>(warp::reply())
},
)
.boxed();
let blinded_block =
let validators =
prefix
.and(warp::path("blinded_blocks"))
.and(warp::path("validators"))
.and(warp::body::json())
.and(warp::header::header::<ForkName>(CONSENSUS_VERSION_HEADER))
.and(warp::path::end())
.and(ctx_filter.clone())
.and_then(
|block: SignedBlindedBeaconBlock<E>,
fork_name: ForkName,
builder: MockBuilder<E>| async move {
let payload = builder
.submit_blinded_block(block)
|registrations: Vec<SignedValidatorRegistrationData>,
builder: MockBuilder<E, S>| async move {
builder
.register_validators(registrations)
.await
.map_err(|e| warp::reject::custom(Custom(e)))?;
let resp: ForkVersionedResponse<_> = ForkVersionedResponse {
version: Some(fork_name),
metadata: Default::default(),
data: payload,
};
let json_payload = serde_json::to_string(&resp)
.map_err(|_| reject("coudn't serialize response"))?;
Ok::<_, warp::reject::Rejection>(
warp::http::Response::builder()
.status(200)
.body(
serde_json::to_string(&json_payload)
.map_err(|_| reject("invalid JSON"))?,
)
.unwrap(),
)
Ok::<_, Rejection>(warp::reply())
},
);
)
.boxed();
let blinded_block = prefix
.and(warp::path("blinded_blocks"))
.and(warp::body::json())
.and(warp::header::header::<ForkName>(CONSENSUS_VERSION_HEADER))
.and(warp::path::end())
.and(ctx_filter.clone())
.and_then(
|block: SignedBlindedBeaconBlock<E>,
fork_name: ForkName,
builder: MockBuilder<E, S>| async move {
let payload = builder
.submit_blinded_block(block)
.await
.map_err(|e| warp::reject::custom(Custom(e)))?;
let resp: ForkVersionedResponse<_> = ForkVersionedResponse {
version: Some(fork_name),
metadata: Default::default(),
data: payload,
};
let json_payload = serde_json::to_string(&resp)
.map_err(|_| reject("coudn't serialize response"))?;
Ok::<_, warp::reject::Rejection>(
warp::http::Response::builder()
.status(200)
.body(
serde_json::to_string(&json_payload)
.map_err(|_| reject("invalid JSON"))?,
)
.unwrap(),
)
},
);
let status = prefix
.and(warp::path("status"))
@@ -1011,7 +1080,7 @@ pub fn serve<E: EthSpec>(
|slot: Slot,
parent_hash: ExecutionBlockHash,
pubkey: PublicKeyBytes,
builder: MockBuilder<E>| async move {
builder: MockBuilder<E, S>| async move {
let fork_name = builder.fork_name_at_slot(slot);
let signed_bid = builder
.get_header(slot, parent_hash, pubkey)