From c6eb82cb26e9401929bbdd781f948da46fd84507 Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 15 Apr 2026 14:46:05 +0530 Subject: [PATCH 1/5] ensure reorg-stable --- src/message_pool/msgpool/provider.rs | 39 +++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/message_pool/msgpool/provider.rs b/src/message_pool/msgpool/provider.rs index 23d9dc6eda68..33fcc4fe8c08 100644 --- a/src/message_pool/msgpool/provider.rs +++ b/src/message_pool/msgpool/provider.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0, MIT use crate::blocks::{CachingBlockHeader, Tipset, TipsetKey}; +use crate::chain::index::ResolveNullTipset; use crate::chain::{ChainStore, HeadChanges}; use crate::message::{ChainMessage, SignedMessage}; use crate::message_pool::errors::Error; @@ -10,7 +11,7 @@ use crate::message_pool::msg_pool::{ }; use crate::networks::Height; use crate::shim::{ - address::Address, + address::{Address, Protocol::*}, econ::TokenAmount, message::Message, state_tree::{ActorState, StateTree}, @@ -101,13 +102,37 @@ impl Provider for ChainStore { .map_err(|err| err.into()) } - // TODO(forest): https://github.com/ChainSafe/forest/issues/6891 + /// Resolves an address to its deterministic key form using the state at + /// finality lookback, This ensures the resolved address is reorg-stable. fn resolve_to_key(&self, addr: &Address, ts: &Tipset) -> Result { - let state = StateTree::new_from_root(self.blockstore().clone(), ts.parent_state()) - .map_err(|e| Error::Other(e.to_string()))?; - state - .resolve_to_deterministic_addr(self.blockstore(), *addr) - .map_err(|e| Error::Other(e.to_string())) + match addr.protocol() { + BLS | Secp256k1 | Delegated => return Ok(*addr), + Actor => { + return Err(Error::Other( + "Cannot resolve actor address to key address".into(), + )); + } + _ => { + let lookback_ts = if ts.epoch() > self.chain_config().policy.chain_finality { + self.chain_index() + .tipset_by_height( + ts.epoch() - self.chain_config().policy.chain_finality, + ts.clone(), + ResolveNullTipset::TakeOlder, + ) + .map_err(|e| Error::Other(e.to_string()))? + } else { + ts.clone() + }; + + let state = + StateTree::new_from_root(self.blockstore().clone(), lookback_ts.parent_state()) + .map_err(|e| Error::Other(e.to_string()))?; + state + .resolve_to_deterministic_addr(self.blockstore(), *addr) + .map_err(|e| Error::Other(e.to_string())) + } + } } fn messages_for_tipset(&self, ts: &Tipset) -> Result>, Error> { From a284b6ac77ba108317cf32a0ed72b54d1f72a1eb Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 15 Apr 2026 15:02:36 +0530 Subject: [PATCH 2/5] fix spellcheck --- src/message_pool/msgpool/provider.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/message_pool/msgpool/provider.rs b/src/message_pool/msgpool/provider.rs index 33fcc4fe8c08..7646bb18c2c2 100644 --- a/src/message_pool/msgpool/provider.rs +++ b/src/message_pool/msgpool/provider.rs @@ -103,7 +103,7 @@ impl Provider for ChainStore { } /// Resolves an address to its deterministic key form using the state at - /// finality lookback, This ensures the resolved address is reorg-stable. + /// finality look-back, This ensures the resolved address is reorg-stable. fn resolve_to_key(&self, addr: &Address, ts: &Tipset) -> Result { match addr.protocol() { BLS | Secp256k1 | Delegated => return Ok(*addr), From 8fc6f0876d7ce5d09f27adf2d6b429357b826e61 Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 15 Apr 2026 15:23:05 +0530 Subject: [PATCH 3/5] lint fix --- src/message_pool/msgpool/provider.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/message_pool/msgpool/provider.rs b/src/message_pool/msgpool/provider.rs index 7646bb18c2c2..555d353321f5 100644 --- a/src/message_pool/msgpool/provider.rs +++ b/src/message_pool/msgpool/provider.rs @@ -106,11 +106,11 @@ impl Provider for ChainStore { /// finality look-back, This ensures the resolved address is reorg-stable. fn resolve_to_key(&self, addr: &Address, ts: &Tipset) -> Result { match addr.protocol() { - BLS | Secp256k1 | Delegated => return Ok(*addr), + BLS | Secp256k1 | Delegated => Ok(*addr), Actor => { - return Err(Error::Other( + Err(Error::Other( "Cannot resolve actor address to key address".into(), - )); + )) } _ => { let lookback_ts = if ts.epoch() > self.chain_config().policy.chain_finality { From 8dea4c23f460e664b301ed512ddcc1aa3f74c4a7 Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 16 Apr 2026 05:02:03 +0530 Subject: [PATCH 4/5] lint fix --- src/message_pool/msgpool/provider.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/message_pool/msgpool/provider.rs b/src/message_pool/msgpool/provider.rs index 555d353321f5..0976dbb67312 100644 --- a/src/message_pool/msgpool/provider.rs +++ b/src/message_pool/msgpool/provider.rs @@ -107,11 +107,9 @@ impl Provider for ChainStore { fn resolve_to_key(&self, addr: &Address, ts: &Tipset) -> Result { match addr.protocol() { BLS | Secp256k1 | Delegated => Ok(*addr), - Actor => { - Err(Error::Other( - "Cannot resolve actor address to key address".into(), - )) - } + Actor => Err(Error::Other( + "Cannot resolve actor address to key address".into(), + )), _ => { let lookback_ts = if ts.epoch() > self.chain_config().policy.chain_finality { self.chain_index() From 50bfbc7872d1fe4bb65761c13ac050ea56c8695f Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 16 Apr 2026 12:03:40 +0530 Subject: [PATCH 5/5] Add test --- src/message_pool/msgpool/msg_pool.rs | 81 +++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/src/message_pool/msgpool/msg_pool.rs b/src/message_pool/msgpool/msg_pool.rs index bdc55a992000..3a10cd66db69 100644 --- a/src/message_pool/msgpool/msg_pool.rs +++ b/src/message_pool/msgpool/msg_pool.rs @@ -942,8 +942,15 @@ pub fn remove( #[cfg(test)] mod tests { + use crate::blocks::RawBlockHeader; + use crate::chain::ChainStore; + use crate::db::MemoryDB; + use crate::message_pool::provider::Provider; use crate::message_pool::test_provider::TestApi; + use crate::networks::ChainConfig; use crate::shim::econ::TokenAmount; + use crate::shim::state_tree::{ActorState, StateTree, StateTreeVersion}; + use crate::utils::db::CborStoreExt as _; use super::*; use crate::shim::message::Message as ShimMessage; @@ -1107,8 +1114,6 @@ mod tests { #[test] fn test_get_sequence_works_with_both_address_forms() { - use crate::message_pool::provider::Provider; - let api = TestApi::default(); let bls_sig_cache = SizeTrackingLruCache::new_mocked(); let key_cache = SizeTrackingLruCache::new_mocked(); @@ -1501,4 +1506,76 @@ mod tests { "different tipset should miss the cache and read fresh state" ); } + + #[test] + fn resolve_to_key_uses_finality_lookback() { + let db = Arc::new(MemoryDB::default()); + + let mut cfg = ChainConfig::default(); + cfg.policy.chain_finality = 1; + let cfg = Arc::new(cfg); + + let bls_a = Address::new_bls(&[8u8; 48]).unwrap(); + let bls_b = Address::new_bls(&[9u8; 48]).unwrap(); + + // root_a: only contains f0300 + let mut st_a = StateTree::new(db.clone(), StateTreeVersion::V5).unwrap(); + st_a.set_actor( + &Address::new_id(300), + ActorState::new_empty(Cid::default(), Some(bls_a)), + ) + .unwrap(); + let root_a = st_a.flush().unwrap(); + + // root_b: only contains f0400 + let mut st_b = StateTree::new(db.clone(), StateTreeVersion::V5).unwrap(); + st_b.set_actor( + &Address::new_id(400), + ActorState::new_empty(Cid::default(), Some(bls_b)), + ) + .unwrap(); + let root_b = st_b.flush().unwrap(); + + let genesis = Tipset::from(CachingBlockHeader::new(RawBlockHeader { + state_root: root_a, + ..Default::default() + })); + db.put_cbor_default(genesis.block_headers().first()) + .unwrap(); + + let ts1 = Tipset::from(CachingBlockHeader::new(RawBlockHeader { + parents: genesis.key().clone(), + epoch: 1, + state_root: root_a, + timestamp: 1, + ..Default::default() + })); + db.put_cbor_default(ts1.block_headers().first()).unwrap(); + + let head = Tipset::from(CachingBlockHeader::new(RawBlockHeader { + parents: ts1.key().clone(), + epoch: 2, + state_root: root_b, + timestamp: 2, + ..Default::default() + })); + db.put_cbor_default(head.block_headers().first()).unwrap(); + + let cs = ChainStore::new( + db.clone(), + db.clone(), + db, + cfg, + genesis.block_headers().first().clone(), + ) + .unwrap(); + + // f0300 exists in lookback state (root_a) → resolves successfully. + let result = Provider::resolve_to_key(&cs, &Address::new_id(300), &head).unwrap(); + assert_eq!(result, bls_a); + + // f0400 exists only in head state (root_b), not in lookback → fails. + Provider::resolve_to_key(&cs, &Address::new_id(400), &head) + .expect_err("actor only in head state must not resolve via finality lookback"); + } }