From 069e46ea9c2ce9271634faf5d41d6ae3b6177077 Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Wed, 3 Dec 2025 12:43:15 +0530 Subject: [PATCH 01/13] store the fvm events in blockstore and fetch from blockstore if not available in cache --- src/chain/store/chain_store.rs | 2 +- src/rpc/methods/chain.rs | 4 +-- src/rpc/methods/eth/filter/mod.rs | 7 +++--- src/shim/executor.rs | 29 ++++++++++++++++++++- src/state_manager/mod.rs | 42 ++++++++++++++++++++++++++++--- 5 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index 9c1e7331b6ed..da26bddc8f91 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -209,7 +209,7 @@ where Ok(()) } - pub fn get_tipset_key(&self, key: &Cid) -> Result, Error> { + pub fn get_tipset_key_by_events_root(&self, key: &Cid) -> Result, Error> { Ok(self.indices.read_obj(key)?) } diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index 02b1e9fbffe5..f4914784f6b7 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -257,8 +257,8 @@ impl RpcMethod<1> for ChainGetEvents { let tsk = ctx .state_manager .chain_store() - .get_tipset_key(&root_cid)? - .with_context(|| format!("can't find events with cid {root_cid}"))?; + .get_tipset_key_by_events_root(&root_cid)? + .with_context(|| format!("can't find tipset for events root {root_cid}"))?; let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?; diff --git a/src/rpc/methods/eth/filter/mod.rs b/src/rpc/methods/eth/filter/mod.rs index 2b1ab8b464c6..4ec0cf4e6c39 100644 --- a/src/rpc/methods/eth/filter/mod.rs +++ b/src/rpc/methods/eth/filter/mod.rs @@ -45,7 +45,6 @@ use anyhow::{Context, Error, anyhow, bail, ensure}; use cid::Cid; use fvm_ipld_blockstore::Blockstore; use fvm_ipld_encoding::IPLD_RAW; -use itertools::Itertools; use serde::*; use std::ops::RangeInclusive; use std::sync::Arc; @@ -384,11 +383,13 @@ impl EthEventHandler { .filter(|(cid, _)| cid.as_ref() == Some(events_root)) .map(|(_, v)| v); - let chain_events = filtered_events + // Do NOT deduplicate events - the AMT can legitimately contain duplicate events + // if a contract emits the same event multiple times. We must preserve the exact + // order and count of events as stored in the AMT. + let chain_events: Vec = filtered_events .into_iter() .flat_map(|events| events.into_iter()) .map(Into::into) - .unique() .collect(); Ok(chain_events) diff --git a/src/shim/executor.rs b/src/shim/executor.rs index 2dfebf7b9cfe..4c26617bef18 100644 --- a/src/shim/executor.rs +++ b/src/shim/executor.rs @@ -7,7 +7,7 @@ use crate::shim::{ }; use crate::utils::get_size::{GetSize, vec_heap_size_with_fn_helper}; use cid::Cid; -use fil_actors_shared::fvm_ipld_amt::Amtv0; +use fil_actors_shared::fvm_ipld_amt::{Amt, Amtv0}; use fvm_ipld_blockstore::Blockstore; use fvm_ipld_encoding::RawBytes; use fvm_shared2::receipt::Receipt as Receipt_v2; @@ -339,6 +339,7 @@ impl ActorEvent { /// Event with extra information stamped by the FVM. #[derive(Clone, Debug, Serialize)] +#[serde(untagged)] pub enum StampedEvent { V3(StampedEvent_v3), V4(StampedEvent_v4), @@ -385,6 +386,32 @@ impl StampedEvent { Self::V4(v4) => v4.event.clone().into(), } } + + /// Loads events directly from the events AMT root CID. + /// Returns events in the exact order they are stored in the AMT. + pub fn get_events( + db: &DB, + events_root: &Cid, + ) -> anyhow::Result> { + let mut events = Vec::new(); + + // Try StampedEvent_v4 first (StampedEvent_v4 and StampedEvent_v3 are identical, use v4 here) + if let Ok(amt) = Amt::::load(events_root, db) { + amt.for_each(|_, event| { + events.push(StampedEvent::V4(event.clone())); + Ok(()) + })?; + } else { + // Fallback to StampedEvent_v3 + let amt = Amt::::load(events_root, db)?; + amt.for_each(|_, event| { + events.push(StampedEvent::V3(event.clone())); + Ok(()) + })?; + } + + Ok(events) + } } #[cfg(test)] diff --git a/src/state_manager/mod.rs b/src/state_manager/mod.rs index 24f501d092ef..bf44c7126634 100644 --- a/src/state_manager/mod.rs +++ b/src/state_manager/mod.rs @@ -61,14 +61,14 @@ use crate::utils::get_size::{ GetSize, vec_heap_size_helper, vec_with_stack_only_item_heap_size_helper, }; use ahash::{HashMap, HashMapExt}; -use anyhow::{Context as _, bail}; +use anyhow::{Context as _, bail, ensure}; use bls_signatures::{PublicKey as BlsPublicKey, Serialize as _}; use chain_rand::ChainRand; use cid::Cid; pub use circulating_supply::GenesisInfo; use fil_actor_verifreg_state::v12::DataCap; use fil_actor_verifreg_state::v13::ClaimID; -use fil_actors_shared::fvm_ipld_amt::Amtv0 as Amt; +use fil_actors_shared::fvm_ipld_amt::{Amt, Amtv0}; use fil_actors_shared::fvm_ipld_bitfield::BitField; use fil_actors_shared::v12::runtime::DomainSeparationTag; use fil_actors_shared::v13::runtime::Policy; @@ -90,6 +90,7 @@ use tokio::sync::{RwLock, broadcast::error::RecvError}; use tracing::{error, info, instrument, trace, warn}; const DEFAULT_TIPSET_CACHE_SIZE: NonZeroUsize = nonzero!(1024usize); +const EVENTS_AMT_BITWIDTH: u32 = 5; /// Intermediary for retrieving state objects and updating actor states. type CidPair = (Cid, Cid); @@ -559,11 +560,24 @@ where let ts = tipset.clone(); let this = Arc::clone(self); let cids = tipset.cids(); + let events_root = events_root.cloned(); self.receipt_event_cache_handler .get_events_or_else( key, Box::new(move || { Box::pin(async move { + // If the events are not in the cache, try to load them from the blockstore + if let Some(events_root) = events_root + && let Ok(stamped_events) = + StampedEvent::get_events(this.blockstore(), &events_root) + { + return Ok(StateEvents { + events: vec![stamped_events], + roots: vec![Some(events_root)], + }); + } + + // If the events are neither in the cache nor in the blockstore, compute them. let state_out = this .compute_tipset_state(ts, NO_CALLBACK, VMTrace::NotTraced) .await?; @@ -1999,8 +2013,28 @@ where let (receipts, events, events_roots) = vm.apply_block_messages(&block_messages, epoch, callback)?; - // step 5: construct receipt root from receipts and flush the state-tree - let receipt_root = Amt::new_from_iter(chain_index.db(), receipts)?; + // step 5: construct receipt root from receipts + let receipt_root = Amtv0::new_from_iter(chain_index.db(), receipts)?; + + // step 6: store events AMTs in the blockstore + for (msg_events, events_root) in events.iter().zip(events_roots.iter()) { + if let Some(event_root) = events_root { + // Store the events AMT - the root CID should match the one computed by FVM + let derived_event_root = Amt::new_from_iter_with_bit_width( + chain_index.db(), + EVENTS_AMT_BITWIDTH, + msg_events.iter(), + ) + .map_err(|e| Error::Other(format!("failed to store events AMT: {e}")))?; + + // Verify the stored root matches the FVM-computed root + ensure!( + derived_event_root.eq(event_root), + "Events AMT root mismatch: derived={derived_event_root}, actual={event_root}." + ); + } + } + let state_root = vm.flush()?; Ok(StateOutput { From d1990da57aa0db1f31ec28d025bab91c34a9f160 Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Fri, 5 Dec 2025 17:50:18 +0530 Subject: [PATCH 02/13] add unit test and update the exisitng event API snapshot test --- src/state_manager/tests.rs | 196 +++++++++++++++++- .../subcommands/api_cmd/test_snapshots.txt | 2 +- 2 files changed, 194 insertions(+), 4 deletions(-) diff --git a/src/state_manager/tests.rs b/src/state_manager/tests.rs index b260c127eebb..5061f7665c9e 100644 --- a/src/state_manager/tests.rs +++ b/src/state_manager/tests.rs @@ -11,7 +11,7 @@ use crate::shim::executor::{Receipt, StampedEvent}; use crate::utils::db::CborStoreExt; use crate::utils::multihash::MultihashCode; use cid::Cid; -use fil_actors_shared::fvm_ipld_amt::Amtv0 as Amt; +use fil_actors_shared::fvm_ipld_amt::{Amt, Amtv0}; use fvm_ipld_blockstore::Blockstore; use fvm_ipld_encoding::DAG_CBOR; use multihash_derive::MultihashDigest; @@ -348,7 +348,7 @@ fn test_update_receipt_and_events_cache_with_events() { event: fvm_shared4::event::ActorEvent { entries: vec![] }, })]; - let events_root = Amt::new_from_iter(&db, mock_event.clone()).unwrap(); + let events_root = Amtv0::new_from_iter(&db, mock_event.clone()).unwrap(); // Create state output with non-empty events let state_output = StateOutput { @@ -385,7 +385,7 @@ fn test_update_receipt_and_events_cache_receipts_success() { events_root: None, }); - let receipt_root = Amt::new_from_iter(&db, vec![receipt]).unwrap(); + let receipt_root = Amtv0::new_from_iter(&db, vec![receipt]).unwrap(); let state_output = StateOutput { state_root: create_dummy_cid(2), @@ -433,3 +433,193 @@ fn test_state_output_get_size() { let s = StateOutputValue::default(); assert_eq!(s.get_size(), std::mem::size_of_val(&s)); } + +fn create_raw_event_v4(emitter: u64, key: &str) -> fvm_shared4::event::StampedEvent { + fvm_shared4::event::StampedEvent { + emitter, + event: fvm_shared4::event::ActorEvent { + entries: vec![fvm_shared4::event::Entry { + flags: fvm_shared4::event::Flags::FLAG_INDEXED_ALL, + key: key.to_string(), + codec: fvm_ipld_encoding::IPLD_RAW, + value: key.as_bytes().to_vec(), + }], + }, + } +} + +fn create_raw_event_v3(emitter: u64, key: &str) -> fvm_shared3::event::StampedEvent { + fvm_shared3::event::StampedEvent { + emitter, + event: fvm_shared3::event::ActorEvent { + entries: vec![fvm_shared3::event::Entry { + flags: fvm_shared3::event::Flags::FLAG_INDEXED_ALL, + key: key.to_string(), + codec: fvm_ipld_encoding::IPLD_RAW, + value: key.as_bytes().to_vec(), + }], + }, + } +} + +#[test] +fn test_events_store_and_retrieve_basic() { + let db: MemoryDB = MemoryDB::default(); + + // Create some test events + let events = [ + create_raw_event_v4(1000, "event1"), + create_raw_event_v4(1001, "event2"), + create_raw_event_v4(1002, "event3"), + ]; + + // Store events in AMT with the same bitwidth as used in apply_block_messages + let events_root = + Amt::new_from_iter_with_bit_width(&db, EVENTS_AMT_BITWIDTH, events.iter()).unwrap(); + + // Retrieve events from the AMT + let retrieved_events = StampedEvent::get_events(&db, &events_root).unwrap(); + + // Verify count matches + assert_eq!(retrieved_events.len(), 3); + + // Verify content matches + assert_eq!(retrieved_events[0].emitter(), 1000); + assert_eq!(retrieved_events[1].emitter(), 1001); + assert_eq!(retrieved_events[2].emitter(), 1002); +} + +#[test] +fn test_events_entries_are_preserved_when_duplicates_are_stored() { + let db = MemoryDB::default(); + + // Create events with intentional duplicates (same content) + let event1 = create_raw_event_v4(1001, "event1"); + let event2 = create_raw_event_v4(1002, "event2"); + let event3 = create_raw_event_v4(1003, "event3"); + + let events = [event1.clone(), event1.clone(), event2, event3, event1]; + let events_root = + Amt::new_from_iter_with_bit_width(&db, EVENTS_AMT_BITWIDTH, events.iter()).unwrap(); + let retrieved_events = StampedEvent::get_events(&db, &events_root).unwrap(); + assert_eq!(retrieved_events.len(), 5); + // Verify the duplicates are at correct positions + assert_eq!(retrieved_events[0].emitter(), 1001); + assert_eq!(retrieved_events[1].emitter(), 1001); // duplicate + assert_eq!(retrieved_events[2].emitter(), 1002); + assert_eq!(retrieved_events[3].emitter(), 1003); + assert_eq!(retrieved_events[4].emitter(), 1001); // non consecutive duplicate +} + +#[test] +fn test_events_preserve_order() { + let db = MemoryDB::default(); + + // Create events with specific emitter IDs to track order + let events = [ + create_raw_event_v4(100, "first"), + create_raw_event_v4(200, "second"), + create_raw_event_v4(300, "third"), + create_raw_event_v4(400, "fourth"), + create_raw_event_v4(500, "fifth"), + ]; + let events_root = + Amt::new_from_iter_with_bit_width(&db, EVENTS_AMT_BITWIDTH, events.iter()).unwrap(); + let retrieved_events = StampedEvent::get_events(&db, &events_root).unwrap(); + + assert_eq!(retrieved_events.len(), 5); + assert_eq!(retrieved_events[0].emitter(), 100); + assert_eq!(retrieved_events[1].emitter(), 200); + assert_eq!(retrieved_events[2].emitter(), 300); + assert_eq!(retrieved_events[3].emitter(), 400); + assert_eq!(retrieved_events[4].emitter(), 500); +} + +#[test] +fn test_events_same_content_same_cid() { + let db = MemoryDB::default(); + + // Create identical event lists + let events1 = [ + create_raw_event_v4(1000, "event_a"), + create_raw_event_v4(1001, "event_b"), + ]; + let events2 = [ + create_raw_event_v4(1000, "event_a"), + create_raw_event_v4(1001, "event_b"), + ]; + + // Store both lists + let root1 = + Amt::new_from_iter_with_bit_width(&db, EVENTS_AMT_BITWIDTH, events1.iter()).unwrap(); + let root2 = + Amt::new_from_iter_with_bit_width(&db, EVENTS_AMT_BITWIDTH, events2.iter()).unwrap(); + + // Same content should produce same CID + assert_eq!( + root1, root2, + "Identical events should produce identical CIDs" + ); +} + +#[test] +fn test_events_empty_list() { + let db = MemoryDB::default(); + + let events: Vec = vec![]; + let events_root = + Amt::new_from_iter_with_bit_width(&db, EVENTS_AMT_BITWIDTH, events.iter()).unwrap(); + + let retrieved_events = StampedEvent::get_events(&db, &events_root).unwrap(); + assert!( + retrieved_events.is_empty(), + "Empty events list should return empty" + ); +} + +#[test] +fn test_events_v3_store_and_retrieve() { + let db = MemoryDB::default(); + + let events = [ + create_raw_event_v3(2000, "v3_event1"), + create_raw_event_v3(2001, "v3_event2"), + ]; + + // Store V3 events + let events_root = + Amt::new_from_iter_with_bit_width(&db, EVENTS_AMT_BITWIDTH, events.iter()).unwrap(); + let retrieved_events = StampedEvent::get_events(&db, &events_root).unwrap(); + + assert_eq!(retrieved_events.len(), 2); + assert_eq!(retrieved_events[0].emitter(), 2000); + assert_eq!(retrieved_events[1].emitter(), 2001); +} + +#[test] +fn test_identical_events_produce_same_root() { + let db = MemoryDB::default(); + + // Create identical event lists + let events1 = [ + create_raw_event_v4(1000, "event_a"), + create_raw_event_v4(1001, "event_b"), + ]; + let events2 = [ + create_raw_event_v4(1000, "event_a"), + create_raw_event_v4(1001, "event_b"), + ]; + + let root1 = + Amt::new_from_iter_with_bit_width(&db, EVENTS_AMT_BITWIDTH, events1.iter()).unwrap(); + let root2 = + Amt::new_from_iter_with_bit_width(&db, EVENTS_AMT_BITWIDTH, events2.iter()).unwrap(); + + assert_eq!(root1, root2); + let retrieved_events = StampedEvent::get_events(&db, &root1).unwrap(); + + // Each AMT contains 2 events, and since roots are the same, we get 2 events + assert_eq!(retrieved_events.len(), 2); + assert_eq!(retrieved_events[0].emitter(), 1000); + assert_eq!(retrieved_events[1].emitter(), 1001); +} diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index 7adf87862686..e35ad6f7104d 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -4,7 +4,7 @@ filecoin_chaingetblockmessages_1736937164799678.rpcsnap.json.zst filecoin_chaingetevents_1746450533519970.rpcsnap.json.zst filecoin_chaingetevents_1746450533600537.rpcsnap.json.zst filecoin_chaingetevents_1746450551991052.rpcsnap.json.zst -filecoin_chaingetevents_1750327595269729.rpcsnap.json.zst +filecoin_chaingetevents_1764864316078100.rpcsnap.json.zst filecoin_chaingetfinalizedtipset_1759828121342574.rpcsnap.json.zst filecoin_chaingetgenesis_1736937286915866.rpcsnap.json.zst filecoin_chaingetmessage_1758734340836824.rpcsnap.json.zst From b15c1bf979f40005f46a13a49f6fe83e7c759e9c Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Fri, 5 Dec 2025 17:50:56 +0530 Subject: [PATCH 03/13] add empty check while fetching events and small fixes --- src/rpc/methods/eth/filter/mod.rs | 11 +++++++++-- src/state_manager/mod.rs | 15 ++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/rpc/methods/eth/filter/mod.rs b/src/rpc/methods/eth/filter/mod.rs index 4ec0cf4e6c39..902e95f3cfa9 100644 --- a/src/rpc/methods/eth/filter/mod.rs +++ b/src/rpc/methods/eth/filter/mod.rs @@ -285,7 +285,9 @@ impl EthEventHandler { ensure!( messages.len() == events.len(), - "Length of messages and events do not match" + "Length of messages ({}) and events ({}) do not match", + messages.len(), + events.len(), ); let mut event_count = 0; @@ -374,7 +376,12 @@ impl EthEventHandler { .tipset_state_events(tipset, Some(events_root)) .await?; - ensure!(state_events.roots.len() == state_events.events.len()); + ensure!( + state_events.roots.len() == state_events.events.len(), + "State events roots ({}) and events length ({}) mismatch ", + state_events.roots.len(), + state_events.events.len(), + ); let filtered_events = state_events .roots diff --git a/src/state_manager/mod.rs b/src/state_manager/mod.rs index bf44c7126634..1aeb985e624d 100644 --- a/src/state_manager/mod.rs +++ b/src/state_manager/mod.rs @@ -90,7 +90,7 @@ use tokio::sync::{RwLock, broadcast::error::RecvError}; use tracing::{error, info, instrument, trace, warn}; const DEFAULT_TIPSET_CACHE_SIZE: NonZeroUsize = nonzero!(1024usize); -const EVENTS_AMT_BITWIDTH: u32 = 5; +pub const EVENTS_AMT_BITWIDTH: u32 = 5; /// Intermediary for retrieving state objects and updating actor states. type CidPair = (Cid, Cid); @@ -566,18 +566,19 @@ where key, Box::new(move || { Box::pin(async move { - // If the events are not in the cache, try to load them from the blockstore - if let Some(events_root) = events_root - && let Ok(stamped_events) = - StampedEvent::get_events(this.blockstore(), &events_root) + // Try to load events directly from the blockstore + if let Some(stamped_events) = events_root + .as_ref() + .and_then(|root| StampedEvent::get_events(this.blockstore(), root).ok()) + .filter(|events| !events.is_empty()) { return Ok(StateEvents { events: vec![stamped_events], - roots: vec![Some(events_root)], + roots: vec![events_root], }); } - // If the events are neither in the cache nor in the blockstore, compute them. + // Fallback: compute the tipset state if events not found in the blockstore let state_out = this .compute_tipset_state(ts, NO_CALLBACK, VMTrace::NotTraced) .await?; From af907956741b827ae14040a0b6aa58872ffa5408 Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Tue, 9 Dec 2025 18:24:29 +0530 Subject: [PATCH 04/13] refactor get events method and udpdate the snapshot tests --- src/rpc/methods/chain.rs | 11 +---- src/rpc/methods/eth/filter/mod.rs | 41 +++++-------------- src/state_manager/mod.rs | 15 +------ .../subcommands/api_cmd/test_snapshots.txt | 6 +-- 4 files changed, 16 insertions(+), 57 deletions(-) diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index f4914784f6b7..494c5f584895 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -254,16 +254,7 @@ impl RpcMethod<1> for ChainGetEvents { ctx: Ctx, (root_cid,): Self::Params, ) -> Result { - let tsk = ctx - .state_manager - .chain_store() - .get_tipset_key_by_events_root(&root_cid)? - .with_context(|| format!("can't find tipset for events root {root_cid}"))?; - - let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?; - - let events = EthEventHandler::collect_chain_events(&ctx, &ts, &root_cid).await?; - + let events = EthEventHandler::get_events_by_event_root(&ctx, &root_cid)?; Ok(events) } } diff --git a/src/rpc/methods/eth/filter/mod.rs b/src/rpc/methods/eth/filter/mod.rs index 902e95f3cfa9..5d2355f09fba 100644 --- a/src/rpc/methods/eth/filter/mod.rs +++ b/src/rpc/methods/eth/filter/mod.rs @@ -37,7 +37,7 @@ use crate::rpc::reflect::Ctx; use crate::rpc::types::{Event, EventEntry}; use crate::shim::address::Address; use crate::shim::clock::ChainEpoch; -use crate::shim::executor::Entry; +use crate::shim::executor::{Entry, StampedEvent}; use crate::state_manager::StateEvents; use crate::utils::misc::env::env_or_default; use ahash::AHashMap as HashMap; @@ -366,39 +366,20 @@ impl EthEventHandler { Ok(()) } - pub async fn collect_chain_events( + /// Gets events by event root. + pub fn get_events_by_event_root( ctx: &Ctx, - tipset: &Tipset, events_root: &Cid, ) -> anyhow::Result> { - let state_events = ctx - .state_manager - .tipset_state_events(tipset, Some(events_root)) - .await?; - - ensure!( - state_events.roots.len() == state_events.events.len(), - "State events roots ({}) and events length ({}) mismatch ", - state_events.roots.len(), - state_events.events.len(), - ); - - let filtered_events = state_events - .roots - .into_iter() - .zip(state_events.events) - .filter(|(cid, _)| cid.as_ref() == Some(events_root)) - .map(|(_, v)| v); - - // Do NOT deduplicate events - the AMT can legitimately contain duplicate events - // if a contract emits the same event multiple times. We must preserve the exact - // order and count of events as stored in the AMT. - let chain_events: Vec = filtered_events - .into_iter() - .flat_map(|events| events.into_iter()) - .map(Into::into) - .collect(); + let state_events = + match StampedEvent::get_events(ctx.chain_store().blockstore(), events_root) { + Ok(e) => e, + Err(e) => { + return Err(anyhow::anyhow!("load events amt: {}", e)); + } + }; + let chain_events: Vec = state_events.into_iter().map(Into::into).collect(); Ok(chain_events) } diff --git a/src/state_manager/mod.rs b/src/state_manager/mod.rs index 1aeb985e624d..a26132cbe8b2 100644 --- a/src/state_manager/mod.rs +++ b/src/state_manager/mod.rs @@ -554,30 +554,17 @@ where pub async fn tipset_state_events( self: &Arc, tipset: &Tipset, - events_root: Option<&Cid>, + _events_root: Option<&Cid>, ) -> anyhow::Result { let key = tipset.key(); let ts = tipset.clone(); let this = Arc::clone(self); let cids = tipset.cids(); - let events_root = events_root.cloned(); self.receipt_event_cache_handler .get_events_or_else( key, Box::new(move || { Box::pin(async move { - // Try to load events directly from the blockstore - if let Some(stamped_events) = events_root - .as_ref() - .and_then(|root| StampedEvent::get_events(this.blockstore(), root).ok()) - .filter(|events| !events.is_empty()) - { - return Ok(StateEvents { - events: vec![stamped_events], - roots: vec![events_root], - }); - } - // Fallback: compute the tipset state if events not found in the blockstore let state_out = this .compute_tipset_state(ts, NO_CALLBACK, VMTrace::NotTraced) diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index e35ad6f7104d..c00a13f64d3e 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -1,10 +1,10 @@ filecoin_beacongetentry_1741270283524367.rpcsnap.json.zst filecoin_chaingetblock_1736937164811210.rpcsnap.json.zst filecoin_chaingetblockmessages_1736937164799678.rpcsnap.json.zst -filecoin_chaingetevents_1746450533519970.rpcsnap.json.zst -filecoin_chaingetevents_1746450533600537.rpcsnap.json.zst -filecoin_chaingetevents_1746450551991052.rpcsnap.json.zst filecoin_chaingetevents_1764864316078100.rpcsnap.json.zst +filecoin_chaingetevents_1765289237680041.rpcsnap.json.zst +filecoin_chaingetevents_1765289237680294.rpcsnap.json.zst +filecoin_chaingetevents_1765289237681455.rpcsnap.json.zst filecoin_chaingetfinalizedtipset_1759828121342574.rpcsnap.json.zst filecoin_chaingetgenesis_1736937286915866.rpcsnap.json.zst filecoin_chaingetmessage_1758734340836824.rpcsnap.json.zst From 0cf17fd778b5eb6647648fd1d54cba3c683826ab Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Tue, 9 Dec 2025 20:05:36 +0530 Subject: [PATCH 05/13] remove the indices store --- src/chain/store/chain_store.rs | 37 ++--------------- src/chain_sync/chain_follower.rs | 1 - src/daemon/context.rs | 1 - src/daemon/db_util.rs | 7 +--- src/db/car/many.rs | 18 +------- src/db/memory.rs | 20 +-------- src/db/mod.rs | 41 ------------------- src/db/parity_db.rs | 28 +------------ src/libp2p/chain_exchange/provider.rs | 1 - src/rpc/methods/chain.rs | 1 - src/rpc/methods/eth/filter/mod.rs | 3 +- src/rpc/methods/sync.rs | 10 +---- src/state_manager/mod.rs | 6 --- src/state_manager/tests.rs | 1 - src/state_manager/utils.rs | 1 - src/tool/offline_server/server.rs | 1 - .../subcommands/api_cmd/api_compare_tests.rs | 1 - .../api_cmd/generate_test_snapshot.rs | 30 +------------- src/tool/subcommands/api_cmd/test_snapshot.rs | 39 ++++++------------ src/tool/subcommands/index_cmd.rs | 1 - src/tool/subcommands/state_compute_cmd.rs | 2 - 21 files changed, 26 insertions(+), 224 deletions(-) diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index da26bddc8f91..92e179d52a48 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -6,7 +6,7 @@ use super::{ index::{ChainIndex, ResolveNullTipset}, tipset_tracker::TipsetTracker, }; -use crate::db::{EthMappingsStore, EthMappingsStoreExt, IndicesStore, IndicesStoreExt}; +use crate::db::{EthMappingsStore, EthMappingsStoreExt}; use crate::interpreter::{BlockMessages, VMTrace}; use crate::libp2p_bitswap::{BitswapStoreRead, BitswapStoreReadWrite}; use crate::message::{ChainMessage, Message as MessageTrait, SignedMessage}; @@ -82,9 +82,6 @@ pub struct ChainStore { /// Ethereum mappings store eth_mappings: Arc, - /// Indices store - indices: Arc, - /// Needed by the Ethereum mapping. chain_config: Arc, } @@ -121,7 +118,6 @@ where db: Arc, heaviest_tipset_key_provider: Arc, eth_mappings: Arc, - indices: Arc, chain_config: Arc, genesis_block_header: CachingBlockHeader, ) -> anyhow::Result { @@ -139,7 +135,6 @@ where genesis_block_header, validated_blocks, eth_mappings, - indices, chain_config, }; @@ -204,15 +199,6 @@ where .map(|(cid, _)| cid)) } - pub fn put_index(&self, key: &Cid, value: &V) -> Result<(), Error> { - self.indices.write_obj(key, value)?; - Ok(()) - } - - pub fn get_tipset_key_by_events_root(&self, key: &Cid) -> Result, Error> { - Ok(self.indices.read_obj(key)?) - } - /// Expands tipset to tipset with all other headers in the same epoch using /// the tipset tracker. fn expand_tipset(&self, header: CachingBlockHeader) -> Result { @@ -754,15 +740,8 @@ mod tests { message_receipts: Cid::new_v1(DAG_CBOR, MultihashCode::Identity.digest(&[])), ..Default::default() }); - let cs = ChainStore::new( - db.clone(), - db.clone(), - db.clone(), - db, - chain_config, - gen_block.clone(), - ) - .unwrap(); + let cs = + ChainStore::new(db.clone(), db.clone(), db, chain_config, gen_block.clone()).unwrap(); assert_eq!(cs.genesis_block_header(), &gen_block); } @@ -776,15 +755,7 @@ mod tests { ..Default::default() }); - let cs = ChainStore::new( - db.clone(), - db.clone(), - db.clone(), - db, - chain_config, - gen_block, - ) - .unwrap(); + let cs = ChainStore::new(db.clone(), db.clone(), db, chain_config, gen_block).unwrap(); let cid = Cid::new_v1(DAG_CBOR, MultihashCode::Blake2b256.digest(&[1, 2, 3])); assert!(!cs.is_block_validated(&cid)); diff --git a/src/chain_sync/chain_follower.rs b/src/chain_sync/chain_follower.rs index 2027256ecc19..5e15146daefa 100644 --- a/src/chain_sync/chain_follower.rs +++ b/src/chain_sync/chain_follower.rs @@ -923,7 +923,6 @@ mod tests { db.clone(), db.clone(), db.clone(), - db.clone(), Default::default(), genesis_header.clone().into(), ) diff --git a/src/daemon/context.rs b/src/daemon/context.rs index 6f0207d87e3b..3829156135e1 100644 --- a/src/daemon/context.rs +++ b/src/daemon/context.rs @@ -246,7 +246,6 @@ async fn create_state_manager( Arc::clone(db), Arc::new(db.clone()), eth_mappings, - db.writer().clone(), chain_config.clone(), genesis_header.clone(), )?); diff --git a/src/daemon/db_util.rs b/src/daemon/db_util.rs index 5cffedda2c2d..a43c180c4212 100644 --- a/src/daemon/db_util.rs +++ b/src/daemon/db_util.rs @@ -339,14 +339,9 @@ where let epoch = ts.epoch(); let tsk = ts.key().clone(); - let state_output = state_manager + state_manager .compute_tipset_state(ts.clone(), NO_CALLBACK, VMTrace::NotTraced) .await?; - for events_root in state_output.events_roots.iter().flatten() { - tracing::trace!("Indexing events root @{epoch}: {events_root}"); - - state_manager.chain_store().put_index(events_root, &tsk)?; - } delegated_messages.append( &mut state_manager diff --git a/src/db/car/many.rs b/src/db/car/many.rs index 24366ffc07cf..fffa41a63c73 100644 --- a/src/db/car/many.rs +++ b/src/db/car/many.rs @@ -11,8 +11,8 @@ use super::{AnyCar, ZstdFrameCache}; use crate::blocks::TipsetKey; use crate::db::{ - BlockstoreWriteOpsSubscribable, EthMappingsStore, IndicesStore, MemoryDB, PersistentStore, - SettingsStore, SettingsStoreExt, + BlockstoreWriteOpsSubscribable, EthMappingsStore, MemoryDB, PersistentStore, SettingsStore, + SettingsStoreExt, }; use crate::libp2p_bitswap::BitswapStoreReadWrite; use crate::rpc::eth::types::EthHash; @@ -251,20 +251,6 @@ impl EthMappingsStore for ManyCar { } } -impl IndicesStore for ManyCar { - fn read_bin(&self, key: &Cid) -> anyhow::Result>> { - IndicesStore::read_bin(self.writer(), key) - } - - fn write_bin(&self, key: &Cid, value: &[u8]) -> anyhow::Result<()> { - IndicesStore::write_bin(self.writer(), key, value) - } - - fn exists(&self, key: &Cid) -> anyhow::Result { - IndicesStore::exists(self.writer(), key) - } -} - impl super::super::HeaviestTipsetKeyProvider for ManyCar { fn heaviest_tipset_key(&self) -> anyhow::Result { match SettingsStoreExt::read_obj::(self, crate::db::setting_keys::HEAD_KEY)? { diff --git a/src/db/memory.rs b/src/db/memory.rs index 597479a2e4be..6050fbc1b49a 100644 --- a/src/db/memory.rs +++ b/src/db/memory.rs @@ -3,7 +3,7 @@ use super::{EthMappingsStore, SettingsStore, SettingsStoreExt}; use crate::blocks::TipsetKey; -use crate::db::{IndicesStore, PersistentStore}; +use crate::db::PersistentStore; use crate::libp2p_bitswap::{BitswapStoreRead, BitswapStoreReadWrite}; use crate::rpc::eth::types::EthHash; use crate::utils::db::car_stream::CarBlock; @@ -21,7 +21,6 @@ pub struct MemoryDB { blockchain_persistent_db: RwLock>>, settings_db: RwLock>>, pub eth_mappings_db: RwLock>>, - pub indices_db: RwLock>>, } impl MemoryDB { @@ -110,23 +109,6 @@ impl EthMappingsStore for MemoryDB { } } -impl IndicesStore for MemoryDB { - fn read_bin(&self, key: &Cid) -> anyhow::Result>> { - Ok(self.indices_db.read().get(key).cloned()) - } - - fn write_bin(&self, key: &Cid, value: &[u8]) -> anyhow::Result<()> { - self.indices_db - .write() - .insert(key.to_owned(), value.to_vec()); - Ok(()) - } - - fn exists(&self, key: &Cid) -> anyhow::Result { - Ok(self.indices_db.read().contains_key(key)) - } -} - impl Blockstore for MemoryDB { fn get(&self, k: &Cid) -> anyhow::Result>> { Ok(self.blockchain_db.read().get(k).cloned().or(self diff --git a/src/db/mod.rs b/src/db/mod.rs index fdf772667b28..48cd6d9d4ff8 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -193,47 +193,6 @@ impl EthMappingsStoreExt for T { } } -pub trait IndicesStore { - fn read_bin(&self, key: &Cid) -> anyhow::Result>>; - - fn write_bin(&self, key: &Cid, value: &[u8]) -> anyhow::Result<()>; - - #[allow(dead_code)] - fn exists(&self, key: &Cid) -> anyhow::Result; -} - -impl IndicesStore for Arc { - fn read_bin(&self, key: &Cid) -> anyhow::Result>> { - IndicesStore::read_bin(self.as_ref(), key) - } - - fn write_bin(&self, key: &Cid, value: &[u8]) -> anyhow::Result<()> { - IndicesStore::write_bin(self.as_ref(), key, value) - } - - fn exists(&self, key: &Cid) -> anyhow::Result { - IndicesStore::exists(self.as_ref(), key) - } -} - -pub trait IndicesStoreExt { - fn read_obj(&self, key: &Cid) -> anyhow::Result>; - fn write_obj(&self, key: &Cid, value: &V) -> anyhow::Result<()>; -} - -impl IndicesStoreExt for T { - fn read_obj(&self, key: &Cid) -> anyhow::Result> { - match self.read_bin(key)? { - Some(bytes) => Ok(Some(fvm_ipld_encoding::from_slice(&bytes)?)), - None => Ok(None), - } - } - - fn write_obj(&self, key: &Cid, value: &V) -> anyhow::Result<()> { - self.write_bin(key, &fvm_ipld_encoding::to_vec(value)?) - } -} - /// Traits for collecting DB stats pub trait DBStatistics { fn get_statistics(&self) -> Option { diff --git a/src/db/parity_db.rs b/src/db/parity_db.rs index 6a7a6b16abea..f5d9deb5bdbb 100644 --- a/src/db/parity_db.rs +++ b/src/db/parity_db.rs @@ -1,7 +1,7 @@ // Copyright 2019-2026 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use super::{EthMappingsStore, IndicesStore, PersistentStore, SettingsStore}; +use super::{EthMappingsStore, PersistentStore, SettingsStore}; use crate::blocks::TipsetKey; use crate::db::{DBStatistics, parity_db_config::ParityDbConfig}; use crate::libp2p_bitswap::{BitswapStoreRead, BitswapStoreReadWrite}; @@ -39,8 +39,6 @@ pub enum DbColumn { /// Anything stored in this column can be considered permanent, unless manually /// deleted. PersistentGraph, - /// Column for storing indexed values. - Indices, } impl DbColumn { @@ -77,12 +75,6 @@ impl DbColumn { compression, ..Default::default() }, - DbColumn::Indices => parity_db::ColumnOptions { - preimage: false, - btree_index: false, - compression, - ..Default::default() - }, } }) .collect() @@ -229,23 +221,6 @@ impl EthMappingsStore for ParityDb { } } -impl IndicesStore for ParityDb { - fn read_bin(&self, key: &Cid) -> anyhow::Result>> { - self.read_from_column(key.to_bytes(), DbColumn::Indices) - } - - fn write_bin(&self, key: &Cid, value: &[u8]) -> anyhow::Result<()> { - self.write_to_column(key.to_bytes(), value, DbColumn::Indices) - } - - fn exists(&self, key: &Cid) -> anyhow::Result { - self.db - .get_size(DbColumn::Indices as u8, &key.to_bytes()) - .map(|size| size.is_some()) - .context("error checking if key exists") - } -} - fn has_subscribers(tx: &tokio::sync::broadcast::Sender) -> bool { tx.closed().now_or_never().is_none() } @@ -463,7 +438,6 @@ mod test { DbColumn::Settings => panic!("invalid column for IPLD data"), DbColumn::EthMappings => panic!("invalid column for IPLD data"), DbColumn::PersistentGraph => panic!("invalid column for GC enabled IPLD data"), - DbColumn::Indices => panic!("invalid indices column for IPLD data"), }; let actual = db.read_from_column(cid.to_bytes(), other_column).unwrap(); assert!(actual.is_none()); diff --git a/src/libp2p/chain_exchange/provider.rs b/src/libp2p/chain_exchange/provider.rs index 371b44a9005c..e7d7d902346e 100644 --- a/src/libp2p/chain_exchange/provider.rs +++ b/src/libp2p/chain_exchange/provider.rs @@ -176,7 +176,6 @@ mod tests { let response = make_chain_exchange_response( &ChainStore::new( - db.clone(), db.clone(), db.clone(), db, diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index 494c5f584895..eba3be35365f 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -1649,7 +1649,6 @@ mod tests { db, Arc::new(MemoryDB::default()), Arc::new(MemoryDB::default()), - Arc::new(MemoryDB::default()), Arc::new(ChainConfig::calibnet()), genesis_block_header, ) diff --git a/src/rpc/methods/eth/filter/mod.rs b/src/rpc/methods/eth/filter/mod.rs index 5d2355f09fba..6719fa264feb 100644 --- a/src/rpc/methods/eth/filter/mod.rs +++ b/src/rpc/methods/eth/filter/mod.rs @@ -280,8 +280,7 @@ impl EthEventHandler { let messages = ctx.chain_store().messages_for_tipset(tipset)?; - let StateEvents { events, .. } = - ctx.state_manager.tipset_state_events(tipset, None).await?; + let StateEvents { events, .. } = ctx.state_manager.tipset_state_events(tipset).await?; ensure!( messages.len() == events.len(), diff --git a/src/rpc/methods/sync.rs b/src/rpc/methods/sync.rs index 0e04d0b2f7f2..7e738e5dc84a 100644 --- a/src/rpc/methods/sync.rs +++ b/src/rpc/methods/sync.rs @@ -187,15 +187,7 @@ mod tests { }); let cs_arc = Arc::new( - ChainStore::new( - db.clone(), - db.clone(), - db.clone(), - db, - chain_config, - genesis_header, - ) - .unwrap(), + ChainStore::new(db.clone(), db.clone(), db, chain_config, genesis_header).unwrap(), ); let state_manager = Arc::new(StateManager::new(cs_arc.clone()).unwrap()); diff --git a/src/state_manager/mod.rs b/src/state_manager/mod.rs index a26132cbe8b2..0030c68f401f 100644 --- a/src/state_manager/mod.rs +++ b/src/state_manager/mod.rs @@ -491,15 +491,10 @@ where let state_output = self .compute_tipset_state(tipset.clone(), NO_CALLBACK, VMTrace::NotTraced) .await?; - for events_root in state_output.events_roots.iter().flatten() { - trace!("Indexing events root @{}: {}", tipset.epoch(), events_root); - self.chain_store().put_index(events_root, key)?; - } self.update_cache_with_state_output(key, &state_output); let ts_state = state_output.into(); - trace!("Completed tipset state calculation {:?}", tipset.cids()); Ok(ts_state) }) @@ -554,7 +549,6 @@ where pub async fn tipset_state_events( self: &Arc, tipset: &Tipset, - _events_root: Option<&Cid>, ) -> anyhow::Result { let key = tipset.key(); let ts = tipset.clone(); diff --git a/src/state_manager/tests.rs b/src/state_manager/tests.rs index 5061f7665c9e..fb531fca2b08 100644 --- a/src/state_manager/tests.rs +++ b/src/state_manager/tests.rs @@ -62,7 +62,6 @@ fn setup_chain_with_tipsets() -> TestChainSetup { db.clone(), db.clone(), db.clone(), - db.clone(), chain_config.clone(), genesis_header.clone().into(), ) diff --git a/src/state_manager/utils.rs b/src/state_manager/utils.rs index 384f92c6e00c..0af6e2f17465 100644 --- a/src/state_manager/utils.rs +++ b/src/state_manager/utils.rs @@ -250,7 +250,6 @@ pub mod state_compute { db.clone(), db.clone(), db.clone(), - db.clone(), chain_config, genesis_header, )?); diff --git a/src/tool/offline_server/server.rs b/src/tool/offline_server/server.rs index 05e23c5ed1d6..47f5ae30fb12 100644 --- a/src/tool/offline_server/server.rs +++ b/src/tool/offline_server/server.rs @@ -94,7 +94,6 @@ pub async fn start_offline_server( db.clone(), db.clone(), db.clone(), - db.clone(), chain_config, genesis_header.clone(), )?); diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index 7460ae909972..9fdf04507358 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -2653,7 +2653,6 @@ async fn revalidate_chain(db: Arc, n_ts_to_validate: usize) -> anyhow:: db.clone(), db.clone(), db.clone(), - db.clone(), chain_config, genesis_header.clone(), )?); diff --git a/src/tool/subcommands/api_cmd/generate_test_snapshot.rs b/src/tool/subcommands/api_cmd/generate_test_snapshot.rs index bd32c0a13400..8425627f8384 100644 --- a/src/tool/subcommands/api_cmd/generate_test_snapshot.rs +++ b/src/tool/subcommands/api_cmd/generate_test_snapshot.rs @@ -10,8 +10,8 @@ use crate::{ chain_sync::network_context::SyncNetworkContext, daemon::db_util::load_all_forest_cars, db::{ - CAR_DB_DIR_NAME, EthMappingsStore, HeaviestTipsetKeyProvider, IndicesStore, MemoryDB, - SettingsStore, SettingsStoreExt, db_engine::open_db, parity_db::ParityDb, + CAR_DB_DIR_NAME, EthMappingsStore, HeaviestTipsetKeyProvider, MemoryDB, SettingsStore, + SettingsStoreExt, db_engine::open_db, parity_db::ParityDb, }, genesis::read_genesis_header, libp2p::{NetworkMessage, PeerManager}, @@ -89,13 +89,6 @@ pub(super) fn build_index(db: Arc>>) -> O .get_or_insert_with(ahash::HashMap::default) .insert(k.to_string(), Payload(v.clone())); } - let reader = db.tracker.indices_db.read(); - for (k, v) in reader.iter() { - index - .indices - .get_or_insert_with(ahash::HashMap::default) - .insert(k.to_string(), Payload(v.clone())); - } if index == Index::default() { None } else { @@ -121,7 +114,6 @@ async fn ctx( db.clone(), db.clone(), db.clone(), - db, chain_config, genesis_header, ) @@ -305,21 +297,3 @@ impl EthMappingsStore for ReadOpsTrackingStore { self.inner.delete(keys) } } - -impl IndicesStore for ReadOpsTrackingStore { - fn read_bin(&self, key: &Cid) -> anyhow::Result>> { - let result = self.inner.read_bin(key)?; - if let Some(v) = &result { - IndicesStore::write_bin(&self.tracker, key, v.as_slice())?; - } - self.inner.read_bin(key) - } - - fn write_bin(&self, key: &Cid, value: &[u8]) -> anyhow::Result<()> { - self.inner.write_bin(key, value) - } - - fn exists(&self, key: &Cid) -> anyhow::Result { - self.inner.exists(key) - } -} diff --git a/src/tool/subcommands/api_cmd/test_snapshot.rs b/src/tool/subcommands/api_cmd/test_snapshot.rs index 2088a1adc530..80265660b6d8 100644 --- a/src/tool/subcommands/api_cmd/test_snapshot.rs +++ b/src/tool/subcommands/api_cmd/test_snapshot.rs @@ -22,7 +22,6 @@ use crate::{ shim::address::{CurrentNetwork, Network}, state_manager::StateManager, }; -use cid::Cid; use openrpc_types::ParamStructure; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; @@ -53,20 +52,12 @@ pub struct RpcTestSnapshot { } fn backfill_eth_mappings(db: &MemoryDB, index: Option) -> anyhow::Result<()> { - if let Some(index) = index { - if let Some(mut guard) = db.eth_mappings_db.try_write() - && let Some(eth_mappings) = index.eth_mappings - { - for (k, v) in eth_mappings.iter() { - guard.insert(EthHash::from_str(k)?, v.0.clone()); - } - } - if let Some(mut guard) = db.indices_db.try_write() - && let Some(indices) = index.indices - { - for (k, v) in indices.iter() { - guard.insert(Cid::from_str(k)?, v.0.clone()); - } + if let Some(index) = index + && let Some(mut guard) = db.eth_mappings_db.try_write() + && let Some(eth_mappings) = index.eth_mappings + { + for (k, v) in eth_mappings.iter() { + guard.insert(EthHash::from_str(k)?, v.0.clone()); } } Ok(()) @@ -140,17 +131,13 @@ async fn ctx( let (tipset_send, _) = flume::bounded(5); let genesis_header = read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db).await?; - let chain_store = Arc::new( - ChainStore::new( - db.clone(), - db.clone(), - db.clone(), - db, - chain_config, - genesis_header.clone(), - ) - .unwrap(), - ); + let chain_store = Arc::new(ChainStore::new( + db.clone(), + db.clone(), + db, + chain_config, + genesis_header.clone(), + )?); let state_manager = Arc::new(StateManager::new(chain_store.clone()).unwrap()); let message_pool = MessagePool::new( MpoolRpcProvider::new(chain_store.publisher().clone(), state_manager.clone()), diff --git a/src/tool/subcommands/index_cmd.rs b/src/tool/subcommands/index_cmd.rs index 78eca7fd9775..4f01fce73e2a 100644 --- a/src/tool/subcommands/index_cmd.rs +++ b/src/tool/subcommands/index_cmd.rs @@ -84,7 +84,6 @@ impl IndexCommands { db.clone(), db.clone(), db.clone(), - db.writer().clone(), chain_config, genesis_header.clone(), )?); diff --git a/src/tool/subcommands/state_compute_cmd.rs b/src/tool/subcommands/state_compute_cmd.rs index 86efa97a10c7..3123e25e3243 100644 --- a/src/tool/subcommands/state_compute_cmd.rs +++ b/src/tool/subcommands/state_compute_cmd.rs @@ -74,7 +74,6 @@ impl ComputeCommand { db.clone(), db.clone(), db.clone(), - db.clone(), chain_config, genesis_header, )?); @@ -136,7 +135,6 @@ impl ReplayComputeCommand { db.clone(), db.clone(), db.clone(), - db.clone(), chain_config, genesis_header, )?); From b7328eb4f5d9b701f9c9deddb44a70efebc0156b Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Wed, 10 Dec 2025 21:35:33 +0530 Subject: [PATCH 06/13] address comments --- src/shim/executor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shim/executor.rs b/src/shim/executor.rs index 4c26617bef18..1e1ac6ea79b6 100644 --- a/src/shim/executor.rs +++ b/src/shim/executor.rs @@ -397,14 +397,14 @@ impl StampedEvent { // Try StampedEvent_v4 first (StampedEvent_v4 and StampedEvent_v3 are identical, use v4 here) if let Ok(amt) = Amt::::load(events_root, db) { - amt.for_each(|_, event| { + amt.for_each_cacheless(|_, event| { events.push(StampedEvent::V4(event.clone())); Ok(()) })?; } else { // Fallback to StampedEvent_v3 let amt = Amt::::load(events_root, db)?; - amt.for_each(|_, event| { + amt.for_each_cacheless(|_, event| { events.push(StampedEvent::V3(event.clone())); Ok(()) })?; From d3f0c50222ffeb58151ae40a0d3b1fa979c4c463 Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Mon, 12 Jan 2026 19:07:04 +0530 Subject: [PATCH 07/13] add DB migration and bump the forest version --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/db/migration/migration_map.rs | 3 + src/db/migration/mod.rs | 1 + src/db/migration/v0_31_0.rs | 336 ++++++++++++++++++++++++++++++ 5 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 src/db/migration/v0_31_0.rs diff --git a/Cargo.lock b/Cargo.lock index cbf674640c5b..1a5594381090 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3119,7 +3119,7 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "forest-filecoin" -version = "0.30.5" +version = "0.31.0" dependencies = [ "ahash", "anes 0.2.1", diff --git a/Cargo.toml b/Cargo.toml index 671cca445e0e..d1e3019d95f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "forest-filecoin" -version = "0.30.5" +version = "0.31.0" authors = ["ChainSafe Systems "] repository = "https://github.com/ChainSafe/forest" edition = "2024" diff --git a/src/db/migration/migration_map.rs b/src/db/migration/migration_map.rs index 1b7f5367245f..23966cd1f527 100644 --- a/src/db/migration/migration_map.rs +++ b/src/db/migration/migration_map.rs @@ -10,6 +10,7 @@ use std::{ use crate::Config; use crate::db::migration::v0_22_1::Migration0_22_0_0_22_1; use crate::db::migration::v0_26_0::Migration0_25_3_0_26_0; +use crate::db::migration::v0_31_0::Migration0_26_0_0_31_0; use anyhow::Context as _; use anyhow::bail; use itertools::Itertools; @@ -155,6 +156,8 @@ pub(super) static MIGRATIONS: LazyLock = LazyLock::new(|| { create_migrations!( "0.22.0" -> "0.22.1" @ Migration0_22_0_0_22_1, "0.25.3" -> "0.26.0" @ Migration0_25_3_0_26_0, + // v0.30.0 is the oldest supported version, and it uses the same schema as v0.26.0. + "0.30.0" -> "0.31.0" @ Migration0_26_0_0_31_0, ); /// Creates a migration chain from `start` to `goal`. The chain is chosen to be the shortest diff --git a/src/db/migration/mod.rs b/src/db/migration/mod.rs index d6c80386d8a6..522da4b1b946 100644 --- a/src/db/migration/mod.rs +++ b/src/db/migration/mod.rs @@ -5,6 +5,7 @@ mod db_migration; mod migration_map; mod v0_22_1; mod v0_26_0; +mod v0_31_0; mod void_migration; pub use db_migration::DbMigration; diff --git a/src/db/migration/v0_31_0.rs b/src/db/migration/v0_31_0.rs new file mode 100644 index 000000000000..65ce0cea90c1 --- /dev/null +++ b/src/db/migration/v0_31_0.rs @@ -0,0 +1,336 @@ +// Copyright 2019-2026 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +//! Migration logic for databases with the v0.26.0 schema (including v0.30.0) to v0.31.0. +//! The `Indices` column has been removed as events are now stored directly in the blockstore +//! as AMTs (Array Mapped Tries) rather than being indexed separately. +//! +//! This migration is used for: +//! - `0.30.0` → `0.31.0` (the oldest supported version) + +use crate::Config; +use crate::blocks::TipsetKey; +use crate::db::CAR_DB_DIR_NAME; +use crate::db::db_engine::Db; +use crate::db::migration::migration_map::MigrationOperationExt as _; +use crate::db::migration::v0_31_0::paritydb_0_26_0::{DbColumn, ParityDb}; +use crate::rpc::eth::types::EthHash; +use crate::utils::multihash::prelude::*; +use anyhow::Context; +use cid::Cid; +use fs_extra::dir::CopyOptions; +use fvm_ipld_encoding::DAG_CBOR; +use semver::Version; +use std::path::{Path, PathBuf}; +use strum::IntoEnumIterator; +use tracing::info; + +use super::migration_map::MigrationOperation; + +pub(super) struct Migration0_26_0_0_31_0 { + from: Version, + to: Version, +} + +/// Migrates the database from version 0.26.0 to 0.31.0 +impl MigrationOperation for Migration0_26_0_0_31_0 { + fn new(from: Version, to: Version) -> Self + where + Self: Sized, + { + Self { from, to } + } + + fn from(&self) -> &Version { + &self.from + } + + fn to(&self) -> &Version { + &self.to + } + + fn migrate_core(&self, chain_data_path: &Path, _: &Config) -> anyhow::Result { + let old_db = self.old_db_path(chain_data_path); + let temp_db = self.temporary_db_path(chain_data_path); + + let old_car_db_path = old_db.join(CAR_DB_DIR_NAME); + let temp_car_db_path = temp_db.join(CAR_DB_DIR_NAME); + + // Make sure `car_db` dir exists as it might not be the case when migrating + // from older versions. + if old_car_db_path.is_dir() { + info!( + "Copying snapshot from {} to {}", + old_db.display(), + temp_db.display() + ); + + fs_extra::copy_items( + &[old_car_db_path.as_path()], + temp_car_db_path, + &CopyOptions::default().copy_inside(true), + )?; + } + + let db = ParityDb::open(old_db)?; + + // Open the new database to migrate data from the old one. + // The new database does NOT have the Indices column. + let new_db = paritydb_0_31_0::ParityDb::open(&temp_db)?; + + for col in DbColumn::iter() { + // Skip the Indices column first - it's being removed in this migration + if col == DbColumn::Indices { + info!("Skipping column {} (removed in this version)", col); + continue; + } + + info!("Migrating column {}", col); + let mut res = anyhow::Ok(()); + match col { + DbColumn::GraphDagCborBlake2b256 | DbColumn::PersistentGraph => { + db.db.iter_column_while(col as u8, |val| { + let hash = MultihashCode::Blake2b256.digest(&val.value); + let cid = Cid::new_v1(DAG_CBOR, hash); + res = new_db + .db + .commit_changes([Db::set_operation( + col as u8, + cid.to_bytes(), + val.value, + )]) + .context("failed to commit"); + + if res.is_err() { + return false; + } + + true + })?; + res?; + } + DbColumn::EthMappings => { + db.db.iter_column_while(col as u8, |val| { + let tsk: Result = + fvm_ipld_encoding::from_slice(&val.value); + if tsk.is_err() { + res = Err(tsk.context("serde error").unwrap_err()); + return false; + } + let cid = tsk.unwrap().cid(); + + if cid.is_err() { + res = Err(cid.context("serde error").unwrap_err()); + return false; + } + + let hash: EthHash = cid.unwrap().into(); + res = new_db + .db + .commit_changes([Db::set_operation( + col as u8, + hash.0.as_bytes().to_vec(), + val.value, + )]) + .context("failed to commit"); + + if res.is_err() { + return false; + } + + true + })?; + res?; + } + _ => { + let mut iter = db.db.iter(col as u8)?; + while let Some((key, value)) = iter.next()? { + new_db + .db + .commit_changes([Db::set_operation(col as u8, key, value)]) + .context("failed to commit")?; + } + } + } + } + + drop(new_db); + + Ok(temp_db) + } +} + +/// Database settings from Forest `v0.26.0` +mod paritydb_0_26_0 { + use parity_db::{CompressionType, Db, Options}; + use std::path::PathBuf; + use strum::{Display, EnumIter, IntoEnumIterator}; + + #[derive(Copy, Clone, Debug, PartialEq, EnumIter, Display)] + #[repr(u8)] + pub(super) enum DbColumn { + GraphDagCborBlake2b256, + GraphFull, + Settings, + EthMappings, + PersistentGraph, + Indices, + } + + impl DbColumn { + fn create_column_options(compression: CompressionType) -> Vec { + DbColumn::iter() + .map(|col| { + match col { + DbColumn::GraphDagCborBlake2b256 | DbColumn::PersistentGraph => { + parity_db::ColumnOptions { + preimage: true, + compression, + ..Default::default() + } + } + DbColumn::GraphFull => parity_db::ColumnOptions { + preimage: true, + // This is needed for key retrieval. + btree_index: true, + compression, + ..Default::default() + }, + DbColumn::Settings => { + parity_db::ColumnOptions { + // explicitly disable preimage for settings column + // othewise we are not able to overwrite entries + preimage: false, + // This is needed for key retrieval. + btree_index: true, + compression, + ..Default::default() + } + } + DbColumn::EthMappings => parity_db::ColumnOptions { + preimage: false, + btree_index: false, + compression, + ..Default::default() + }, + DbColumn::Indices => parity_db::ColumnOptions { + preimage: false, + btree_index: false, + compression, + ..Default::default() + }, + } + }) + .collect() + } + } + + pub(super) struct ParityDb { + pub db: parity_db::Db, + } + + impl ParityDb { + pub(super) fn to_options(path: PathBuf) -> Options { + Options { + path, + sync_wal: true, + sync_data: true, + stats: false, + salt: None, + columns: DbColumn::create_column_options(CompressionType::Lz4), + compression_threshold: [(0, 128)].into_iter().collect(), + } + } + + pub(super) fn open(path: impl Into) -> anyhow::Result { + let opts = Self::to_options(path.into()); + Ok(Self { + db: Db::open_or_create(&opts)?, + }) + } + } +} + +/// Database settings from Forest `v0.31.0` (Indices column removed) +mod paritydb_0_31_0 { + use parity_db::{CompressionType, Db, Options}; + use std::path::PathBuf; + use strum::{Display, EnumIter, IntoEnumIterator}; + + #[derive(Copy, Clone, Debug, PartialEq, EnumIter, Display)] + #[repr(u8)] + pub(super) enum DbColumn { + GraphDagCborBlake2b256, + GraphFull, + Settings, + EthMappings, + PersistentGraph, + } + + impl DbColumn { + fn create_column_options(compression: CompressionType) -> Vec { + DbColumn::iter() + .map(|col| { + match col { + DbColumn::GraphDagCborBlake2b256 | DbColumn::PersistentGraph => { + parity_db::ColumnOptions { + preimage: true, + compression, + ..Default::default() + } + } + DbColumn::GraphFull => parity_db::ColumnOptions { + preimage: true, + // This is needed for key retrieval. + btree_index: true, + compression, + ..Default::default() + }, + DbColumn::Settings => { + parity_db::ColumnOptions { + // explicitly disable preimage for settings column + // othewise we are not able to overwrite entries + preimage: false, + // This is needed for key retrieval. + btree_index: true, + compression, + ..Default::default() + } + } + DbColumn::EthMappings => parity_db::ColumnOptions { + preimage: false, + btree_index: false, + compression, + ..Default::default() + }, + } + }) + .collect() + } + } + + pub(super) struct ParityDb { + pub db: parity_db::Db, + } + + impl ParityDb { + pub(super) fn to_options(path: PathBuf) -> Options { + Options { + path, + sync_wal: true, + sync_data: true, + stats: false, + salt: None, + columns: DbColumn::create_column_options(CompressionType::Lz4), + compression_threshold: [(0, 128)].into_iter().collect(), + } + } + + pub(super) fn open(path: impl Into) -> anyhow::Result { + let opts = Self::to_options(path.into()); + Ok(Self { + db: Db::open_or_create(&opts)?, + }) + } + } +} From 6fb70ec8fd8b3cd29dbf83e0f1d39ce820622a4f Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Mon, 12 Jan 2026 20:32:00 +0530 Subject: [PATCH 08/13] fix linter issue --- src/db/migration/v0_31_0.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/db/migration/v0_31_0.rs b/src/db/migration/v0_31_0.rs index 65ce0cea90c1..d59b7ed34d1c 100644 --- a/src/db/migration/v0_31_0.rs +++ b/src/db/migration/v0_31_0.rs @@ -2,11 +2,10 @@ // SPDX-License-Identifier: Apache-2.0, MIT //! Migration logic for databases with the v0.26.0 schema (including v0.30.0) to v0.31.0. -//! The `Indices` column has been removed as events are now stored directly in the blockstore -//! as AMTs (Array Mapped Tries) rather than being indexed separately. +//! The `Indices` column has been removed as events are now stored as `AMTs` in the blockstore. //! //! This migration is used for: -//! - `0.30.0` → `0.31.0` (the oldest supported version) +//! - `0.30.0` to `0.31.0` (the oldest supported version) use crate::Config; use crate::blocks::TipsetKey; From 49f17a64f01c40328d237844caeeef653e966a18 Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Tue, 13 Jan 2026 20:04:42 +0530 Subject: [PATCH 09/13] add change log entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e94e2ef8abdf..2f57025d22de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,12 +44,14 @@ ### Changed - [#6368](https://github.com/ChainSafe/forest/pull/6368): Migrated build and development tooling from Makefile to `mise`. Contributors should install `mise` and use `mise run` commands instead of `make` commands. +- [#6286](https://github.com/ChainSafe/forest/pull/6286) Serve `Filecoin.ChainGetEvents` API directly from the blockstore instead of computing. ### Removed ### Fixed - [#6327](https://github.com/ChainSafe/forest/pull/6327) Fixed: Forest returns 404 for all invalid api paths. +- [#6286](https://github.com/ChainSafe/forest/pull/6286) Fixed: `Filecoin.ChainGetEvents` API returns correct events. ## Forest v0.30.5 "Dulce de Leche" From 10e21f8b5f2759ee0b664d17f589b591be4d4d57 Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Tue, 13 Jan 2026 20:07:01 +0530 Subject: [PATCH 10/13] remove the ChainGetEvents API from the filter list --- scripts/tests/api_compare/filter-list | 4 ---- scripts/tests/api_compare/filter-list-offline | 3 --- 2 files changed, 7 deletions(-) diff --git a/scripts/tests/api_compare/filter-list b/scripts/tests/api_compare/filter-list index c144a2275692..0656b736d920 100644 --- a/scripts/tests/api_compare/filter-list +++ b/scripts/tests/api_compare/filter-list @@ -1,6 +1,2 @@ # This list contains potentially broken methods (or tests) that are ignored. # They should be considered bugged, and not used until the root cause is resolved. - -# Ignoring ChainGetEvents for now, as we are not sure if we should get duplicated events or not. -# TODO(forest): https://github.com/ChainSafe/forest/issues/6271. -!Filecoin.ChainGetEvents diff --git a/scripts/tests/api_compare/filter-list-offline b/scripts/tests/api_compare/filter-list-offline index 7f1a2b6d3bf8..50f62a6f7fe1 100644 --- a/scripts/tests/api_compare/filter-list-offline +++ b/scripts/tests/api_compare/filter-list-offline @@ -27,6 +27,3 @@ !Filecoin.EthGetBlockByNumber !eth_getBlockByNumber !Filecoin.ChainSetHead -# Ignoring ChainGetEvents for now, as we are not sure if we should get duplicated events or not. -# TODO(forest): https://github.com/ChainSafe/forest/issues/6271. -!Filecoin.ChainGetEvents From 488e5c3939bcb0bf60159866fb9c4c591558df8a Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Tue, 13 Jan 2026 20:12:13 +0530 Subject: [PATCH 11/13] add comment for the API --- src/rpc/methods/chain.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs index eba3be35365f..113ecc5a8208 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs @@ -239,6 +239,8 @@ impl RpcMethod<1> for ChainGetMessage { } } +/// Returns the events stored under the given event AMT root CID. +/// Errors if the root CID cannot be found in the blockstore. pub enum ChainGetEvents {} impl RpcMethod<1> for ChainGetEvents { const NAME: &'static str = "Filecoin.ChainGetEvents"; From eec093f8aad47d047c0eca4121ded1280cd5b5bd Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Mon, 19 Jan 2026 23:34:27 +0530 Subject: [PATCH 12/13] address migration comments --- src/db/migration/migration_map.rs | 5 +- src/db/migration/v0_31_0.rs | 258 ++++-------------------------- 2 files changed, 32 insertions(+), 231 deletions(-) diff --git a/src/db/migration/migration_map.rs b/src/db/migration/migration_map.rs index 23966cd1f527..7ffbaffb9a8a 100644 --- a/src/db/migration/migration_map.rs +++ b/src/db/migration/migration_map.rs @@ -10,7 +10,7 @@ use std::{ use crate::Config; use crate::db::migration::v0_22_1::Migration0_22_0_0_22_1; use crate::db::migration::v0_26_0::Migration0_25_3_0_26_0; -use crate::db::migration::v0_31_0::Migration0_26_0_0_31_0; +use crate::db::migration::v0_31_0::Migration0_30_5_0_31_0; use anyhow::Context as _; use anyhow::bail; use itertools::Itertools; @@ -156,8 +156,7 @@ pub(super) static MIGRATIONS: LazyLock = LazyLock::new(|| { create_migrations!( "0.22.0" -> "0.22.1" @ Migration0_22_0_0_22_1, "0.25.3" -> "0.26.0" @ Migration0_25_3_0_26_0, - // v0.30.0 is the oldest supported version, and it uses the same schema as v0.26.0. - "0.30.0" -> "0.31.0" @ Migration0_26_0_0_31_0, + "0.30.5" -> "0.31.0" @ Migration0_30_5_0_31_0, ); /// Creates a migration chain from `start` to `goal`. The chain is chosen to be the shortest diff --git a/src/db/migration/v0_31_0.rs b/src/db/migration/v0_31_0.rs index d59b7ed34d1c..e41d4436df1c 100644 --- a/src/db/migration/v0_31_0.rs +++ b/src/db/migration/v0_31_0.rs @@ -1,38 +1,24 @@ // Copyright 2019-2026 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -//! Migration logic for databases with the v0.26.0 schema (including v0.30.0) to v0.31.0. +//! Migration logic for databases with the v0.30.5 schema to v0.31.0. //! The `Indices` column has been removed as events are now stored as `AMTs` in the blockstore. -//! -//! This migration is used for: -//! - `0.30.0` to `0.31.0` (the oldest supported version) +use super::migration_map::MigrationOperation; use crate::Config; -use crate::blocks::TipsetKey; -use crate::db::CAR_DB_DIR_NAME; -use crate::db::db_engine::Db; use crate::db::migration::migration_map::MigrationOperationExt as _; -use crate::db::migration::v0_31_0::paritydb_0_26_0::{DbColumn, ParityDb}; -use crate::rpc::eth::types::EthHash; -use crate::utils::multihash::prelude::*; use anyhow::Context; -use cid::Cid; -use fs_extra::dir::CopyOptions; -use fvm_ipld_encoding::DAG_CBOR; use semver::Version; use std::path::{Path, PathBuf}; -use strum::IntoEnumIterator; use tracing::info; -use super::migration_map::MigrationOperation; - -pub(super) struct Migration0_26_0_0_31_0 { +pub(super) struct Migration0_30_5_0_31_0 { from: Version, to: Version, } -/// Migrates the database from version 0.26.0 to 0.31.0 -impl MigrationOperation for Migration0_26_0_0_31_0 { +/// Migrates the database from version 0.30.5 to 0.31.0 +impl MigrationOperation for Migration0_30_5_0_31_0 { fn new(from: Version, to: Version) -> Self where Self: Sized, @@ -52,116 +38,29 @@ impl MigrationOperation for Migration0_26_0_0_31_0 { let old_db = self.old_db_path(chain_data_path); let temp_db = self.temporary_db_path(chain_data_path); - let old_car_db_path = old_db.join(CAR_DB_DIR_NAME); - let temp_car_db_path = temp_db.join(CAR_DB_DIR_NAME); - - // Make sure `car_db` dir exists as it might not be the case when migrating - // from older versions. - if old_car_db_path.is_dir() { - info!( - "Copying snapshot from {} to {}", - old_db.display(), - temp_db.display() - ); - - fs_extra::copy_items( - &[old_car_db_path.as_path()], - temp_car_db_path, - &CopyOptions::default().copy_inside(true), - )?; - } - - let db = ParityDb::open(old_db)?; - - // Open the new database to migrate data from the old one. - // The new database does NOT have the Indices column. - let new_db = paritydb_0_31_0::ParityDb::open(&temp_db)?; - - for col in DbColumn::iter() { - // Skip the Indices column first - it's being removed in this migration - if col == DbColumn::Indices { - info!("Skipping column {} (removed in this version)", col); - continue; - } - - info!("Migrating column {}", col); - let mut res = anyhow::Ok(()); - match col { - DbColumn::GraphDagCborBlake2b256 | DbColumn::PersistentGraph => { - db.db.iter_column_while(col as u8, |val| { - let hash = MultihashCode::Blake2b256.digest(&val.value); - let cid = Cid::new_v1(DAG_CBOR, hash); - res = new_db - .db - .commit_changes([Db::set_operation( - col as u8, - cid.to_bytes(), - val.value, - )]) - .context("failed to commit"); - - if res.is_err() { - return false; - } - - true - })?; - res?; - } - DbColumn::EthMappings => { - db.db.iter_column_while(col as u8, |val| { - let tsk: Result = - fvm_ipld_encoding::from_slice(&val.value); - if tsk.is_err() { - res = Err(tsk.context("serde error").unwrap_err()); - return false; - } - let cid = tsk.unwrap().cid(); + info!( + "Renaming database directory from {} to {}", + old_db.display(), + temp_db.display() + ); + // Rename the database directory to the temporary directory + std::fs::rename(&old_db, &temp_db).context("failed to rename database directory")?; - if cid.is_err() { - res = Err(cid.context("serde error").unwrap_err()); - return false; - } - - let hash: EthHash = cid.unwrap().into(); - res = new_db - .db - .commit_changes([Db::set_operation( - col as u8, - hash.0.as_bytes().to_vec(), - val.value, - )]) - .context("failed to commit"); + // Create a placeholder so the delete step succeeds + std::fs::create_dir_all(&old_db).context("failed to create placeholder directory")?; - if res.is_err() { - return false; - } - - true - })?; - res?; - } - _ => { - let mut iter = db.db.iter(col as u8)?; - while let Some((key, value)) = iter.next()? { - new_db - .db - .commit_changes([Db::set_operation(col as u8, key, value)]) - .context("failed to commit")?; - } - } - } - } - - drop(new_db); + info!("Dropping last column (Indices) from database"); + let mut opts = paritydb_0_30_5::to_options(temp_db.clone()); + parity_db::Db::drop_last_column(&mut opts).context("failed to drop last column")?; + info!("Migration completed successfully"); Ok(temp_db) } } -/// Database settings from Forest `v0.26.0` -mod paritydb_0_26_0 { - use parity_db::{CompressionType, Db, Options}; +/// Database settings from Forest `v0.30.5` +mod paritydb_0_30_5 { + use parity_db::{CompressionType, Options}; use std::path::PathBuf; use strum::{Display, EnumIter, IntoEnumIterator}; @@ -224,112 +123,15 @@ mod paritydb_0_26_0 { } } - pub(super) struct ParityDb { - pub db: parity_db::Db, - } - - impl ParityDb { - pub(super) fn to_options(path: PathBuf) -> Options { - Options { - path, - sync_wal: true, - sync_data: true, - stats: false, - salt: None, - columns: DbColumn::create_column_options(CompressionType::Lz4), - compression_threshold: [(0, 128)].into_iter().collect(), - } - } - - pub(super) fn open(path: impl Into) -> anyhow::Result { - let opts = Self::to_options(path.into()); - Ok(Self { - db: Db::open_or_create(&opts)?, - }) - } - } -} - -/// Database settings from Forest `v0.31.0` (Indices column removed) -mod paritydb_0_31_0 { - use parity_db::{CompressionType, Db, Options}; - use std::path::PathBuf; - use strum::{Display, EnumIter, IntoEnumIterator}; - - #[derive(Copy, Clone, Debug, PartialEq, EnumIter, Display)] - #[repr(u8)] - pub(super) enum DbColumn { - GraphDagCborBlake2b256, - GraphFull, - Settings, - EthMappings, - PersistentGraph, - } - - impl DbColumn { - fn create_column_options(compression: CompressionType) -> Vec { - DbColumn::iter() - .map(|col| { - match col { - DbColumn::GraphDagCborBlake2b256 | DbColumn::PersistentGraph => { - parity_db::ColumnOptions { - preimage: true, - compression, - ..Default::default() - } - } - DbColumn::GraphFull => parity_db::ColumnOptions { - preimage: true, - // This is needed for key retrieval. - btree_index: true, - compression, - ..Default::default() - }, - DbColumn::Settings => { - parity_db::ColumnOptions { - // explicitly disable preimage for settings column - // othewise we are not able to overwrite entries - preimage: false, - // This is needed for key retrieval. - btree_index: true, - compression, - ..Default::default() - } - } - DbColumn::EthMappings => parity_db::ColumnOptions { - preimage: false, - btree_index: false, - compression, - ..Default::default() - }, - } - }) - .collect() - } - } - - pub(super) struct ParityDb { - pub db: parity_db::Db, - } - - impl ParityDb { - pub(super) fn to_options(path: PathBuf) -> Options { - Options { - path, - sync_wal: true, - sync_data: true, - stats: false, - salt: None, - columns: DbColumn::create_column_options(CompressionType::Lz4), - compression_threshold: [(0, 128)].into_iter().collect(), - } - } - - pub(super) fn open(path: impl Into) -> anyhow::Result { - let opts = Self::to_options(path.into()); - Ok(Self { - db: Db::open_or_create(&opts)?, - }) + pub(super) fn to_options(path: PathBuf) -> Options { + Options { + path, + sync_wal: true, + sync_data: true, + stats: false, + salt: None, + columns: DbColumn::create_column_options(CompressionType::Lz4), + compression_threshold: [(0, 128)].into_iter().collect(), } } } From 93edf96ca57c1ca3963ac4b5c1d13b23136cf14c Mon Sep 17 00:00:00 2001 From: Aryan Tikarya Date: Tue, 20 Jan 2026 16:27:12 +0530 Subject: [PATCH 13/13] address remaining comments --- CHANGELOG.md | 2 +- src/db/migration/v0_31_0.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ccceecf7062..d7c0c982d863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,7 @@ - [#6368](https://github.com/ChainSafe/forest/pull/6368): Migrated build and development tooling from Makefile to `mise`. Contributors should install `mise` and use `mise run` commands instead of `make` commands. -- [#6286](https://github.com/ChainSafe/forest/pull/6286) Serve `Filecoin.ChainGetEvents` API directly from the blockstore instead of computing. +- [#6286](https://github.com/ChainSafe/forest/pull/6286) `Filecoin.ChainGetEvents` now returns an error if the events are not present in the db. ### Removed diff --git a/src/db/migration/v0_31_0.rs b/src/db/migration/v0_31_0.rs index e41d4436df1c..57e87de54c9a 100644 --- a/src/db/migration/v0_31_0.rs +++ b/src/db/migration/v0_31_0.rs @@ -43,7 +43,6 @@ impl MigrationOperation for Migration0_30_5_0_31_0 { old_db.display(), temp_db.display() ); - // Rename the database directory to the temporary directory std::fs::rename(&old_db, &temp_db).context("failed to rename database directory")?; // Create a placeholder so the delete step succeeds