Skip to main content

forest/state_manager/
address_resolution.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::*;
5use crate::chain::AtFinalityResolution;
6use crate::prelude::*;
7use crate::shim::address::Payload;
8use bls_signatures::{PublicKey as BlsPublicKey, Serialize as _};
9
10impl StateManager {
11    /// Returns a BLS public key from provided address
12    pub fn get_bls_public_key(
13        db: &(impl Blockstore + ShallowClone),
14        addr: Address,
15        state_cid: Cid,
16    ) -> Result<BlsPublicKey, Error> {
17        let state =
18            StateTree::new_from_root(db, &state_cid).map_err(|e| Error::Other(e.to_string()))?;
19        let kaddr = state
20            .resolve_to_deterministic_address(db, addr)
21            .context("Failed to resolve key address")?;
22
23        match kaddr.into_payload() {
24            Payload::BLS(key) => BlsPublicKey::from_bytes(&key)
25                .context("Failed to construct bls public key")
26                .map_err(Error::from),
27            _ => Err(Error::state(
28                "Address must be BLS address to load bls public key",
29            )),
30        }
31    }
32
33    /// Looks up ID [Address] from the state at the given [Tipset].
34    pub fn lookup_id(&self, addr: &Address, ts: &Tipset) -> Result<Option<Address>, Error> {
35        let state_tree =
36            StateTree::new_from_root(self.db(), ts.parent_state()).map_err(|e| format!("{e:?}"))?;
37        Ok(state_tree
38            .lookup_id(addr)
39            .map_err(|e| Error::Other(e.to_string()))?
40            .map(Address::new_id))
41    }
42
43    /// Looks up required ID [Address] from the state at the given [Tipset].
44    pub fn lookup_required_id(&self, addr: &Address, ts: &Tipset) -> Result<Address, Error> {
45        self.lookup_id(addr, ts)?
46            .ok_or_else(|| Error::Other(format!("Failed to lookup the id address {addr}")))
47    }
48
49    /// Similar to [`StateTree::resolve_to_deterministic_addr`] but does not allow [`crate::shim::address::Protocol::Actor`] type of addresses.
50    /// Uses the [`Tipset`] `ts` to generate the VM state.
51    pub async fn resolve_to_deterministic_address(
52        &self,
53        address: Address,
54        ts: &Tipset,
55    ) -> anyhow::Result<Address> {
56        use crate::shim::address::Protocol::*;
57        match address.protocol() {
58            BLS | Secp256k1 | Delegated => Ok(address),
59            Actor => anyhow::bail!("cannot resolve actor address to key address"),
60            ID => {
61                let id = address.id()?;
62                // The cache is disabled for the RPC test-snapshot generator
63                // and replay harness (see the field docs on `StateManager`).
64                let Some(cache) = &self.id_to_deterministic_address_cache else {
65                    return self.resolve_id_address_at_tipset(address, ts).await;
66                };
67                if let Some(resolved) = cache.get(&id) {
68                    return Ok(resolved);
69                }
70                // Only a resolution witnessed at a finality-deep tipset is
71                // safe to memoize by bare ID: ID assignments within the
72                // finality window can differ between competing forks, while
73                // anything at or below the lookback is identical on every
74                // possible future chain.
75                let at_finality = {
76                    let cs = self.chain_store().shallow_clone();
77                    let ts = ts.clone();
78                    tokio::task::spawn_blocking(move || {
79                        cs.resolve_to_deterministic_address_at_finality(&address, &ts)
80                    })
81                    .await
82                    .context("tokio join error")?
83                };
84                if let Ok(resolution) = at_finality {
85                    return Ok(match resolution {
86                        AtFinalityResolution::ReorgStable(resolved) => {
87                            cache.insert(id, resolved);
88                            resolved
89                        }
90                        AtFinalityResolution::Unstable(resolved) => resolved,
91                    });
92                }
93                self.resolve_id_address_at_tipset(address, ts).await
94            }
95        }
96    }
97
98    /// Resolves an ID address against `ts` without touching the cache: first
99    /// via the parent state, then by computing the tipset state if needed.
100    async fn resolve_id_address_at_tipset(
101        &self,
102        address: Address,
103        ts: &Tipset,
104    ) -> anyhow::Result<Address> {
105        // First try to resolve the actor in the parent state, so we don't have to compute anything.
106        if let Ok(state) = self.get_state_tree(ts.parent_state()) {
107            match state.resolve_to_deterministic_address(self.db(), address) {
108                Ok(resolved) => return Ok(resolved),
109                // Re-executing the tipset can only change this for an actor absent
110                // from the parent state, or one a migration rewrites
111                // while `ts` is computed (e.g. FIP-0085). No point in doing it for
112                // keyless actors.
113                Err(e)
114                    if matches!(state.get_actor(&address), Ok(Some(_)))
115                        && !self.computes_across_migration(ts) =>
116                {
117                    return Err(e);
118                }
119                Err(_) => {}
120            }
121        }
122        // If that fails, compute the tip-set and try again.
123        let TipsetState { state_root, .. } = self.load_tipset_state(ts).await?;
124        let state = self.get_state_tree(&state_root)?;
125        state.resolve_to_deterministic_address(self.db(), address)
126    }
127
128    /// Whether computing `ts`'s state runs a network-upgrade migration. Such a
129    /// migration can rewrite an actor's code, so the pre-migration parent state
130    /// is not authoritative for a present actor across that boundary. Errs on
131    /// the side of `true` when the parent tipset cannot be loaded.
132    fn computes_across_migration(&self, ts: &Tipset) -> bool {
133        let Ok(parent) = self.chain_index().load_required_tipset(ts.parents()) else {
134            return true;
135        };
136        self.chain_config()
137            .has_expensive_fork_between(parent.epoch(), ts.epoch())
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use crate::blocks::{CachingBlockHeader, RawBlockHeader, Tipset};
145    use crate::chain::ChainStore;
146    use crate::db::{DbImpl, MemoryDB};
147    use crate::networks::ChainConfig;
148    use crate::shim::state_tree::{ActorState, StateTree, StateTreeVersion};
149    use crate::test_utils::dummy_ticket;
150    use crate::utils::db::CborStoreExt as _;
151
152    /// Present in the state from genesis onwards, so it is visible at the
153    /// finality lookback of the head.
154    const OLD_ACTOR: u64 = 300;
155    /// Present only in the head's parent state — younger than finality.
156    const YOUNG_ACTOR: u64 = 400;
157    /// Present from genesis but with a non-account code and no delegated
158    /// address, so it exists yet has no resolvable key address (a miner, a
159    /// singleton, ...).
160    const NON_ACCOUNT_ACTOR: u64 = 500;
161
162    /// Builds a 3-tipset chain (genesis, ts1, head at epoch 2). Genesis and
163    /// ts1 carry `root_a` (contains f0300 -> bls_a); head carries `root_b`,
164    /// built on top of `root_a` (contains f0300 -> bls_a and f0400 -> bls_b).
165    /// Returns (state_manager, head, bls_a, bls_b).
166    fn setup_with_finality(chain_finality: ChainEpoch) -> (StateManager, Tipset, Address, Address) {
167        let db: DbImpl = Arc::new(MemoryDB::default()).into();
168
169        let mut cfg = ChainConfig::default();
170        cfg.policy.chain_finality = chain_finality;
171        let cfg = Arc::new(cfg);
172
173        let bls_a = Address::new_bls(&[8u8; 48]).unwrap();
174        let bls_b = Address::new_bls(&[9u8; 48]).unwrap();
175
176        let mut st_a = StateTree::new(&db, StateTreeVersion::V5).unwrap();
177        st_a.set_actor(
178            &Address::new_id(OLD_ACTOR),
179            ActorState::new_empty(Cid::default(), Some(bls_a)),
180        )
181        .unwrap();
182        st_a.set_actor(
183            &Address::new_id(NON_ACCOUNT_ACTOR),
184            ActorState::new_empty(Cid::default(), None),
185        )
186        .unwrap();
187        let root_a = st_a.flush().unwrap();
188
189        // Builds on top of `root_a` (rather than a fresh tree) so f0300 stays
190        // resolvable at head's own parent state too, matching how a real
191        // state tree accumulates actors across epochs, and keeping every
192        // resolution in these tests on the cheap parent-state path.
193        let mut st_b = StateTree::new_from_root(&db, &root_a).unwrap();
194        st_b.set_actor(
195            &Address::new_id(YOUNG_ACTOR),
196            ActorState::new_empty(Cid::default(), Some(bls_b)),
197        )
198        .unwrap();
199        let root_b = st_b.flush().unwrap();
200
201        let genesis = Tipset::from(CachingBlockHeader::new(RawBlockHeader {
202            ticket: dummy_ticket(0),
203            state_root: root_a,
204            // `StateManager::new` builds a beacon schedule from the genesis
205            // timestamp, which must be non-zero.
206            timestamp: 1,
207            ..Default::default()
208        }));
209        db.put_cbor_default(genesis.block_headers().first())
210            .unwrap();
211
212        let ts1 = Tipset::from(CachingBlockHeader::new(RawBlockHeader {
213            parents: genesis.key().clone(),
214            ticket: dummy_ticket(1),
215            epoch: 1,
216            state_root: root_a,
217            timestamp: 1,
218            ..Default::default()
219        }));
220        db.put_cbor_default(ts1.block_headers().first()).unwrap();
221
222        let head = Tipset::from(CachingBlockHeader::new(RawBlockHeader {
223            parents: ts1.key().clone(),
224            ticket: dummy_ticket(2),
225            epoch: 2,
226            state_root: root_b,
227            timestamp: 2,
228            ..Default::default()
229        }));
230        db.put_cbor_default(head.block_headers().first()).unwrap();
231
232        let cs = ChainStore::new(db, cfg, genesis.block_headers().first().clone()).unwrap();
233        let sm = StateManager::new(cs).unwrap();
234        (sm, head, bls_a, bls_b)
235    }
236
237    #[tokio::test]
238    async fn caches_resolution_witnessed_at_finality_lookback() {
239        let (sm, head, bls_a, bls_b) = setup_with_finality(1);
240        let resolved = sm
241            .resolve_to_deterministic_address(Address::new_id(OLD_ACTOR), &head)
242            .await
243            .unwrap();
244        assert_eq!(resolved, bls_a);
245        let cache = sm.id_to_deterministic_address_cache().unwrap();
246        assert_eq!(cache.get(&OLD_ACTOR), Some(bls_a));
247
248        // Prove subsequent calls are served from the cache: poison the entry
249        // and observe the poisoned value coming back.
250        cache.insert(OLD_ACTOR, bls_b);
251        let resolved = sm
252            .resolve_to_deterministic_address(Address::new_id(OLD_ACTOR), &head)
253            .await
254            .unwrap();
255        assert_eq!(resolved, bls_b);
256    }
257
258    #[tokio::test]
259    async fn does_not_cache_actor_younger_than_finality() {
260        let (sm, head, _bls_a, bls_b) = setup_with_finality(1);
261        // f0400 is absent at the lookback: resolvable at the head, uncached.
262        let resolved = sm
263            .resolve_to_deterministic_address(Address::new_id(YOUNG_ACTOR), &head)
264            .await
265            .unwrap();
266        assert_eq!(resolved, bls_b);
267        let cache = sm.id_to_deterministic_address_cache().unwrap();
268        assert_eq!(cache.get(&YOUNG_ACTOR), None);
269    }
270
271    #[tokio::test]
272    async fn does_not_cache_on_chain_younger_than_finality() {
273        // Finality deeper than the whole chain: the lookback would degrade to
274        // resolving at `ts` itself, which is not reorg-stable — never cache.
275        let (sm, head, _bls_a, bls_b) = setup_with_finality(900);
276        let resolved = sm
277            .resolve_to_deterministic_address(Address::new_id(YOUNG_ACTOR), &head)
278            .await
279            .unwrap();
280        assert_eq!(resolved, bls_b);
281        assert_eq!(
282            sm.id_to_deterministic_address_cache().unwrap().len(),
283            0,
284            "nothing may be cached without a finality-deep witness"
285        );
286    }
287
288    #[tokio::test]
289    async fn does_not_cache_at_exact_finality_boundary() {
290        // Head epoch == chain_finality: the guard is strictly `>`, so this is
291        // not finality-deep and the lookback degrades to resolving at the
292        // head's own parent state (which does contain f0300, since `root_b`
293        // was built on top of `root_a`). The resolution succeeds but, being
294        // `Unstable`, must not be cached.
295        let (sm, head, bls_a, _bls_b) = setup_with_finality(2);
296        let resolved = sm
297            .resolve_to_deterministic_address(Address::new_id(OLD_ACTOR), &head)
298            .await
299            .unwrap();
300        assert_eq!(resolved, bls_a);
301        assert_eq!(
302            sm.id_to_deterministic_address_cache().unwrap().len(),
303            0,
304            "epoch == chain_finality is not finality-deep and must not be cached"
305        );
306    }
307
308    #[tokio::test]
309    async fn present_non_account_actor_errors_without_caching() {
310        let (sm, head, _bls_a, _bls_b) = setup_with_finality(1);
311        let resolved = sm
312            .resolve_to_deterministic_address(Address::new_id(NON_ACCOUNT_ACTOR), &head)
313            .await;
314        assert!(
315            resolved.is_err(),
316            "an actor that is present but is not an account has no key address"
317        );
318        assert_eq!(
319            sm.id_to_deterministic_address_cache().unwrap().len(),
320            0,
321            "an unresolvable actor must not be cached"
322        );
323    }
324
325    #[tokio::test]
326    async fn non_id_addresses_bypass_cache_and_lookback() {
327        let (sm, head, bls_a, _bls_b) = setup_with_finality(1);
328        let resolved = sm
329            .resolve_to_deterministic_address(bls_a, &head)
330            .await
331            .unwrap();
332        assert_eq!(resolved, bls_a);
333        assert_eq!(sm.id_to_deterministic_address_cache().unwrap().len(), 0);
334    }
335}