diff --git a/fendermint/vm/interpreter/src/fvm/checkpoint.rs b/fendermint/vm/interpreter/src/fvm/checkpoint.rs index 5042b7844..cb4b1b465 100644 --- a/fendermint/vm/interpreter/src/fvm/checkpoint.rs +++ b/fendermint/vm/interpreter/src/fvm/checkpoint.rs @@ -22,9 +22,9 @@ use fendermint_vm_genesis::{Power, Validator, ValidatorKey}; use ipc_actors_abis::checkpointing_facet as checkpoint; use ipc_actors_abis::gateway_getter_facet as getter; use ipc_api::staking::ConfigurationNumber; -use ipc_observability::emit; +use ipc_observability::{emit, serde::HexEncodableBlockHash}; -use super::observe::{CheckpointCreated, CheckpointSigned, HexEncodableBlockHash}; +use super::observe::{CheckpointCreated, CheckpointFinalized, CheckpointSigned}; use super::state::ipc::tokens_to_burn; use super::{ broadcast::Broadcaster, @@ -296,6 +296,36 @@ where Ok(()) } +// Emit a CheckpointFinalized trace event if a checkpoint has been finalized on the current block. +pub fn emit_trace_if_check_checkpoint_finalized( + gateway: &GatewayCaller, + state: &mut FvmExecState, +) -> anyhow::Result<()> +where + DB: Blockstore + Clone, +{ + if !gateway.enabled(state)? { + return Ok(()); + } + + let block_height = state.block_height(); + let block_hash = state + .block_hash() + .ok_or_else(|| anyhow!("block hash not set"))?; + + // Check if the checkpoint has been finalized. + let checkpoint_quorum = gateway.checkpoint_info(state, block_height)?; + + if checkpoint_quorum.reached { + emit(CheckpointFinalized { + height: block_height, + hash: HexEncodableBlockHash(block_hash.to_vec()), + }) + } + + Ok(()) +} + fn convert_tokenizables( tokenizables: Vec, ) -> anyhow::Result> { diff --git a/fendermint/vm/interpreter/src/fvm/exec.rs b/fendermint/vm/interpreter/src/fvm/exec.rs index aa468a6cb..835918c70 100644 --- a/fendermint/vm/interpreter/src/fvm/exec.rs +++ b/fendermint/vm/interpreter/src/fvm/exec.rs @@ -195,6 +195,8 @@ where } async fn end(&self, mut state: Self::State) -> anyhow::Result<(Self::State, Self::EndOutput)> { + checkpoint::emit_trace_if_check_checkpoint_finalized(&self.gateway, &mut state)?; + let updates = if let Some((checkpoint, updates)) = checkpoint::maybe_create_checkpoint(&self.gateway, &mut state) .context("failed to create checkpoint")? diff --git a/fendermint/vm/interpreter/src/fvm/observe.rs b/fendermint/vm/interpreter/src/fvm/observe.rs index c3be78aca..adca52e6c 100644 --- a/fendermint/vm/interpreter/src/fvm/observe.rs +++ b/fendermint/vm/interpreter/src/fvm/observe.rs @@ -1,12 +1,9 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -use hex; -use std::fmt; - use ipc_observability::{ - impl_traceable, impl_traceables, lazy_static, register_metrics, Recordable, TraceLevel, - Traceable, + impl_traceable, impl_traceables, lazy_static, register_metrics, serde::HexEncodableBlockHash, + Recordable, TraceLevel, Traceable, }; use prometheus::{ @@ -36,6 +33,8 @@ register_metrics! { "Height of the checkpoint signed", &["validator"] ); + BOTTOMUP_CHECKPOINT_FINALIZED_HEIGHT: IntGauge + = register_int_gauge!("bottomup_checkpoint_finalized_height", "Height of the checkpoint finalized"); } impl_traceables!( @@ -51,7 +50,8 @@ impl_traceables!( TraceLevel::Info, "Bottomup", CheckpointCreated, - CheckpointSigned<'a> + CheckpointSigned<'a>, + CheckpointFinalized ); impl Recordable for MsgExecCheck<'_> { @@ -102,17 +102,6 @@ macro_rules! message_exec_struct { message_exec_struct!(MsgExecCheck, MsgExecEstimate, MsgExecApply, MsgExecCall); -/// Hex encoded hash. -pub type HashHex<'a> = &'a str; -// Hex encodable block hash. -pub struct HexEncodableBlockHash(pub Vec); - -impl fmt::Debug for HexEncodableBlockHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", hex::encode(&self.0)) - } -} - #[derive(Debug)] pub struct CheckpointCreated { pub height: u64, @@ -145,6 +134,18 @@ impl Recordable for CheckpointSigned<'_> { } } +#[derive(Debug)] +pub struct CheckpointFinalized { + pub height: i64, + pub hash: HexEncodableBlockHash, +} + +impl Recordable for CheckpointFinalized { + fn record_metrics(&self) { + BOTTOMUP_CHECKPOINT_FINALIZED_HEIGHT.set(self.height); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/fendermint/vm/interpreter/src/fvm/state/ipc.rs b/fendermint/vm/interpreter/src/fvm/state/ipc.rs index 4cd74d5fc..97c510f23 100644 --- a/fendermint/vm/interpreter/src/fvm/state/ipc.rs +++ b/fendermint/vm/interpreter/src/fvm/state/ipc.rs @@ -146,6 +146,17 @@ impl GatewayCaller { self.getter.call(state, |c| c.get_incomplete_checkpoints()) } + /// Retrieve checkpoint info by block height. + pub fn checkpoint_info( + &self, + state: &mut FvmExecState, + height: i64, + ) -> anyhow::Result { + self.getter.call(state, |c| { + c.get_checkpoint_info(ethers::types::U256::from(height)) + }) + } + /// Apply all pending validator changes, returning the newly adopted configuration number, or 0 if there were no changes. pub fn apply_validator_changes(&self, state: &mut FvmExecState) -> anyhow::Result { self.topdown.call(state, |c| c.apply_finality_changes()) @@ -304,7 +315,7 @@ impl GatewayCaller { height: u64, ) -> anyhow::Result> { let (_, _, addrs, _) = self.getter.call(state, |c| { - c.get_checkpoint_signature_bundle(ethers::types::U256::from(height)) + c.get_checkpoint_signature_bundle(et::U256::from(height)) })?; let addrs = addrs.into_iter().map(|a| a.into()).collect(); diff --git a/ipc/provider/src/checkpoint.rs b/ipc/provider/src/checkpoint.rs index d01572de5..81edd5234 100644 --- a/ipc/provider/src/checkpoint.rs +++ b/ipc/provider/src/checkpoint.rs @@ -4,13 +4,13 @@ use crate::config::Subnet; use crate::manager::{BottomUpCheckpointRelayer, EthSubnetManager}; -use crate::observe::{CheckpointFinalized, HexEncodableBlockHash}; +use crate::observe::CheckpointFinalized; use anyhow::{anyhow, Result}; use futures_util::future::try_join_all; use fvm_shared::address::Address; use fvm_shared::clock::ChainEpoch; use ipc_api::checkpoint::{BottomUpCheckpointBundle, QuorumReachedEvent}; -use ipc_observability::emit; +use ipc_observability::{emit, serde::HexEncodableBlockHash}; use ipc_wallet::{EthKeyAddress, PersistentKeyStore}; use std::cmp::max; use std::fmt::{Display, Formatter}; diff --git a/ipc/provider/src/observe.rs b/ipc/provider/src/observe.rs index 187f0d13e..604c8c954 100644 --- a/ipc/provider/src/observe.rs +++ b/ipc/provider/src/observe.rs @@ -1,11 +1,9 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: Apache-2.0, MIT -use std::fmt; - use ipc_observability::{ - impl_traceable, impl_traceables, lazy_static, register_metrics, Recordable, TraceLevel, - Traceable, + impl_traceable, impl_traceables, lazy_static, register_metrics, serde::HexEncodableBlockHash, + Recordable, TraceLevel, Traceable, }; use prometheus::{register_int_gauge, IntGauge, Registry}; @@ -16,15 +14,6 @@ register_metrics! { impl_traceables!(TraceLevel::Info, "Bottomup", CheckpointFinalized); -// Hex encodable block hash. -pub struct HexEncodableBlockHash(pub Vec); - -impl fmt::Debug for HexEncodableBlockHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", hex::encode(&self.0)) - } -} - #[derive(Debug)] pub struct CheckpointFinalized { pub height: i64,