Skip to content
Merged
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
21 changes: 20 additions & 1 deletion crates/blockchain/src/block_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
//! without re-running the STF. The final STF runs once after selection to
//! seal `state_root`.

use std::collections::{HashMap, HashSet};
use std::{
collections::{HashMap, HashSet},
time::Instant,
};

use ethlambda_crypto::aggregate_proofs;
use ethlambda_state_transition::{
Expand Down Expand Up @@ -54,17 +57,23 @@ pub(crate) fn build_block(
) -> Result<(Block, Vec<AggregatedSignatureProof>, PostBlockCheckpoints), StoreError> {
info!(slot, proposer_index, "Building block");

let select_start = Instant::now();
let selected = select_attestations(
head_state,
slot,
parent_root,
known_block_roots,
aggregated_payloads,
);
metrics::observe_block_proposal_phase("select_payloads", select_start.elapsed());

let child_payloads_consumed = selected.len();

// Compact: merge proofs sharing the same AttestationData via recursive
// aggregation so each AttestationData appears at most once (leanSpec #510).
let compact_start = Instant::now();
let compacted = compact_attestations(selected, head_state)?;
metrics::observe_block_proposal_phase("compact", compact_start.elapsed());

let (aggregated_attestations, aggregated_signatures): (Vec<_>, Vec<_>) =
compacted.into_iter().unzip();
Expand All @@ -80,10 +89,19 @@ pub(crate) fn build_block(
body: BlockBody { attestations },
};
let mut post_state = head_state.clone();
// ethlambda runs the STF once after selection (it projects justification
// incrementally instead of re-running the STF per loop round), so this is
// a single `stf_simulate` observation per build.
let stf_start = Instant::now();
process_slots(&mut post_state, slot)?;
process_block(&mut post_state, &final_block)?;
metrics::observe_block_proposal_phase("stf_simulate", stf_start.elapsed());
final_block.state_root = post_state.hash_tree_root();

metrics::inc_block_proposal_child_payloads_consumed(child_payloads_consumed as u64);
metrics::observe_block_proposal_attestation_data_selected(final_block.body.attestations.len());
metrics::observe_block_proposal_aggregates_selected(aggregated_signatures.len());

let post_checkpoints = PostBlockCheckpoints {
justified: post_state.latest_justified,
finalized: post_state.latest_finalized,
Expand Down Expand Up @@ -156,6 +174,7 @@ fn select_attestations(
let (att_data, proofs) = &chain.aggregated_payloads[&data_root];

processed_data_roots.insert(data_root);
metrics::inc_block_proposal_attestation_builds();

let before = selected.len();
extend_proofs_greedily(proofs, &mut selected, att_data);
Expand Down
102 changes: 102 additions & 0 deletions crates/blockchain/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ pub const ATTESTATION_AGGREGATE_COVERAGE_SECTIONS: &[&str] = &[
/// locally-aggregated pre-merge (`timely`) payloads.
pub const ATTESTATION_AGGREGATE_COVERAGE_DIFF_DIRECTIONS: &[&str] = &["block_only", "timely_only"];

/// Phase labels for `lean_block_proposal_attestation_build_phase_seconds`.
///
/// `select_payloads`: greedy per-`AttestationData` proof selection.
/// `compact`: recursive merge of proofs sharing the same `AttestationData`.
/// `stf_simulate`: the single candidate-block state transition that seals the
/// state root. Unlike leanSpec (which re-runs the STF inside a fixed-point
/// loop), ethlambda projects justification/finalization incrementally during
/// `select_payloads` and runs the STF exactly once, so its `stf_simulate`
/// timing is a single observation per build rather than one per loop round.
pub const BLOCK_PROPOSAL_ATTESTATION_BUILD_PHASES: &[&str] =
&["select_payloads", "compact", "stf_simulate"];

// --- Gauges ---

static LEAN_HEAD_SLOT: std::sync::LazyLock<IntGauge> = std::sync::LazyLock::new(|| {
Expand Down Expand Up @@ -420,6 +432,62 @@ static LEAN_BLOCK_BUILDING_FAILURES_TOTAL: std::sync::LazyLock<IntCounter> =
register_int_counter!("lean_block_building_failures_total", "Failed block builds").unwrap()
});

// --- Block Proposal Attestation Selection (build_block fixed-point loop) ---

static LEAN_BLOCK_PROPOSAL_ATTESTATION_BUILD_PHASE_SECONDS: std::sync::LazyLock<HistogramVec> =
std::sync::LazyLock::new(|| {
register_histogram_vec!(
"lean_block_proposal_attestation_build_phase_seconds",
"Phase-level time in block-proposal attestation selection: select_payloads (greedy \
per-AttestationData proof pick), compact (recursive merge of proofs per \
AttestationData), stf_simulate (candidate block state transition).",
&["phase"],
vec![
0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0
]
)
.unwrap()
});

static LEAN_BLOCK_PROPOSAL_ATTESTATION_BUILDS_TOTAL: std::sync::LazyLock<IntCounter> =
std::sync::LazyLock::new(|| {
register_int_counter!(
"lean_block_proposal_attestation_builds_total",
"Attestations selected during block-proposal selection (one increment per \
selection-loop round that picks an AttestationData)."
)
.unwrap()
});

static LEAN_BLOCK_PROPOSAL_CHILD_PAYLOADS_CONSUMED_TOTAL: std::sync::LazyLock<IntCounter> =
std::sync::LazyLock::new(|| {
register_int_counter!(
"lean_block_proposal_child_payloads_consumed_total",
"Child aggregated payloads selected during greedy proof picking (before compaction)."
)
.unwrap()
});

static LEAN_BLOCK_PROPOSAL_ATTESTATION_DATA_SELECTED: std::sync::LazyLock<Histogram> =
std::sync::LazyLock::new(|| {
register_histogram!(
"lean_block_proposal_attestation_data_selected",
"Distinct AttestationData entries in the proposal block body",
vec![0.0, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0]
)
.unwrap()
});

static LEAN_BLOCK_PROPOSAL_AGGREGATES_SELECTED: std::sync::LazyLock<Histogram> =
std::sync::LazyLock::new(|| {
register_histogram!(
"lean_block_proposal_aggregates_selected",
"Aggregated signature proofs in the proposal result after compaction",
vec![0.0, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0]
)
.unwrap()
});

// --- Sync Status ---

/// Node synchronization status.
Expand Down Expand Up @@ -512,6 +580,12 @@ pub fn init() {
std::sync::LazyLock::force(&LEAN_BLOCK_BUILDING_TIME_SECONDS);
std::sync::LazyLock::force(&LEAN_BLOCK_BUILDING_SUCCESS_TOTAL);
std::sync::LazyLock::force(&LEAN_BLOCK_BUILDING_FAILURES_TOTAL);
// Block proposal attestation selection
std::sync::LazyLock::force(&LEAN_BLOCK_PROPOSAL_ATTESTATION_BUILD_PHASE_SECONDS);
std::sync::LazyLock::force(&LEAN_BLOCK_PROPOSAL_ATTESTATION_BUILDS_TOTAL);
std::sync::LazyLock::force(&LEAN_BLOCK_PROPOSAL_CHILD_PAYLOADS_CONSUMED_TOTAL);
std::sync::LazyLock::force(&LEAN_BLOCK_PROPOSAL_ATTESTATION_DATA_SELECTED);
std::sync::LazyLock::force(&LEAN_BLOCK_PROPOSAL_AGGREGATES_SELECTED);
// Sync status
std::sync::LazyLock::force(&LEAN_NODE_SYNC_STATUS);
}
Expand Down Expand Up @@ -739,6 +813,34 @@ pub fn inc_block_building_failures() {
LEAN_BLOCK_BUILDING_FAILURES_TOTAL.inc();
}

/// Observe the duration of a block-proposal attestation-selection phase.
/// `phase` must be one of [`BLOCK_PROPOSAL_ATTESTATION_BUILD_PHASES`].
pub fn observe_block_proposal_phase(phase: &str, elapsed: Duration) {
LEAN_BLOCK_PROPOSAL_ATTESTATION_BUILD_PHASE_SECONDS
.with_label_values(&[phase])
.observe(elapsed.as_secs_f64());
}

/// Increment the completed block-proposal attestation selection runs counter.
pub fn inc_block_proposal_attestation_builds() {
LEAN_BLOCK_PROPOSAL_ATTESTATION_BUILDS_TOTAL.inc();
}

/// Increment the greedily-selected child payloads counter (before compaction).
pub fn inc_block_proposal_child_payloads_consumed(count: u64) {
LEAN_BLOCK_PROPOSAL_CHILD_PAYLOADS_CONSUMED_TOTAL.inc_by(count);
}

/// Observe the number of distinct `AttestationData` entries in the proposal block body.
pub fn observe_block_proposal_attestation_data_selected(count: usize) {
LEAN_BLOCK_PROPOSAL_ATTESTATION_DATA_SELECTED.observe(count as f64);
}

/// Observe the number of aggregated signature proofs in the proposal result after compaction.
pub fn observe_block_proposal_aggregates_selected(count: usize) {
LEAN_BLOCK_PROPOSAL_AGGREGATES_SELECTED.observe(count as f64);
}

/// Set the node sync status. Sets the given status label to 1 and all others to 0.
pub fn set_node_sync_status(status: SyncStatus) {
let active = status.as_str();
Expand Down
5 changes: 5 additions & 0 deletions docs/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ The exposed metrics follow [the leanMetrics specification](https://github.com/le
| `lean_block_building_time_seconds` | Histogram | Time taken to build a block | On block production | | 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 0.75, 1 | βœ… |
| `lean_block_building_success_total` | Counter | Successful block builds | On block production | | | βœ… |
| `lean_block_building_failures_total` | Counter | Failed block builds (error building the block, signing the block root, or processing it locally) | On block production failure | | | βœ… |
| `lean_block_proposal_attestation_build_phase_seconds` | Histogram | Phase-level time in block-proposal attestation selection | On block production | phase=select_payloads,compact,stf_simulate | 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2, 4, 8 | βœ… |
| `lean_block_proposal_attestation_builds_total` | Counter | Attestations selected during block-proposal selection (one per selection-loop round that picks an `AttestationData`) | On each attestation selection | | | βœ… |
| `lean_block_proposal_child_payloads_consumed_total` | Counter | Child aggregated payloads selected during greedy proof picking (before compaction) | On block production | | | βœ… |
| `lean_block_proposal_attestation_data_selected` | Histogram | Distinct `AttestationData` entries in the proposal block body | On block production | | 0, 1, 2, 4, 8, 16, 32 | βœ… |
| `lean_block_proposal_aggregates_selected` | Histogram | Aggregated signature proofs in the proposal result after compaction | On block production | | 0, 1, 2, 4, 8, 16, 32, 64, 128 | βœ… |

## Fork-Choice Metrics

Expand Down
Loading