forest/state_migration/common/
mod.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4//! Common code that's shared across all migration code.
5//! Each network upgrade / state migration code lives in their own module.
6
7use std::{num::NonZeroUsize, sync::Arc};
8
9use crate::{
10    shim::{address::Address, clock::ChainEpoch, econ::TokenAmount, state_tree::StateTree},
11    utils::{cache::SizeTrackingLruCache, get_size::CidWrapper},
12};
13use cid::Cid;
14use fvm_ipld_blockstore::Blockstore;
15
16mod macros;
17mod migration_job;
18pub(in crate::state_migration) mod migrators;
19mod state_migration;
20pub(in crate::state_migration) mod verifier;
21
22pub(in crate::state_migration) use state_migration::StateMigration;
23pub(in crate::state_migration) type Migrator<BS> = Arc<dyn ActorMigration<BS> + Send + Sync>;
24
25/// Cache of existing CID to CID migrations for an actor.
26#[derive(Clone)]
27pub(in crate::state_migration) struct MigrationCache {
28    cache: Arc<SizeTrackingLruCache<String, CidWrapper>>,
29}
30
31impl MigrationCache {
32    pub fn new(size: NonZeroUsize) -> Self {
33        Self {
34            cache: Arc::new(SizeTrackingLruCache::new_with_metrics(
35                "migration".into(),
36                size,
37            )),
38        }
39    }
40
41    pub fn get(&self, key: &str) -> Option<Cid> {
42        self.cache.get_cloned(key).map(From::from)
43    }
44
45    pub fn get_or_insert_with<F>(&self, key: String, f: F) -> anyhow::Result<Cid>
46    where
47        F: FnOnce() -> anyhow::Result<Cid>,
48    {
49        if let Some(v) = self.cache.get_cloned(&key) {
50            Ok(v.into())
51        } else {
52            let v = f()?;
53            self.push(key, v);
54            Ok(v)
55        }
56    }
57
58    pub fn push(&self, key: String, value: Cid) {
59        self.cache.push(key, value.into());
60    }
61}
62
63#[allow(dead_code)] // future migrations might need the fields.
64pub(in crate::state_migration) struct ActorMigrationInput {
65    /// Actor's address
66    pub address: Address,
67    /// Actor's balance
68    pub balance: TokenAmount,
69    /// Actor's state head CID
70    pub head: Cid,
71    /// Epoch of last state transition prior to migration
72    pub prior_epoch: ChainEpoch,
73    /// Cache of existing CID to CID migrations for this actor
74    pub cache: MigrationCache,
75}
76
77/// Output of actor migration job.
78pub(in crate::state_migration) struct ActorMigrationOutput {
79    /// New CID for the actor
80    pub new_code_cid: Cid,
81    /// New state head CID
82    pub new_head: Cid,
83}
84
85/// Trait that defines the interface for actor migration job.
86pub(in crate::state_migration) trait ActorMigration<BS: Blockstore> {
87    fn migrate_state(
88        &self,
89        store: &BS,
90        input: ActorMigrationInput,
91    ) -> anyhow::Result<Option<ActorMigrationOutput>>;
92
93    /// Some migration jobs might need to be deferred to be executed after the regular state migration.
94    /// These may require some metadata collected during other migrations.
95    fn is_deferred(&self) -> bool {
96        false
97    }
98}
99
100/// Trait that defines the interface for actor migration job to be executed after the state migration.
101pub(in crate::state_migration) trait PostMigrator<BS: Blockstore>:
102    Send + Sync
103{
104    fn post_migrate_state(&self, store: &BS, actors_out: &mut StateTree<BS>) -> anyhow::Result<()>;
105}
106
107/// Trait defining the interface for actor migration verifier.
108pub(in crate::state_migration) trait PostMigrationCheck<BS: Blockstore>:
109    Send + Sync
110{
111    fn post_migrate_check(&self, store: &BS, actors_out: &StateTree<BS>) -> anyhow::Result<()>;
112}
113
114/// Sized wrapper of [`PostMigrator`].
115pub(in crate::state_migration) type PostMigratorArc<BS> = Arc<dyn PostMigrator<BS>>;
116
117/// Sized wrapper of [`PostMigrationCheck`].
118pub(in crate::state_migration) type PostMigrationCheckArc<BS> = Arc<dyn PostMigrationCheck<BS>>;
119
120/// Trait that migrates from one data structure to another, similar to
121/// [`std::convert::TryInto`] trait but taking an extra block store parameter
122pub(in crate::state_migration) trait TypeMigration<From, To> {
123    fn migrate_type(from: From, store: &impl Blockstore) -> anyhow::Result<To>;
124}
125
126/// Type that implements [`TypeMigration`] for different type pairs. Prefer
127/// using a single `struct` so that the compiler could catch duplicate
128/// implementations
129pub(in crate::state_migration) struct TypeMigrator;
130
131#[cfg(test)]
132mod tests {
133    use std::num::NonZeroUsize;
134
135    use super::MigrationCache;
136    use crate::utils::cid::CidCborExt;
137    use cid::Cid;
138
139    #[test]
140    fn test_migration_cache() {
141        let cache = MigrationCache::new(NonZeroUsize::new(10).unwrap());
142        let cid = Cid::from_cbor_blake2b256(&42).unwrap();
143        cache.push("Cthulhu".to_owned(), cid);
144        assert_eq!(cache.get("Cthulhu"), Some(cid));
145        assert_eq!(cache.get("Ao"), None);
146
147        let cid = Cid::from_cbor_blake2b256(&666).unwrap();
148        assert_eq!(cache.get("Azathoth"), None);
149
150        let value = cache
151            .get_or_insert_with("Azathoth".to_owned(), || Ok(cid))
152            .unwrap();
153        assert_eq!(value, cid);
154        assert_eq!(cache.get("Azathoth"), Some(cid));
155
156        // Tests that there is no deadlock when inserting a value while reading the cache.
157        let value = cache
158            .get_or_insert_with("Dagon".to_owned(), || Ok(cache.get("Azathoth").unwrap()))
159            .unwrap();
160        assert_eq!(value, cid);
161        assert_eq!(cache.get("Dagon"), Some(cid));
162    }
163}