Skip to content
Draft
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
93 changes: 93 additions & 0 deletions src/blocks/checkpoints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2019-2026 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

//! Provides utilities for efficiently locating the genesis block and known checkpoints
//! in the Filecoin blockchain by leveraging a list of precomputed, hash-chained block CIDs.
//! This avoids scanning millions of epochs, significantly speeding up chain traversal.

use crate::{
blocks::{CachingBlockHeader, Tipset},
networks::NetworkChain,
shim::clock::ChainEpoch,
};
use ahash::HashMap;
use anyhow::Context as _;
use cid::Cid;
use fvm_ipld_blockstore::Blockstore;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, serde_as};
use std::sync::{LazyLock, OnceLock};

/// Holds mappings from chain epochs to block CIDs for each network.
#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct KnownBlocks {
#[serde_as(as = "HashMap<_, DisplayFromStr>")]
pub calibnet: HashMap<ChainEpoch, Cid>,
#[serde_as(as = "HashMap<_, DisplayFromStr>")]
pub mainnet: HashMap<ChainEpoch, Cid>,
}

/// Lazily loaded static instance of `KnownBlocks` from YAML.
/// Caches (`OnceLock`) are used to avoid recomputing known tipsets.
pub static KNOWN_BLOCKS: LazyLock<KnownBlocks> = LazyLock::new(|| {
serde_yaml::from_str(include_str!("../../build/known_blocks.yaml")).expect("infallible")
});

/// Returns a cached, ascending-epoch list of known [`Tipset`]s for the given network.
pub fn known_tipsets(
bs: &impl Blockstore,
network: &NetworkChain,
) -> anyhow::Result<&'static Vec<Tipset>> {
static CACHE_CALIBNET: OnceLock<Vec<Tipset>> = OnceLock::new();
static CACHE_MAINNET: OnceLock<Vec<Tipset>> = OnceLock::new();
let (cache, known_blocks) = match network {
NetworkChain::Calibnet => (&CACHE_CALIBNET, &KNOWN_BLOCKS.calibnet),
NetworkChain::Mainnet => (&CACHE_MAINNET, &KNOWN_BLOCKS.mainnet),
_ => anyhow::bail!("unsupported network {network}"),
};
if let Some(v) = cache.get() {
Ok(v)
} else {
let tipsets = known_blocks_to_known_tipsets(bs, known_blocks)?;
_ = cache.set(tipsets);
cache.get().context("infallible")
}
Comment on lines +43 to +56
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Memoize checkpoint lookup failures per blockstore.

This only caches the success path. If one checkpoint header or parent tipset is missing, known_tipsets() keeps retrying the same initialization work on every ChainStore::tipset_by_height() call before falling back. On stores without the full checkpoint set, that turns the optimization into permanent overhead. Consider moving this cache onto ChainStore so success/failure is memoized per blockstore instance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/blocks/checkpoints.rs` around lines 43 - 56, The current cache
(CACHE_CALIBNET / CACHE_MAINNET) only memoizes the success path from
known_blocks_to_known_tipsets and re-runs initialization on every
ChainStore::tipset_by_height call when a blockstore is missing checkpoint data;
change the design to memoize the Result per blockstore instance instead of
global OnceLock: move the cache into ChainStore (e.g., add a field like
known_tipsets_cache: HashMap<NetworkChain, OnceCell<Result<Vec<Tipset>,
anyhow::Error>>> or similar) and store the Ok or Err from
known_blocks_to_known_tipsets so failures are cached too; update callers that
use CACHE_CALIBNET/CACHE_MAINNET to read from ChainStore’s per-instance cache
and return the cached Err immediately to avoid repeated work in
known_tipsets()/ChainStore::tipset_by_height.

}

fn known_blocks_to_known_tipsets(
bs: &impl Blockstore,
blocks: &HashMap<ChainEpoch, Cid>,
) -> anyhow::Result<Vec<Tipset>> {
let mut tipsets: Vec<Tipset> = blocks
.values()
.map(|&b| block_cid_to_required_parent_tipset(bs, b))
.try_collect()?;
tipsets.sort_by_key(|ts| ts.epoch());
Ok(tipsets)
}

fn block_cid_to_parent_tipset(bs: &impl Blockstore, block: Cid) -> anyhow::Result<Option<Tipset>> {
if let Some(block) = CachingBlockHeader::load(bs, block)? {
Tipset::load(bs, &block.parents)
} else {
Ok(None)
}
}

fn block_cid_to_required_parent_tipset(bs: &impl Blockstore, block: Cid) -> anyhow::Result<Tipset> {
block_cid_to_parent_tipset(bs, block)?
.with_context(|| format!("failed to load parent tipset of block {block}"))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_known_blocks() {
assert!(!KNOWN_BLOCKS.calibnet.is_empty());
assert!(!KNOWN_BLOCKS.mainnet.is_empty());
}
}
1 change: 1 addition & 0 deletions src/blocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use thiserror::Error;
mod block;
#[cfg(test)]
mod chain4u;
pub mod checkpoints;
mod election_proof;
mod gossip_block;
mod header;
Expand Down
28 changes: 9 additions & 19 deletions src/blocks/tipset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use crate::{
multihash::MultihashCode,
},
};
use ahash::HashMap;
use anyhow::Context as _;
use cid::Cid;
use fvm_ipld_blockstore::Blockstore;
Expand Down Expand Up @@ -412,29 +411,20 @@ impl Tipset {

/// Fetch the genesis block header for a given tipset.
pub fn genesis(&self, store: &impl Blockstore) -> anyhow::Result<CachingBlockHeader> {
// Scanning through millions of epochs to find the genesis is quite
// slow. Let's use a list of known blocks to short-circuit the search.
// The blocks are hash-chained together and known blocks are guaranteed
// to have a known genesis.
#[derive(Serialize, Deserialize)]
struct KnownHeaders {
calibnet: HashMap<ChainEpoch, String>,
mainnet: HashMap<ChainEpoch, String>,
}

static KNOWN_HEADERS: OnceLock<KnownHeaders> = OnceLock::new();
let headers = KNOWN_HEADERS.get_or_init(|| {
serde_yaml::from_str(include_str!("../../build/known_blocks.yaml")).unwrap()
});

for tipset in self.clone().chain(store) {
// Search for known calibnet and mainnet blocks
for (genesis_cid, known_blocks) in [
(*calibnet::GENESIS_CID, &headers.calibnet),
(*mainnet::GENESIS_CID, &headers.mainnet),
(
*calibnet::GENESIS_CID,
&super::checkpoints::KNOWN_BLOCKS.calibnet,
),
(
*mainnet::GENESIS_CID,
&super::checkpoints::KNOWN_BLOCKS.mainnet,
),
] {
if let Some(known_block_cid) = known_blocks.get(&tipset.epoch())
&& known_block_cid == &tipset.min_ticket_block().cid().to_string()
&& known_block_cid == tipset.min_ticket_block().cid()
{
return store
.get_cbor(&genesis_cid)?
Expand Down
60 changes: 43 additions & 17 deletions src/chain/store/chain_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use super::{
index::{ChainIndex, ResolveNullTipset},
tipset_tracker::TipsetTracker,
};
use crate::libp2p_bitswap::{BitswapStoreRead, BitswapStoreReadWrite};
use crate::message::{ChainMessage, Message as MessageTrait, SignedMessage};
use crate::networks::{ChainConfig, Height};
use crate::rpc::eth::{eth_tx_from_signed_eth_message, types::EthHash};
Expand All @@ -17,6 +16,10 @@ use crate::shim::{
};
use crate::state_manager::StateOutput;
use crate::utils::db::{BlockstoreExt, CborStoreExt};
use crate::{
blocks::checkpoints::known_tipsets,
libp2p_bitswap::{BitswapStoreRead, BitswapStoreReadWrite},
};
use crate::{
blocks::{CachingBlockHeader, Tipset, TipsetKey, TxMeta},
db::HeaviestTipsetKeyProvider,
Expand Down Expand Up @@ -242,6 +245,41 @@ where
Tipset::from(self.genesis_block_header())
}

/// Find tipset at epoch `to` in the chain
pub fn tipset_by_height(
&self,
to: ChainEpoch,
from: Option<Tipset>,
resolve: ResolveNullTipset,
) -> Result<Tipset, Error> {
let head = self.heaviest_tipset();
// Fail fast when `to` is too large
if to > head.epoch() {
return Err(Error::Other(format!(
"looking for tipset with height greater than the current chain head, req: {to}, head: {}",
head.epoch()
)));
}

let best_known_from = if let Ok(known_tipsets) =
known_tipsets(self.blockstore(), &self.chain_config().network)
&& let Some(ts) = known_tipsets.iter().find(|ts| ts.epoch() > to).cloned()
{
Some(ts)
} else {
None
};
Comment on lines +264 to +271
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C4 'known_blocks|known_blocks\.yaml|checkpoint|CHAIN_FINALITY|finality' build.rs src

Repository: ChainSafe/forest

Length of output: 50372


🏁 Script executed:

#!/bin/bash
# Look at the update_chain_checkpoints function and surrounding finality checks
sed -n '110,160p' src/dev/subcommands/update_checkpoints_cmd.rs

# Check CHECKPOINT_INTERVAL and CHAIN_FINALITY relationship
rg -n 'CHAIN_FINALITY|chain_finality' src/dev/subcommands/update_checkpoints_cmd.rs

# Check constants.rs or similar for CHAIN_FINALITY value
rg -n 'const.*CHAIN_FINALITY|const.*CHECKPOINT_INTERVAL' --type rs

Repository: ChainSafe/forest

Length of output: 1999


🏁 Script executed:

#!/bin/bash
# Find the fetch_checkpoint_at_height function and check for finality guards
rg -A 20 'async fn fetch_checkpoint_at_height' src/dev/subcommands/update_checkpoints_cmd.rs

# Search for CHAIN_FINALITY definitions in networks or config
rg 'chain_finality\s*[:=]' src/networks/ src/shim/ | head -20

# Look at the end of update_chain_checkpoints to see if there's finality validation
sed -n '110,200p' src/dev/subcommands/update_checkpoints_cmd.rs | tail -50

Repository: ChainSafe/forest

Length of output: 3335


🏁 Script executed:

#!/bin/bash
# Check CHAIN_FINALITY values for mainnet/calibnet
rg -B 2 -A 2 'chain_finality.*:.*900|chain_finality.*=.*900' src/networks/

# Look at full tipset_by_height implementation to check the `from` filtering issue
sed -n '246,275p' src/chain/store/chain_store.rs

Repository: ChainSafe/forest

Length of output: 1168


🏁 Script executed:

#!/bin/bash
# Find CHAIN_FINALITY values for mainnet and calibnet
rg 'chain_finality.*900' src/networks/ -A 1 -B 1

# Also check the policy structure
rg -A 5 'struct.*Policy' src/shim/ | head -30

# Check if there's any finality-distance validation when adding checkpoints
rg -B 5 -A 5 'latest_checkpoint_epoch.*=' src/dev/subcommands/update_checkpoints_cmd.rs

Repository: ChainSafe/forest

Length of output: 990


🏁 Script executed:

#!/bin/bash
# Find the actual CHAIN_FINALITY constants for networks
rg 'chain_finality\s*[:=]\s*900' src/networks/

# Check what CHECKPOINT_INTERVAL is
rg 'CHECKPOINT_INTERVAL' src/dev/subcommands/update_checkpoints_cmd.rs

# Check mainnet policy to see if CHECKPOINT_INTERVAL >= CHAIN_FINALITY
python3 << 'EOF'
checkpoint_interval = 86400  # 30 days from code
chain_finality = 900  # EC consensus finality for Filecoin
print(f"CHECKPOINT_INTERVAL: {checkpoint_interval} epochs")
print(f"CHAIN_FINALITY: {chain_finality} epochs")
print(f"CHECKPOINT_INTERVAL >= CHAIN_FINALITY: {checkpoint_interval >= chain_finality}")
print(f"\nDistance from head when rounding down:")
print(f"  Max distance: {checkpoint_interval - 1} epochs")
print(f"  Safe if >= CHAIN_FINALITY: {checkpoint_interval - 1 >= chain_finality}")
EOF

# Check if there's any safety margin or validation applied AFTER fetching
sed -n '110,180p' src/dev/subcommands/update_checkpoints_cmd.rs | grep -A 10 'Fetching checkpoints'

Repository: ChainSafe/forest

Length of output: 1055


🏁 Script executed:

#!/bin/bash
# Check if there's validation for from.epoch() >= to in chain_index.tipset_by_height
rg -A 30 'fn tipset_by_height' src/chain/index.rs | head -40

# Also check if the caller-provided `from` parameter is documented
rg -B 5 'pub fn tipset_by_height' src/chain/store/chain_store.rs

Repository: ChainSafe/forest

Length of output: 313


🏁 Script executed:

#!/bin/bash
# Find where ChainIndex is defined
fd ChainIndex src/ --type f | head -5

# Find the implementation of chain_index().tipset_by_height()
rg -l 'impl.*ChainIndex' src/ | head -5

# Search for the actual tipset_by_height in ChainIndex
rg -A 30 'impl.*ChainIndex' src/chain/ | grep -A 30 'tipset_by_height'

Repository: ChainSafe/forest

Length of output: 113


🏁 Script executed:

#!/bin/bash
# Find the tipset_by_height method in ChainIndex
rg -A 40 'fn tipset_by_height' src/chain/store/index.rs

# Also verify the behavior when from.epoch() < to
sed -n '1,100p' src/chain/store/index.rs | tail -50

Repository: ChainSafe/forest

Length of output: 3717


Add validation to reject caller-provided from that is already below the target height to.

The checkpoint finality constraint is sound—CHECKPOINT_INTERVAL (86400 epochs) >> CHAIN_FINALITY (900 epochs) guarantees that known tipsets are immutable. However, the match logic at lines 253–260 does not validate that the selected from satisfies from.epoch() >= to. If a caller passes a from with from.epoch() < to and no closer checkpoint is found, this invalid from will be delegated to ChainIndex::tipset_by_height(), which will immediately fail with a cryptic error ("looking for tipset with height greater than start point").

Add a validation after the match (or inline in the match arms) to ensure from.epoch() >= to:

let from = match (from, best_known_from) {
    (Some(a), Some(b)) if b.epoch() < a.epoch() => b,
    (Some(a), _) => a,
    (None, Some(b)) => b,
    (None, None) => self.heaviest_tipset(),
};
if from.epoch() < to {
    return Err(Error::Other(format!("start tipset epoch {} is before target epoch {}", from.epoch(), to)));
}

This prevents downstream errors and provides a clear, actionable error message to callers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/chain/store/chain_store.rs` around lines 253 - 260, The match that
chooses the starting tipset (using the local best_known_from variable computed
from known_tipsets(self.blockstore(), &self.chain_config().network)) can return
a caller-provided from that is below the target epoch to, causing a cryptic
failure later in ChainIndex::tipset_by_height; after you pick the effective from
(the logic that currently picks between the input from, best_known_from, or
self.heaviest_tipset()), add a validation that verifies from.epoch() >= to and
if not return an explicit Err(Error::Other(...)) noting the start tipset epoch
and the target epoch; update the code paths involving best_known_from, the match
that picks from, and any use of heaviest_tipset() so they perform this check
immediately after selection.

let from = match (from, best_known_from) {
// prefer `b` when `b` is closer to `to`
(Some(a), Some(b)) if b.epoch() < a.epoch() => b,
// prefer `a` when presents
(Some(a), _) => a,
(None, Some(b)) => b,
// fallback to chain head
(None, None) => head,
};
Comment on lines +272 to +280
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Ignore unusable from tipsets before delegating to ChainIndex.

If a caller passes Some(from) with from.epoch() < to, the (Some(a), _) => a arm forwards an invalid start point and ChainIndex::tipset_by_height() fails immediately, even though best_known_from or the head could satisfy the lookup. Filter from with epoch() >= to before this match.

♻️ Suggested fix
-        let from = match (from, best_known_from) {
+        let from = match (from.filter(|ts| ts.epoch() >= to), best_known_from) {
             // prefer `b` when `b` is closer to `to`
             (Some(a), Some(b)) if b.epoch() < a.epoch() => b,
             // prefer `a` when presents
             (Some(a), _) => a,
             (None, Some(b)) => b,
             // fallback to chain head
             (None, None) => self.heaviest_tipset(),
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/chain/store/chain_store.rs` around lines 261 - 269, The match
unconditionally prefers `from` when present even if `from.epoch() < to`, which
can forward an unusable start and make `ChainIndex::tipset_by_height()` fail;
before the match, normalize `from` by turning any `Some(a)` with `a.epoch() <
to` into `None` (e.g., `let from = match from { Some(a) if a.epoch() >= to =>
Some(a), _ => None };`) so the subsequent match on `(from, best_known_from)`
will fall through to `best_known_from` or `self.heaviest_tipset()` when `from`
is below `to`, ensuring `tipset_by_height()` gets a valid start point.

Comment on lines +264 to +280
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Only seed from known checkpoints once they're finalized against the active head.

best_known_from can replace the caller/default start tipset purely because it's closer to to. When the active head is still within chain_finality of that checkpoint, the checkpoint is not guaranteed to be on the same branch yet, so this lookup can answer from the wrong fork. Filter checkpoint candidates against the effective head epoch before letting them override from.

♻️ Possible fix
+        let head_epoch = from
+            .as_ref()
+            .map(|ts| ts.epoch())
+            .unwrap_or_else(|| self.heaviest_tipset().epoch());
         let best_known_from = if let Ok(known_tipsets) =
             known_tipsets(self.blockstore(), &self.chain_config().network)
-            && let Some(ts) = known_tipsets.iter().find(|ts| ts.epoch() > to).cloned()
+            && let Some(ts) = known_tipsets
+                .iter()
+                .find(|ts| {
+                    ts.epoch() > to
+                        && ts.epoch() + self.chain_config().policy.chain_finality <= head_epoch
+                })
+                .cloned()
         {
             Some(ts)
         } else {
             None
         };
Based on learnings: "constraining cache lookups to finalized epochs ... provides fork safety without the performance cost of ancestry verification."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/chain/store/chain_store.rs` around lines 253 - 269, best_known_from may
pick a checkpoint that is not finalized relative to the current active head and
thus choose a tipset from the wrong fork; update the lookup in the block where
best_known_from is computed (using known_tipsets(self.blockstore(),
&self.chain_config().network) and the subsequent match that picks `from`) to
filter candidate tipsets by requiring their epoch to be <= effective_head_epoch
- chain_finality (compute effective_head_epoch from the current head/heaviest
tipset via self.heaviest_tipset().epoch() or equivalent) before allowing them to
override `from`, so only checkpoints finalized against the active head can
replace the caller/default start tipset.

self.chain_index().tipset_by_height(to, from, resolve)
}
/// Subscribes head changes.
pub fn subscribe_head_changes(&self) -> broadcast::Receiver<HeadChanges> {
self.head_changes_tx.subscribe()
Expand Down Expand Up @@ -278,22 +316,10 @@ where
}

pub fn load_child_tipset(&self, ts: &Tipset) -> Result<Tipset, Error> {
let head = self.heaviest_tipset();
if head.parents() == ts.key() {
Ok(head)
} else if head.epoch() > ts.epoch() {
let maybe_child = self.chain_index().tipset_by_height(
ts.epoch() + 1,
head,
ResolveNullTipset::TakeNewer,
)?;
if maybe_child.parents() == ts.key() {
Ok(maybe_child)
} else {
Err(Error::NotFound(
format!("child of tipset@{}", ts.epoch()).into(),
))
}
let maybe_child =
self.tipset_by_height(ts.epoch() + 1, None, ResolveNullTipset::TakeNewer)?;
if maybe_child.parents() == ts.key() {
Ok(maybe_child)
} else {
Err(Error::NotFound(
format!("child of tipset@{}", ts.epoch()).into(),
Expand Down
12 changes: 2 additions & 10 deletions src/dev/subcommands/state_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,7 @@ impl ComputeCommand {
let (ts, ts_next) = {
// We don't want to track all entries that are visited by `tipset_by_height`
db.pause_tracking();
let ts = chain_store.chain_index().tipset_by_height(
epoch,
chain_store.heaviest_tipset(),
ResolveNullTipset::TakeOlder,
)?;
let ts = chain_store.tipset_by_height(epoch, None, ResolveNullTipset::TakeOlder)?;
let ts_next = chain_store.load_child_tipset(&ts)?;
db.resume_tracking();
SettingsStoreExt::write_obj(
Expand Down Expand Up @@ -210,11 +206,7 @@ impl ValidateCommand {
let ts = {
// We don't want to track all entries that are visited by `tipset_by_height`
db.pause_tracking();
let ts = chain_store.chain_index().tipset_by_height(
epoch,
chain_store.heaviest_tipset(),
ResolveNullTipset::TakeOlder,
)?;
let ts = chain_store.tipset_by_height(epoch, None, ResolveNullTipset::TakeOlder)?;
db.resume_tracking();
SettingsStoreExt::write_obj(&db.tracker, crate::db::setting_keys::HEAD_KEY, ts.key())?;
// Only track the desired tipset
Expand Down
37 changes: 18 additions & 19 deletions src/rpc/methods/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,9 @@ impl RpcMethod<0> for ChainGetFinalizedTipset {
Err(_) => {
// fallback to ec finality
tracing::warn!("F3 finalization unavailable, falling back to EC finality");
let ec_tipset = ctx.chain_index().tipset_by_height(
let ec_tipset = ctx.chain_store().tipset_by_height(
ec_finality_epoch,
head,
None,
ResolveNullTipset::TakeOlder,
)?;
Ok(ec_tipset)
Expand Down Expand Up @@ -438,8 +438,8 @@ impl RpcMethod<1> for ForestChainExport {

let head = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
let start_ts =
ctx.chain_index()
.tipset_by_height(epoch, head, ResolveNullTipset::TakeOlder)?;
ctx.chain_store()
.tipset_by_height(epoch, Some(head), ResolveNullTipset::TakeOlder)?;

let options = Some(ExportOptions {
skip_checksum,
Expand Down Expand Up @@ -629,10 +629,9 @@ impl RpcMethod<1> for ForestChainExportDiff {
);
}

let head = ctx.chain_store().heaviest_tipset();
let start_ts =
ctx.chain_index()
.tipset_by_height(from, head, ResolveNullTipset::TakeOlder)?;
ctx.chain_store()
.tipset_by_height(from, None, ResolveNullTipset::TakeOlder)?;

crate::tool::subcommands::archive_cmd::do_export(
&ctx.store_owned(),
Expand Down Expand Up @@ -929,9 +928,9 @@ impl RpcMethod<2> for ChainGetTipSetByHeight {
let ts = ctx
.chain_store()
.load_required_tipset_or_heaviest(&tipset_key)?;
let tss = ctx
.chain_index()
.tipset_by_height(height, ts, ResolveNullTipset::TakeOlder)?;
let tss =
ctx.chain_store()
.tipset_by_height(height, Some(ts), ResolveNullTipset::TakeOlder)?;
Ok(tss)
}
}
Expand Down Expand Up @@ -959,9 +958,9 @@ impl RpcMethod<2> for ChainGetTipSetAfterHeight {
let ts = ctx
.chain_store()
.load_required_tipset_or_heaviest(&tipset_key)?;
let tss = ctx
.chain_index()
.tipset_by_height(height, ts, ResolveNullTipset::TakeNewer)?;
let tss =
ctx.chain_store()
.tipset_by_height(height, Some(ts), ResolveNullTipset::TakeNewer)?;
Ok(tss)
}
}
Expand Down Expand Up @@ -1102,9 +1101,9 @@ impl ChainGetTipSetV2 {
if finalized.epoch() >= safe_height {
Ok(finalized)
} else {
Ok(ctx.chain_index().tipset_by_height(
Ok(ctx.chain_store().tipset_by_height(
safe_height,
head,
None,
ResolveNullTipset::TakeOlder,
)?)
}
Expand Down Expand Up @@ -1140,9 +1139,9 @@ impl ChainGetTipSetV2 {
pub fn get_ec_finalized_tipset(ctx: &Ctx<impl Blockstore>) -> anyhow::Result<Tipset> {
let head = ctx.chain_store().heaviest_tipset();
let ec_finality_epoch = (head.epoch() - ctx.chain_config().policy.chain_finality).max(0);
Ok(ctx.chain_index().tipset_by_height(
Ok(ctx.chain_store().tipset_by_height(
ec_finality_epoch,
head,
None,
ResolveNullTipset::TakeOlder,
)?)
}
Expand All @@ -1160,9 +1159,9 @@ impl ChainGetTipSetV2 {
// Get tipset by height.
if let Some(height) = &selector.height {
let anchor = Self::get_tipset_by_anchor(ctx, height.anchor.as_ref()).await?;
let ts = ctx.chain_index().tipset_by_height(
let ts = ctx.chain_store().tipset_by_height(
height.at,
anchor,
Some(anchor),
height.resolve_null_tipset_policy(),
)?;
return Ok(ts);
Expand Down
9 changes: 2 additions & 7 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -944,9 +944,7 @@ fn resolve_block_number_tipset<DB: Blockstore>(
if height > head.epoch() - 1 {
bail!("requested a future epoch (beyond \"latest\")");
}
Ok(chain
.chain_index()
.tipset_by_height(height, head, resolve)?)
Ok(chain.tipset_by_height(height, None, resolve)?)
}

fn resolve_block_hash_tipset<DB: Blockstore>(
Expand All @@ -959,10 +957,7 @@ fn resolve_block_hash_tipset<DB: Blockstore>(
// verify that the tipset is in the canonical chain
if require_canonical {
// walk up the current chain (our head) until we reach ts.epoch()
let walk_ts =
chain
.chain_index()
.tipset_by_height(ts.epoch(), chain.heaviest_tipset(), resolve)?;
let walk_ts = chain.tipset_by_height(ts.epoch(), None, resolve)?;
// verify that it equals the expected tipset
if walk_ts != ts {
bail!("tipset is not canonical");
Expand Down
4 changes: 2 additions & 2 deletions src/rpc/methods/eth/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,9 @@ impl EthEventHandler {
*range.end()
};

let max_tipset = ctx.chain_index().tipset_by_height(
let max_tipset = ctx.chain_store().tipset_by_height(
max_height,
ctx.chain_store().heaviest_tipset(),
None,
ResolveNullTipset::TakeOlder,
)?;
for tipset in max_tipset
Expand Down
Loading
Loading