Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6296d16
perf: add lru cache for load_executed_tipset to speed up hot queries
hanabi1224 Mar 18, 2026
5fdd7b1
resolve AI comments
hanabi1224 Mar 18, 2026
d0b182b
populate cache after state computation
hanabi1224 Mar 18, 2026
eec3715
Merge branch 'hm/cache-load_executed_tipset' of github.com:ChainSafe/…
hanabi1224 Mar 18, 2026
62eb46c
fix
hanabi1224 Mar 18, 2026
a1fb72e
Merge remote-tracking branch 'origin/main' into hm/cache-load_execute…
hanabi1224 Mar 18, 2026
a12b2b2
code comment
hanabi1224 Mar 18, 2026
0a6a7ff
Merge remote-tracking branch 'origin/main' into hm/cache-load_execute…
hanabi1224 Mar 19, 2026
4ec750c
fix comment
hanabi1224 Mar 19, 2026
17417c7
cleanup
hanabi1224 Mar 21, 2026
5ad8102
Merge remote-tracking branch 'origin/main' into hm/cache-load_execute…
hanabi1224 Mar 21, 2026
bc454b1
Merge branch 'main' into hm/cache-load_executed_tipset
hanabi1224 Mar 23, 2026
194ef9d
fix
hanabi1224 Mar 23, 2026
35ae695
Merge remote-tracking branch 'origin/main' into hm/cache-load_execute…
hanabi1224 Mar 23, 2026
d34d65e
lint
hanabi1224 Mar 23, 2026
1fe1825
fix
hanabi1224 Mar 23, 2026
842d1db
optimize
hanabi1224 Mar 23, 2026
d1aabf0
Merge branch 'main' into hm/cache-load_executed_tipset
hanabi1224 Mar 23, 2026
4004e0f
Merge remote-tracking branch 'origin/main' into hm/cache-load_execute…
hanabi1224 Mar 23, 2026
653b2f0
make ExecutedTipset cheap to clone
hanabi1224 Mar 23, 2026
f2b17de
Merge remote-tracking branch 'origin/main' into hm/cache-load_execute…
hanabi1224 Mar 24, 2026
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
15 changes: 3 additions & 12 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,10 +500,7 @@ impl Block {
let ExecutedTipset {
state_root,
executed_messages,
} = ctx
.state_manager
.load_executed_tipset_without_events(&tipset)
.await?;
} = ctx.state_manager.load_executed_tipset(&tipset).await?;
let has_transactions = !executed_messages.is_empty();
let state_tree = ctx.state_manager.get_state_tree(&state_root)?;

Expand Down Expand Up @@ -1419,10 +1416,7 @@ async fn get_block_receipts<DB: Blockstore + Send + Sync + 'static>(
let ExecutedTipset {
state_root,
executed_messages,
} = ctx
.state_manager
.load_executed_tipset_without_events(&ts_ref)
.await?;
} = ctx.state_manager.load_executed_tipset(&ts_ref).await?;

// Load the state tree
let state_tree = ctx.state_manager.get_state_tree(&state_root)?;
Expand Down Expand Up @@ -1933,10 +1927,7 @@ async fn eth_fee_history<B: Blockstore + Send + Sync + 'static>(
let base_fee = &ts.block_headers().first().parent_base_fee;
let ExecutedTipset {
executed_messages, ..
} = ctx
.state_manager
.load_executed_tipset_without_events(&ts)
.await?;
} = ctx.state_manager.load_executed_tipset(&ts).await?;
let mut tx_gas_rewards = Vec::with_capacity(executed_messages.len());
for ExecutedMessage {
message, receipt, ..
Expand Down
6 changes: 3 additions & 3 deletions src/shim/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use fvm_shared4::receipt::Receipt as Receipt_v4;
use fvm2::executor::ApplyRet as ApplyRet_v2;
use fvm3::executor::ApplyRet as ApplyRet_v3;
use fvm4::executor::ApplyRet as ApplyRet_v4;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use spire_enum::prelude::delegated_enum;
use std::borrow::Borrow as _;

Expand Down Expand Up @@ -79,7 +79,7 @@ impl ApplyRet {
// Note: it's impossible to properly derive Deserialize.
// To deserialize into `Receipt`, refer to `fn get_parent_receipt`
#[delegated_enum(impl_conversions)]
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Receipt {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
V2(Receipt_v2),
Expand Down Expand Up @@ -225,7 +225,7 @@ impl ActorEvent {

/// Event with extra information stamped by the FVM.
#[delegated_enum(impl_conversions)]
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum StampedEvent {
V3(StampedEvent_v3),
Expand Down
76 changes: 40 additions & 36 deletions src/state_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ use crate::shim::{
use crate::state_manager::cache::TipsetStateCache;
use crate::state_manager::chain_rand::draw_randomness;
use crate::state_migration::run_state_migrations;
use crate::utils::get_size::GetSize;
use crate::utils::cache::SizeTrackingLruCache;
use crate::utils::get_size::{GetSize, vec_heap_size_helper};
use ahash::{HashMap, HashMapExt};
use anyhow::{Context as _, bail, ensure};
use bls_signatures::{PublicKey as BlsPublicKey, Serialize as _};
Expand All @@ -78,6 +79,7 @@ use rayon::prelude::ParallelBridge;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::ops::RangeInclusive;
use std::sync::LazyLock;
use std::time::Duration;
use std::{num::NonZeroUsize, sync::Arc};
use tokio::sync::{RwLock, broadcast::error::RecvError};
Expand All @@ -93,26 +95,40 @@ type CidPair = (Cid, Cid);
///
/// Includes the executed message itself, the execution receipt, and
/// optional events emitted by the actor during execution.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutedMessage {
pub message: ChainMessage,
pub receipt: Receipt,
pub events: Option<Vec<StampedEvent>>,
}

impl GetSize for ExecutedMessage {
fn get_heap_size(&self) -> usize {
self.message.get_heap_size()
+ self.receipt.get_heap_size()
+ self
.events
.as_ref()
.map(vec_heap_size_helper)
.unwrap_or_default()
}
}

/// Aggregated execution result for a tipset.
///
/// `state_root` is the resulting state tree root after message execution
/// and `executed_messages` contains per-message execution details.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutedTipset {
pub state_root: Cid,
pub executed_messages: Vec<ExecutedMessage>,
}

/// Options controlling how `load_executed_tipset` fetches extra execution data.
///
/// `include_events` toggles whether event logs are loaded from receipts.
pub struct LoadExecutedTipsetOptions {
pub include_events: bool,
impl GetSize for ExecutedTipset {
fn get_heap_size(&self) -> usize {
// state_root(Cid) has no heap allocation, so we only calculate the heap size of executed_messages
vec_heap_size_helper(&self.executed_messages)
}
}

#[derive(Debug, Default, Clone, GetSize)]
Expand Down Expand Up @@ -471,48 +487,36 @@ where
.await
}

/// Load an executed tipset, including message receipts and state root,
/// without loading event logs from receipts.
pub async fn load_executed_tipset_without_events(
self: &Arc<Self>,
ts: &Tipset,
) -> anyhow::Result<ExecutedTipset> {
let receipt_ts = self.chain_store().load_child_tipset(ts).ok();
self.load_executed_tipset_inner(
ts,
receipt_ts.as_ref(),
LoadExecutedTipsetOptions {
include_events: false,
},
)
.await
}

/// Load an executed tipset, including message receipts and state root,
/// with event logs loaded when available.
/// Load an executed tipset, including state root, message receipts and events with caching.
pub async fn load_executed_tipset(
self: &Arc<Self>,
ts: &Tipset,
) -> anyhow::Result<ExecutedTipset> {
// A tipset key should always map to a deterministic state output, so it's safe to cache the entire executed tipset with the same key.
static CACHE: LazyLock<SizeTrackingLruCache<TipsetKey, ExecutedTipset>> =
LazyLock::new(|| {
SizeTrackingLruCache::new_with_metrics(
"executed_tipset".into(),
nonzero!(1024usize),
)
});
if let Some(cached) = CACHE.get_cloned(ts.key()) {
return Ok(cached);
}
let receipt_ts = self.chain_store().load_child_tipset(ts).ok();
self.load_executed_tipset_inner(
ts,
receipt_ts.as_ref(),
LoadExecutedTipsetOptions {
include_events: true,
},
)
.await
let result = self
.load_executed_tipset_inner(ts, receipt_ts.as_ref())
.await?;
CACHE.push(ts.key().clone(), result.clone());
Ok(result)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

async fn load_executed_tipset_inner(
self: &Arc<Self>,
msg_ts: &Tipset,
// when `msg_ts` is the current head, `receipt_ts` is `None`
receipt_ts: Option<&Tipset>,
options: LoadExecutedTipsetOptions,
) -> anyhow::Result<ExecutedTipset> {
let LoadExecutedTipsetOptions { include_events } = options;
if let Some(receipt_ts) = receipt_ts {
anyhow::ensure!(
msg_ts.key() == receipt_ts.parents(),
Expand Down Expand Up @@ -546,7 +550,7 @@ where
);
let mut executed_messages = Vec::with_capacity(messages.len());
for (message, receipt) in messages.into_iter().zip(receipts.into_iter()) {
let events = if include_events && let Some(events_root) = receipt.events_root() {
let events = if let Some(events_root) = receipt.events_root() {
Some(
match StampedEvent::get_events(self.cs.blockstore(), &events_root) {
Ok(events) => events,
Expand Down
4 changes: 4 additions & 0 deletions src/utils/get_size/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ macro_rules! impl_vec_alike_heap_size_helper {
};
}

pub fn vec_heap_size_helper<T: GetSize>(v: &Vec<T>) -> usize {
impl_vec_alike_heap_size_helper!(v, T)
}

pub fn vec_heap_size_with_fn_helper<T>(v: &Vec<T>, get_heap_size: impl Fn(&T) -> usize) -> usize {
impl_vec_alike_heap_size_with_fn_helper!(v, T, std::mem::size_of::<T>, get_heap_size)
}
Expand Down
Loading