Skip to content
Open
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
5 changes: 3 additions & 2 deletions src/rpc/methods/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,7 +1266,6 @@ impl RpcMethod<4> for StateSearchMsg {
}
}

/// Looks back up to limit epochs in the chain for a message, and returns its receipt and the tipset where it was executed.
/// See <https://github.com/filecoin-project/lotus/blob/master/documentation/en/api-methods-v0-deprecated.md#StateSearchMsgLimited>
pub enum StateSearchMsgLimited {}

Expand All @@ -1275,7 +1274,9 @@ impl RpcMethod<2> for StateSearchMsgLimited {
const PARAM_NAMES: [&'static str; 2] = ["message_cid", "look_back_limit"];
const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V0); // Not supported in V1
const PERMISSION: Permission = Permission::Read;

const DESCRIPTION: Option<&'static str> = Some(
"Looks back up to limit epochs in the chain for a message, and returns its receipt and the tipset where it was executed.",
);
type Params = (Cid, i64);
type Ok = MessageLookup;

Expand Down
3 changes: 2 additions & 1 deletion src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ pub use methods::*;
/// Protocol or transport-specific error
pub use jsonrpsee::core::ClientError;

const LOOKBACK_NO_LIMIT: ChainEpoch = -1;
/// Sentinel value, indicating no limit on how far back to search in the chain (all the way to genesis epoch).
pub const LOOKBACK_NO_LIMIT: ChainEpoch = -1;
Copy link
Member

Choose a reason for hiding this comment

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

Let's document this constant while we're at it; the name is not self-explanatory.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.


/// The macro `callback` will be passed in each type that implements
/// [`RpcMethod`].
Expand Down
25 changes: 20 additions & 5 deletions src/state_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1046,17 +1046,17 @@ where
&self,
mut current: Tipset,
message: &ChainMessage,
look_back_limit: Option<i64>,
allow_replaced: Option<bool>,
lookback_max_epoch: ChainEpoch,
allow_replaced: bool,
) -> Result<Option<(Tipset, Receipt)>, Error> {
let allow_replaced = allow_replaced.unwrap_or(true);
let message_from_address = message.from();
let message_sequence = message.sequence();
let mut current_actor_state = self
.get_required_actor(&message_from_address, *current.parent_state())
.map_err(Error::state)?;
let message_from_id = self.lookup_required_id(&message_from_address, &current)?;
while current.epoch() > look_back_limit.unwrap_or_default() {

while current.epoch() >= lookback_max_epoch {
let parent_tipset = self
.chain_index()
.load_required_tipset(current.parents())
Expand Down Expand Up @@ -1091,14 +1091,29 @@ where
Ok(None)
}

/// Searches backwards through the chain for a message receipt.
fn search_back_for_message(
&self,
current: Tipset,
message: &ChainMessage,
look_back_limit: Option<i64>,
allow_replaced: Option<bool>,
) -> Result<Option<(Tipset, Receipt)>, Error> {
self.check_search(current, message, look_back_limit, allow_replaced)
let current_epoch = current.epoch();
let allow_replaced = allow_replaced.unwrap_or(true);

// Calculate the max lookback epoch (inclusive lower bound) for the search.
let lookback_max_epoch = match look_back_limit {
// No search: limit = 0 means search 0 epochs
Some(0) => return Ok(None),
// Limited search: calculate the inclusive lower bound
// For ex: limit = 5 at epoch 1000: min_epoch = 996, searches [996, 1000] = 5 epochs
Some(limit) if limit > 0 && limit < current_epoch => current_epoch - limit + 1,
// Search all the way to genesis (epoch 0)
_ => 0,
};
Comment on lines +1105 to +1114
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential off-by-one error when limit equals current_epoch.

The condition limit < current_epoch on Line 1111 excludes the case where limit == current_epoch, causing it to fall through to the unlimited search case (epoch 0). This results in searching one more epoch than requested.

Example at epoch 1000:

  • limit=1000: Current code searches [0, 1000] = 1001 epochs (falls through to line 1113)
  • limit=1000: Formula would give 1000 - 1000 + 1 = 1 → searches [1, 1000] = 1000 epochs
🔧 Suggested fix
-        // Calculate the max lookback epoch (inclusive lower bound) for the search.
+        // Calculate the lookback epoch (inclusive lower bound) for the search.
         let lookback_max_epoch = match look_back_limit {
             // No search: limit = 0 means search 0 epochs
             Some(0) => return Ok(None),
-            // Limited search: calculate the inclusive lower bound
-            // For ex: limit = 5 at epoch 1000: min_epoch = 996, searches [996, 1000] = 5 epochs
-            Some(limit) if limit > 0 && limit < current_epoch => current_epoch - limit + 1,
-            // Search all the way to genesis (epoch 0)
+            // Limited search: calculate the inclusive lower bound, clamped to genesis
+            // Example: limit=5 at epoch=1000 → min_epoch=996, searches [996,1000] = 5 epochs
+            // Example: limit=2000 at epoch=1000 → min_epoch=0, searches [0,1000] = 1001 epochs (all available)
+            Some(limit) if limit > 0 => (current_epoch - limit + 1).max(0),
+            // Unlimited search: None or negative limit (e.g., -1) searches to genesis (epoch 0)
             _ => 0,
         };

This change:

  1. Correctly handles limit == current_epoch using the formula instead of falling through
  2. Explicitly clamps to 0 when limit > current_epoch, making the behavior clearer
  3. Maintains the same behavior for unlimited searches (None or negative limits)
🤖 Prompt for AI Agents
In @src/state_manager/mod.rs around lines 1105 - 1114, The match on
look_back_limit mis-handles limit == current_epoch and > current_epoch; update
the branches for lookback_max_epoch so positive limits are handled as: if
Some(limit) and limit > 0 && limit <= current_epoch compute current_epoch -
limit + 1 (so limit == current_epoch yields 1), and add a branch Some(limit) if
limit > current_epoch => 0 to explicitly clamp to genesis; keep the Some(0) =>
return Ok(None) and the default => 0 behavior unchanged.


self.check_search(current, message, lookback_max_epoch, allow_replaced)
}

/// Returns a message receipt from a given tipset and message CID.
Expand Down
4 changes: 2 additions & 2 deletions src/tool/subcommands/api_cmd/test_snapshots.txt
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ filecoin_statereadstate_1748444218790447.rpcsnap.json.zst
filecoin_statereadstate_1748444218790807.rpcsnap.json.zst
filecoin_statereadstate_1749657617007020.rpcsnap.json.zst
filecoin_statereplay_1743504051038215.rpcsnap.json.zst
filecoin_statesearchmsg_1741784596636715.rpcsnap.json.zst
filecoin_statesearchmsglimited_1741784596704877.rpcsnap.json.zst
filecoin_statesearchmsg_1767947430707712.rpcsnap.json.zst
filecoin_statesearchmsglimited_1767936905056090.rpcsnap.json.zst
filecoin_statesectorexpiration_1741784727996672.rpcsnap.json.zst
filecoin_statesectorgetinfo_1743098602783105.rpcsnap.json.zst
filecoin_statesectorpartition_1737546933391247.rpcsnap.json.zst
Expand Down
Loading