Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/builder/src/building/assemble/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl Fixture {
SlotContext {
slot: 1,
parent_hash: self.parent_hash,
parent_block_number: 0,
parent_block_number: Some(0),
timestamp: self.parent_timestamp + 12,
prev_randao: B256::repeat_byte(0xcc),
withdrawals: Withdrawals::default(),
Expand Down
37 changes: 33 additions & 4 deletions crates/builder/src/building/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub struct ProposerDuty {
pub struct SlotContext {
pub slot: u64,
pub parent_hash: B256,
pub parent_block_number: u64,
pub parent_block_number: Option<u64>,
pub timestamp: u64,
pub prev_randao: B256,
pub withdrawals: Withdrawals,
Expand Down Expand Up @@ -144,7 +144,7 @@ mod tests {
data: PayloadAttributesEventData {
proposer_index: 1,
proposal_slot: slot.into(),
parent_block_number: slot - 1,
parent_block_number: Some(slot - 1),
parent_block_root: String::new(),
parent_block_hash: parent,
payload_attributes: PayloadAttributes {
Expand Down Expand Up @@ -174,7 +174,7 @@ mod tests {

assert_eq!(context.slot, 10);
assert_eq!(context.parent_hash, B256::repeat_byte(0x11));
assert_eq!(context.parent_block_number, 9);
assert_eq!(context.parent_block_number, Some(9));
assert_eq!(context.timestamp, 1_700_000_000 + 120);
assert_eq!(context.prev_randao, B256::repeat_byte(0xcc));
assert_eq!(context.parent_beacon_block_root, B256::repeat_byte(0xdd));
Expand Down Expand Up @@ -337,11 +337,40 @@ mod tests {
let context = tracker.on_payload_attributes(event).expect("a complete event must build");

assert_eq!(context.slot, 11111);
assert_eq!(context.parent_block_number, 999);
assert_eq!(context.parent_block_number, Some(999));
assert_eq!(context.timestamp, 1_700_000_000);
assert_eq!(context.parent_hash, B256::repeat_byte(0x22));
assert_eq!(context.parent_beacon_block_root, B256::repeat_byte(0x44));
assert_eq!(context.withdrawals.len(), 1);
assert_eq!(context.withdrawals[0].amount, 32_000_000_000);
}

#[test]
fn parses_a_gloas_payload_attributes_event_without_a_parent_block_number() {
let json = r#"{
"version": "gloas",
"data": {
"proposer_index": "123",
"proposal_slot": "11111",
"parent_block_root": "0x1111111111111111111111111111111111111111111111111111111111111111",
"parent_block_hash": "0x2222222222222222222222222222222222222222222222222222222222222222",
"payload_attributes": {
"timestamp": "1700000000",
"prev_randao": "0x3333333333333333333333333333333333333333333333333333333333333333",
"suggested_fee_recipient": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"withdrawals": [],
"parent_beacon_block_root": "0x4444444444444444444444444444444444444444444444444444444444444444"
}
}
}"#;

let event: PayloadAttributesEvent = serde_json::from_str(json).unwrap();
let mut tracker = SlotTracker::default();
tracker.on_duties(vec![duty_response(11111)]);

let context = tracker.on_payload_attributes(event).expect("a Gloas event must build");

assert_eq!(context.slot, 11111);
assert_eq!(context.parent_block_number, None);
}
}
2 changes: 1 addition & 1 deletion crates/builder/src/building/submit/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn slot_context() -> SlotContext {
SlotContext {
slot: 42,
parent_hash: B256::repeat_byte(0x11),
parent_block_number: 41,
parent_block_number: Some(41),
timestamp: 1_700_000_000,
prev_randao: B256::repeat_byte(0xcc),
withdrawals: Withdrawals::default(),
Expand Down
3 changes: 3 additions & 0 deletions crates/builder/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use flux::{
tile::{TileConfig, attach_tile},
utils::ThreadPriority,
};
use helix_common::utils::install_default_crypto_provider;
use tracing::info;
use tracing_subscriber::EnvFilter;

Expand Down Expand Up @@ -31,6 +32,8 @@ use validation::{BlockValidator, server as validation_server};
static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;

fn main() -> eyre::Result<()> {
install_default_crypto_provider();

let cli = BuilderCli::parse();
init_tracing(&cli);

Expand Down
20 changes: 18 additions & 2 deletions crates/common/src/beacon/types/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,29 @@ pub struct PayloadAttributesEventData {
#[serde(with = "serde_utils::quoted_u64")]
pub proposer_index: u64,
pub proposal_slot: Slot,
#[serde(with = "serde_utils::quoted_u64")]
pub parent_block_number: u64,
/// Absent from Gloas, which no longer carries it.
#[serde(default, with = "quoted_u64_opt")]
pub parent_block_number: Option<u64>,
pub parent_block_root: String,
pub parent_block_hash: B256,
pub payload_attributes: PayloadAttributes,
}

mod quoted_u64_opt {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_utils::quoted_u64::Quoted;

pub fn serialize<S: Serializer>(value: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error> {
value.map(|value| Quoted { value }).serialize(serializer)
}

pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<u64>, D::Error> {
Ok(Option::<Quoted<u64>>::deserialize(deserializer)?.map(|quoted| quoted.value))
}
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct PayloadAttributes {
#[serde(with = "serde_utils::quoted_u64")]
Expand Down
5 changes: 5 additions & 0 deletions crates/database/src/postgres/postgres_db_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,11 @@ impl PostgresDatabaseService {
) -> Result<(), DatabaseError> {
let mut record = DbMetricRecord::new("set_proposer_duties");

if proposer_duties.is_empty() {
record.record_success();
return Ok(());
}

let mut client = self.high_priority_pool.get().await?;
let transaction = client.transaction().await?;

Expand Down
17 changes: 10 additions & 7 deletions crates/relay/src/auctioneer/get_execution_payload_bid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use tokio::sync::oneshot;
use tracing::warn;
use tree_hash::TreeHash;

const WEI_PER_GWEI: u64 = 1_000_000_000;

use crate::{
api::proposer::{GloasBuilderIdentity, ProposerApiError},
auctioneer::{
Expand Down Expand Up @@ -80,8 +82,9 @@ pub(super) fn build_signed_bid(
let execution_requests = execution_requests_to_gloas(entry.bid_data_ref().execution_requests);
let execution_requests_root = execution_requests.tree_hash_root();

// Per gattaca-com/helix#489: no payment-split product need yet, so execution_payment = value.
let value = entry.value().saturating_to::<u64>();
// The proposer is paid in-block, so the enshrined `value` stays 0.
let execution_payment =
(entry.value() / alloy_primitives::U256::from(WEI_PER_GWEI)).saturating_to::<u64>();

let bid = ExecutionPayloadBid {
parent_block_hash: ExecutionBlockHash(params.parent_hash),
Expand All @@ -92,8 +95,8 @@ pub(super) fn build_signed_bid(
gas_limit: payload.gas_limit,
builder_index: identity.builder_index,
slot,
value,
execution_payment: value,
value: 0,
execution_payment,
blob_kzg_commitments: convert_kzg_commitments_to_progressive(
&entry.payload_and_blobs().blobs_bundle.commitments,
),
Expand Down Expand Up @@ -279,7 +282,7 @@ mod tests {
let block_hash = B256::repeat_byte(0x99);
let parent_hash = B256::repeat_byte(0x11);
let parent_root = B256::repeat_byte(0x22);
let entry = payload_entry(block_hash, 42);
let entry = payload_entry(block_hash, 42 * WEI_PER_GWEI);
let identity = bid_identity(7);
let params = params(parent_hash, parent_root);

Expand All @@ -289,8 +292,8 @@ mod tests {
assert_eq!(signed_bid.message.parent_block_hash.0, parent_hash);
assert_eq!(signed_bid.message.parent_block_root, parent_root);
assert_eq!(signed_bid.message.builder_index, 7);
assert_eq!(signed_bid.message.value, 42);
assert_eq!(signed_bid.message.execution_payment, 42);
assert_eq!(signed_bid.message.value, 0, "the proposer is paid in-block");
assert_eq!(signed_bid.message.execution_payment, 42, "wei converts to gwei");

let epoch = signed_bid.message.slot.epoch(helix_types::MainnetEthSpec::slots_per_epoch());
let fork = chain_info.spec.fork_at_epoch(epoch);
Expand Down
9 changes: 7 additions & 2 deletions crates/relay/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,12 @@ async fn run(

if config.is_registration_instance {
for core in config.cores.reg_workers.clone() {
let tile =
RegistrationTile::new(core, chain_info.as_ref().clone(), reg_worker_rx.clone());
let tile = RegistrationTile::new(
core,
chain_info.as_ref().clone(),
reg_worker_rx.clone(),
slot_events.clone(),
);
attach_tile(tile, spine, TileConfig::new(core, ThreadPriority::OSDefault));
}
}
Expand Down Expand Up @@ -321,6 +325,7 @@ async fn run(
config.tcp_max_connections,
spine.spine.dcache_ptr_for::<NewBidSubmission>(),
http_submissions.clone(),
slot_events.clone(),
);
attach_tile(
block_submission_tcp_listener,
Expand Down
86 changes: 59 additions & 27 deletions crates/relay/src/registration/tile.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
use std::time::{Duration, Instant};
use std::{
sync::Arc,
time::{Duration, Instant},
};

use flux::{
tile::{Tile, TileName},
utils::{ShortTypename, short_typename},
};
use flux_profiler::timed;
use flux_utils::SharedVector;
use helix_common::{chain_info::ChainInfo, utils::utcnow_ns};
use helix_types::SignedValidatorRegistration;
use tracing::info;

use crate::{HelixSpine, api::proposer::ProposerApiError, registration::handle::RegWorkerJob};
use crate::{
HelixSpine, api::proposer::ProposerApiError, housekeeper::SlotUpdate,
registration::handle::RegWorkerJob, spine::messages::SlotMsg,
};

/// Registrations aren't slot-scoped (they arrive on their own schedule, not
/// tied to `bid_slot`), so this reports on the same wall-clock cadence as the
/// rest of `Telemetry` rather than on a slot boundary.
/// `registrations_seen == valid + invalid`; `batches_seen == completed +
/// aborted`.
/// `registrations_seen == valid + invalid`; `batches_seen == completed + aborted`.
#[derive(Default)]
struct RegStats {
valid: u32,
Expand All @@ -30,7 +33,6 @@ struct Telemetry {
next_record: Instant,
loop_start: Instant,
loop_worked: Duration,
stats: RegStats,
}

impl Telemetry {
Expand Down Expand Up @@ -63,18 +65,6 @@ impl Telemetry {

WORKER_UTIL.with_label_values(&[id]).observe(util);
WORKER_QUEUE_LEN.with_label_values(&[queue_type]).observe(rx.len() as f64);

let stats = std::mem::take(&mut self.stats);
info!(
id,
registrations_seen = stats.valid + stats.invalid,
valid = stats.valid,
invalid = stats.invalid,
batches_seen = stats.completed + stats.aborted,
completed = stats.completed,
aborted = stats.aborted,
"registration tile stats"
);
}
}
}
Expand All @@ -89,7 +79,6 @@ impl Default for Telemetry {
Duration::from_millis(utcnow_ns() % 10 * 5),
loop_start: Instant::now(),
loop_worked: Default::default(),
stats: RegStats::default(),
}
}
}
Expand All @@ -100,16 +89,57 @@ pub struct RegistrationTile {
chain_info: ChainInfo,
tel: Telemetry,
rx: crossbeam_channel::Receiver<RegWorkerJob>,
slot_events: Arc<SharedVector<SlotUpdate>>,
bid_slot: u64,
stats: RegStats,
}

impl RegistrationTile {
pub fn new(
core_id: usize,
chain_info: ChainInfo,
rx: crossbeam_channel::Receiver<RegWorkerJob>,
slot_events: Arc<SharedVector<SlotUpdate>>,
) -> Self {
let id = ShortTypename::from_str_truncate(&format!("registration_{core_id}"));
Self { core_id, id, chain_info, tel: Default::default(), rx }
Self {
core_id,
id,
chain_info,
tel: Default::default(),
rx,
slot_events,
bid_slot: 0,
stats: RegStats::default(),
}
}

fn on_slot_msg(&mut self, msg: SlotMsg) {
let Some(ev) = self.slot_events.get(msg.ix) else { return };
let bid_slot = ev.bid_slot.as_u64();
if bid_slot <= self.bid_slot {
return;
}
self.report_slot_stats();
self.bid_slot = bid_slot;
}

fn report_slot_stats(&mut self) {
if self.bid_slot == 0 {
return;
}
let stats = std::mem::take(&mut self.stats);
info!(
id = &*self.id,
bid_slot = self.bid_slot,
registrations_seen = stats.valid + stats.invalid,
valid = stats.valid,
invalid = stats.invalid,
batches_seen = stats.completed + stats.aborted,
completed = stats.completed,
aborted = stats.aborted,
"registration slot stats"
);
}

fn handle_reg_task(&mut self, task: RegWorkerJob) {
Expand All @@ -119,9 +149,9 @@ impl RegistrationTile {
let completed = self.process_reg_task(task);
let tag = if completed { "RegistrationBatch" } else { "RegistrationBatch_Aborted" };
if completed {
self.tel.stats.completed += 1;
self.stats.completed += 1;
} else {
self.tel.stats.aborted += 1;
self.stats.aborted += 1;
}

let dur = start_task.elapsed();
Expand All @@ -146,9 +176,9 @@ impl RegistrationTile {
let start = Instant::now();
let valid = validate_registration(&self.chain_info, &regs[i]);
if valid.is_ok() {
self.tel.stats.valid += 1;
self.stats.valid += 1;
} else {
self.tel.stats.invalid += 1;
self.stats.invalid += 1;
}
res.push((i, valid.is_ok()));

Expand All @@ -164,7 +194,9 @@ impl RegistrationTile {
}

impl Tile<HelixSpine> for RegistrationTile {
fn loop_body(&mut self, _adapter: &mut flux::spine::SpineAdapter<HelixSpine>) {
fn loop_body(&mut self, adapter: &mut flux::spine::SpineAdapter<HelixSpine>) {
adapter.consume(|msg: SlotMsg, _| self.on_slot_msg(msg));

if let Ok(task) = self.rx.recv_timeout(Duration::from_millis(50)) {
self.handle_reg_task(task);
}
Expand Down
Loading
Loading