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
45 changes: 45 additions & 0 deletions crates/blockchain/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,48 @@ pub fn set_node_start_time() {
.as_secs();
LEAN_NODE_START_TIME_SECONDS.set(timestamp as i64);
}

/// Increment the valid attestations counter.
pub fn inc_attestations_valid(source: &str) {
static LEAN_ATTESTATIONS_VALID_TOTAL: std::sync::LazyLock<prometheus::IntCounterVec> =
std::sync::LazyLock::new(|| {
prometheus::register_int_counter_vec!(
"lean_attestations_valid_total",
"Count of valid attestations",
&["source"]
)
.unwrap()
});
LEAN_ATTESTATIONS_VALID_TOTAL
.with_label_values(&[source])
.inc();
}

/// Increment the invalid attestations counter.
pub fn inc_attestations_invalid(source: &str) {
static LEAN_ATTESTATIONS_INVALID_TOTAL: std::sync::LazyLock<prometheus::IntCounterVec> =
std::sync::LazyLock::new(|| {
prometheus::register_int_counter_vec!(
"lean_attestations_invalid_total",
"Count of invalid attestations",
&["source"]
)
.unwrap()
});
LEAN_ATTESTATIONS_INVALID_TOTAL
.with_label_values(&[source])
.inc();
}

/// Increment the fork choice reorgs counter.
pub fn inc_fork_choice_reorgs() {
static LEAN_FORK_CHOICE_REORGS_TOTAL: std::sync::LazyLock<prometheus::IntCounter> =
std::sync::LazyLock::new(|| {
prometheus::register_int_counter!(
"lean_fork_choice_reorgs_total",
"Count of fork choice reorganizations"
)
.unwrap()
});
LEAN_FORK_CHOICE_REORGS_TOTAL.inc();
}
63 changes: 59 additions & 4 deletions crates/blockchain/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use ethlambda_types::{
};
use tracing::{info, trace, warn};

use crate::SECONDS_PER_SLOT;
use crate::{SECONDS_PER_SLOT, metrics};

const JUSTIFICATION_LOOKBACK_SLOTS: u64 = 3;

Expand Down Expand Up @@ -175,13 +175,20 @@ impl Store {
}

pub fn update_head(&mut self) {
let head = ethlambda_fork_choice::compute_lmd_ghost_head(
let old_head = self.head;
let new_head = ethlambda_fork_choice::compute_lmd_ghost_head(
self.latest_justified.root,
&self.blocks,
&self.latest_known_attestations,
0,
);
self.head = head;

if is_reorg(old_head, new_head, &self.blocks) {
metrics::inc_fork_choice_reorgs();
info!(%old_head, %new_head, "Fork choice reorg detected");
}

self.head = new_head;
}

pub fn update_safe_target(&mut self) {
Expand Down Expand Up @@ -308,7 +315,10 @@ impl Store {
validator_id,
data: signed_attestation.message,
};
self.validate_attestation(&attestation)?;
if let Err(err) = self.validate_attestation(&attestation) {
metrics::inc_attestations_invalid("gossip");
return Err(err);
}
let target = attestation.data.target;
let target_state = self
.states
Expand Down Expand Up @@ -339,6 +349,7 @@ impl Store {
let signature = ValidatorSignature::from_bytes(&signed_attestation.signature)
.map_err(|_| StoreError::SignatureDecodingFailed)?;
self.gossip_signatures.insert(signature_key, signature);
metrics::inc_attestations_valid("gossip");
}
Ok(())
}
Expand Down Expand Up @@ -501,6 +512,9 @@ impl Store {
// TODO: validate attestations before processing
if let Err(err) = self.on_attestation(attestation, true) {
warn!(%slot, %validator_id, %err, "Invalid attestation in block");
metrics::inc_attestations_invalid("block");
} else {
metrics::inc_attestations_valid("block");
}
}
}
Expand Down Expand Up @@ -716,6 +730,47 @@ impl Store {
}
}

/// Check if a head change represents a reorg.
///
/// A reorg occurs when the chains diverge - i.e., when walking back from the higher
/// slot head to the lower slot head's slot, we don't arrive at the lower slot head.
fn is_reorg(old_head: H256, new_head: H256, blocks: &HashMap<H256, Block>) -> bool {
if new_head == old_head {
return false;
}

let Some(old_head_block) = blocks.get(&old_head) else {
return false;
};

let Some(new_head_block) = blocks.get(&new_head) else {
return false;
};

let old_slot = old_head_block.slot;
let new_slot = new_head_block.slot;

// Determine which head has the higher slot and walk back from it
let (mut current_root, target_slot, target_root) = if new_slot >= old_slot {
(new_head, old_slot, old_head)
} else {
(old_head, new_slot, new_head)
};

// Walk back through the chain until we reach the target slot
while let Some(current_block) = blocks.get(&current_root) {
if current_block.slot <= target_slot {
// We've reached the target slot - check if we're at the target block
return current_root != target_root;
}
current_root = current_block.parent_root;
}

// Couldn't walk back far enough (missing blocks in chain)
// Conservative: assume no reorg if we can't determine
false
}

/// Errors that can occur during Store operations.
#[derive(Debug, thiserror::Error)]
pub enum StoreError {
Expand Down
6 changes: 3 additions & 3 deletions docs/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ The exposed metrics follow [the leanMetrics specification](https://github.com/le
| `lean_current_slot` | Gauge | Current slot of the lean chain | On scrape | | | ✅(*) |
| `lean_safe_target_slot` | Gauge | Safe target slot | On safe target update | | | ✅ |
|`lean_fork_choice_block_processing_time_seconds`| Histogram | Time taken to process block | On fork choice process block | | 0.005, 0.01, 0.025, 0.05, 0.1, 1 | □ |
|`lean_attestations_valid_total`| Counter | Total number of valid attestations | On validate attestation | source=block,gossip | | |
|`lean_attestations_invalid_total`| Counter | Total number of invalid attestations | On validate attestation | source=block,gossip | | |
|`lean_attestations_valid_total`| Counter | Total number of valid attestations | On validate attestation | source=block,gossip | | |
|`lean_attestations_invalid_total`| Counter | Total number of invalid attestations | On validate attestation | source=block,gossip | | |
|`lean_attestation_validation_time_seconds`| Histogram | Time taken to validate attestation | On validate attestation | | 0.005, 0.01, 0.025, 0.05, 0.1, 1 | □ |
| `lean_fork_choice_reorgs_total` | Counter | Total number of fork choice reorgs | On fork choice reorg | | | |
| `lean_fork_choice_reorgs_total` | Counter | Total number of fork choice reorgs | On fork choice reorg | | | |
| `lean_fork_choice_reorg_depth` | Histogram | Depth of fork choice reorgs (in blocks) | On fork choice reorg | | 1, 2, 3, 5, 7, 10, 20, 30, 50, 100 | □ |

## State Transition Metrics
Expand Down