forest/state_migration/nv21/
miner.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4//! This module contains the migration logic for the `NV21` upgrade for the
5//! Miner actor.
6
7use crate::shim::econ::TokenAmount;
8use crate::state_migration::common::{
9    ActorMigration, ActorMigrationInput, ActorMigrationOutput, TypeMigration, TypeMigrator,
10};
11use crate::{
12    shim::address::Address, state_migration::common::MigrationCache, utils::db::CborStoreExt,
13};
14use anyhow::Context as _;
15use cid::{Cid, multibase::Base};
16use fil_actor_miner_state::{
17    v11::Deadline as DeadlineOld, v11::Deadlines as DeadlinesOld, v11::State as MinerStateOld,
18    v12::Deadline as DeadlineNew, v12::Deadlines as DeadlinesNew, v12::State as MinerStateNew,
19};
20use fil_actors_shared::fvm_ipld_amt;
21use fil_actors_shared::v11::{Array as ArrayOld, runtime::Policy as PolicyOld};
22use fil_actors_shared::v12::{Array as ArrayNew, runtime::Policy as PolicyNew};
23use fvm_ipld_blockstore::Blockstore;
24use std::sync::Arc;
25
26pub struct MinerMigrator {
27    empty_deadline_v11: Cid,
28    empty_deadlines_v11: Cid,
29    empty_deadline_v12: Cid,
30    empty_deadlines_v12: Cid,
31    policy_new: PolicyNew,
32    out_cid: Cid,
33}
34
35pub(in crate::state_migration) fn miner_migrator<BS: Blockstore>(
36    policy_old: &PolicyOld,
37    policy_new: &PolicyNew,
38    store: &Arc<BS>,
39    out_cid: Cid,
40) -> anyhow::Result<Arc<dyn ActorMigration<BS> + Send + Sync>> {
41    let empty_deadline_v11 = DeadlineOld::new(store)?;
42    let empty_deadline_v11 = store.put_cbor_default(&empty_deadline_v11)?;
43
44    let empty_deadlines_v11 = DeadlinesOld::new(policy_old, empty_deadline_v11);
45    let empty_deadlines_v11 = store.put_cbor_default(&empty_deadlines_v11)?;
46
47    let empty_deadline_v12 = DeadlineNew::new(store)?;
48    let empty_deadline_v12 = store.put_cbor_default(&empty_deadline_v12)?;
49
50    let empty_deadlines_v12 = DeadlinesNew::new(policy_new, empty_deadline_v12);
51    let empty_deadlines_v12 = store.put_cbor_default(&empty_deadlines_v12)?;
52
53    Ok(Arc::new(MinerMigrator {
54        empty_deadline_v11,
55        empty_deadlines_v11,
56        empty_deadline_v12,
57        empty_deadlines_v12,
58        policy_new: policy_new.clone(),
59        out_cid,
60    }))
61}
62
63impl<BS: Blockstore> ActorMigration<BS> for MinerMigrator {
64    fn migrate_state(
65        &self,
66        store: &BS,
67        input: ActorMigrationInput,
68    ) -> anyhow::Result<Option<ActorMigrationOutput>> {
69        let in_state: MinerStateOld = store.get_cbor_required(&input.head)?;
70
71        let new_sectors = self.migrate_sectors_with_cache(
72            store,
73            &input.cache,
74            &input.address,
75            &in_state.sectors,
76        )?;
77
78        let new_deadlines =
79            self.migrate_deadlines(store, &input.cache, &input.address, &in_state.deadlines)?;
80
81        let out_state = MinerStateNew {
82            info: in_state.info,
83            pre_commit_deposits: TokenAmount::from(in_state.pre_commit_deposits).into(),
84            locked_funds: TokenAmount::from(in_state.locked_funds).into(),
85            vesting_funds: in_state.vesting_funds,
86            fee_debt: TokenAmount::from(in_state.fee_debt).into(),
87            initial_pledge: TokenAmount::from(in_state.initial_pledge).into(),
88            pre_committed_sectors: in_state.pre_committed_sectors,
89            pre_committed_sectors_cleanup: in_state.pre_committed_sectors_cleanup,
90            allocated_sectors: in_state.allocated_sectors,
91            sectors: new_sectors,
92            proving_period_start: in_state.proving_period_start,
93            current_deadline: in_state.current_deadline,
94            deadlines: new_deadlines,
95            early_terminations: in_state.early_terminations,
96            deadline_cron_active: in_state.deadline_cron_active,
97        };
98        let new_head = store.put_cbor_default(&out_state)?;
99
100        Ok(Some(ActorMigrationOutput {
101            new_code_cid: self.out_cid,
102            new_head,
103        }))
104    }
105}
106
107impl MinerMigrator {
108    fn migrate_sectors_with_cache<BS: Blockstore>(
109        &self,
110        store: &BS,
111        cache: &MigrationCache,
112        address: &Address,
113        in_root: &Cid,
114    ) -> anyhow::Result<Cid> {
115        cache
116            .get_or_insert_with(sectors_amt_key(in_root)?, || -> anyhow::Result<Cid> {
117                let prev_in = cache.get(&miner_prev_sectors_in_key(address));
118                let prev_out = cache.get(&miner_prev_sectors_out_key(address));
119
120                let out_root = if let Some(prev_in) = prev_in
121                    && let Some(prev_out) = prev_out
122                {
123                    self.migrate_sectors_with_diff(store, in_root, &prev_in, &prev_out)?
124                } else {
125                    let in_array = ArrayOld::load(in_root, store)?;
126                    let mut out_array = self.migrate_sectors_from_scratch(store, &in_array)?;
127                    out_array.flush()?
128                };
129
130                cache.push(miner_prev_sectors_in_key(address), in_root.to_owned());
131                cache.push(miner_prev_sectors_out_key(address), out_root.to_owned());
132                Ok(out_root)
133            })
134            .map(|cid| cid.to_owned())
135    }
136
137    fn migrate_sectors_with_diff<BS: Blockstore>(
138        &self,
139        store: &BS,
140        in_root: &Cid,
141        prev_in: &Cid,
142        prev_out: &Cid,
143    ) -> anyhow::Result<Cid> {
144        let prev_in_sectors =
145            ArrayOld::<fil_actor_miner_state::v11::SectorOnChainInfo, BS>::load(prev_in, store)?;
146        let in_sectors =
147            ArrayOld::<fil_actor_miner_state::v11::SectorOnChainInfo, BS>::load(in_root, store)?;
148
149        let diffs = fvm_ipld_amt::diff(&prev_in_sectors, &in_sectors)?;
150
151        let mut prev_out_sectors =
152            ArrayNew::<fil_actor_miner_state::v12::SectorOnChainInfo, BS>::load(prev_out, store)?;
153
154        for diff in diffs {
155            use fvm_ipld_amt::ChangeType;
156            match &diff.change_type() {
157                ChangeType::Remove => {
158                    prev_out_sectors.delete(diff.key)?;
159                }
160                ChangeType::Modify | ChangeType::Add => {
161                    let info = in_sectors
162                        .get(diff.key)?
163                        .context("Failed to get info from in_sectors")?;
164                    prev_out_sectors.set(diff.key, TypeMigrator::migrate_type(info, store)?)?;
165                }
166            };
167        }
168
169        Ok(prev_out_sectors.flush()?)
170    }
171
172    fn migrate_sectors_from_scratch<'bs, BS: Blockstore>(
173        &self,
174        store: &'bs BS,
175        in_array: &ArrayOld<fil_actor_miner_state::v11::SectorOnChainInfo, BS>,
176    ) -> anyhow::Result<ArrayNew<'bs, fil_actor_miner_state::v12::SectorOnChainInfo, BS>> {
177        use fil_actor_miner_state::v12::SECTORS_AMT_BITWIDTH;
178
179        let mut out_array =
180            ArrayNew::<fil_actor_miner_state::v12::SectorOnChainInfo, _>::new_with_bit_width(
181                store,
182                SECTORS_AMT_BITWIDTH,
183            );
184
185        in_array.for_each(|key, info_v11| {
186            out_array.set(key, TypeMigrator::migrate_type(info_v11, store)?)?;
187            Ok(())
188        })?;
189
190        Ok(out_array)
191    }
192
193    fn migrate_deadlines<BS: Blockstore>(
194        &self,
195        store: &BS,
196        cache: &MigrationCache,
197        address: &Address,
198        deadlines: &Cid,
199    ) -> anyhow::Result<Cid> {
200        if deadlines == &self.empty_deadlines_v11 {
201            return Ok(self.empty_deadlines_v12);
202        }
203
204        let in_deadlines = store.get_cbor_required::<DeadlinesOld>(deadlines)?;
205        let mut out_deadlines = DeadlinesNew::new(&self.policy_new, self.empty_deadline_v12);
206
207        for (i, deadline) in in_deadlines.due.iter().enumerate() {
208            if deadline == &self.empty_deadline_v11 {
209                if let Some(due_i) = out_deadlines.due.get_mut(i) {
210                    *due_i = self.empty_deadline_v12;
211                } else {
212                    out_deadlines.due.push(self.empty_deadline_v12);
213                }
214            } else {
215                let in_deadline = store.get_cbor_required::<DeadlineOld>(deadline)?;
216
217                let out_sectors_snapshot_cid_cache_key =
218                    sectors_amt_key(&in_deadline.sectors_snapshot)?;
219
220                let out_sectors_snapshot_cid = cache.get_or_insert_with(
221                    out_sectors_snapshot_cid_cache_key,
222                    || -> anyhow::Result<Cid> {
223                        let prev_in_root = cache.get(&miner_prev_sectors_in_key(address));
224                        let prev_out_root = cache.get(&miner_prev_sectors_out_key(address));
225
226                        if let Some(prev_in_root) = prev_in_root
227                            && let Some(prev_out_root) = prev_out_root
228                        {
229                            self.migrate_sectors_with_diff(
230                                store,
231                                &in_deadline.sectors_snapshot,
232                                &prev_in_root,
233                                &prev_out_root,
234                            )
235                        } else {
236                            let in_sector_snapshot =
237                                ArrayOld::load(&in_deadline.sectors_snapshot, store)?;
238
239                            let mut out_snapshot =
240                                self.migrate_sectors_from_scratch(store, &in_sector_snapshot)?;
241
242                            Ok(out_snapshot.flush()?)
243                        }
244                    },
245                )?;
246
247                let out_deadline = DeadlineNew {
248                    partitions: in_deadline.partitions,
249                    expirations_epochs: in_deadline.expirations_epochs,
250                    partitions_posted: in_deadline.partitions_posted,
251                    early_terminations: in_deadline.early_terminations,
252                    live_sectors: in_deadline.live_sectors,
253                    total_sectors: in_deadline.total_sectors,
254                    faulty_power: TypeMigrator::migrate_type(in_deadline.faulty_power, store)?,
255                    optimistic_post_submissions: in_deadline.optimistic_post_submissions,
256                    sectors_snapshot: out_sectors_snapshot_cid,
257                    partitions_snapshot: in_deadline.partitions_snapshot,
258                    optimistic_post_submissions_snapshot: in_deadline
259                        .optimistic_post_submissions_snapshot,
260                };
261
262                let out_deadline = store.put_cbor_default(&out_deadline)?;
263                if let Some(due_i) = out_deadlines.due.get_mut(i) {
264                    *due_i = out_deadline;
265                } else {
266                    out_deadlines.due.push(out_deadline);
267                }
268            }
269        }
270        store.put_cbor_default(&out_deadlines)
271    }
272}
273
274fn sectors_amt_key(sectors: &Cid) -> anyhow::Result<String> {
275    let key = sectors.to_string_of_base(Base::Base32Lower)?;
276    Ok(format!("sectorsAmt-{key}"))
277}
278
279fn miner_prev_sectors_in_key(address: &Address) -> String {
280    format!("prevSectorsIn-{address}")
281}
282
283fn miner_prev_sectors_out_key(address: &Address) -> String {
284    format!("prevSectorsOut-{address}")
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290    use crate::make_calibnet_policy;
291    use crate::networks::{ChainConfig, Height};
292    use crate::shim::actors::*;
293    use crate::shim::{
294        econ::TokenAmount,
295        machine::{BuiltinActor, BuiltinActorManifest},
296        state_tree::{ActorState, StateTree, StateTreeVersion},
297    };
298    use crate::utils::multihash::prelude::*;
299    use fvm_ipld_encoding::IPLD_RAW;
300    use fvm_shared2::bigint::Zero;
301
302    #[test]
303    fn test_nv21_miner_migration() {
304        let store = Arc::new(crate::db::MemoryDB::default());
305        let (mut state_tree_old, manifest_old) = make_input_tree(&store);
306        let system_actor_old = state_tree_old
307            .get_required_actor(&system::ADDRESS.into())
308            .unwrap();
309        let system_state_old: fil_actor_system_state::v11::State =
310            store.get_cbor_required(&system_actor_old.state).unwrap();
311        let manifest_data_cid_old = system_state_old.builtin_actors;
312        assert_eq!(manifest_data_cid_old, manifest_old.source_cid());
313
314        let addr_id = 10000;
315        let addr = Address::new_id(addr_id);
316        let worker_id = addr_id + 100;
317
318        // base stuff to create miners
319        let miner_cid_old = manifest_old.get(BuiltinActor::Miner).unwrap();
320        let mut miner_state1 = make_base_miner_state(&store, addr_id, worker_id);
321        let mut deadline = DeadlineOld::new(&store).unwrap();
322        let mut sectors_snapshot =
323            ArrayOld::<fil_actor_miner_state::v11::SectorOnChainInfo, _>::new_with_bit_width(
324                &store,
325                fil_actor_miner_state::v11::SECTORS_AMT_BITWIDTH,
326            );
327        sectors_snapshot
328            .set(
329                0,
330                fil_actor_miner_state::v11::SectorOnChainInfo {
331                    simple_qa_power: true,
332                    ..Default::default()
333                },
334            )
335            .unwrap();
336        sectors_snapshot
337            .set(
338                1,
339                fil_actor_miner_state::v11::SectorOnChainInfo {
340                    simple_qa_power: false,
341                    ..Default::default()
342                },
343            )
344            .unwrap();
345        deadline.sectors_snapshot = sectors_snapshot.flush().unwrap();
346        let deadline_cid = store.put_cbor_default(&deadline).unwrap();
347        let deadlines = DeadlinesOld::new(&make_calibnet_policy!(v11), deadline_cid);
348        miner_state1.deadlines = store.put_cbor_default(&deadlines).unwrap();
349
350        let miner1_state_cid = store.put_cbor_default(&miner_state1).unwrap();
351        let miner1 = ActorState::new(miner_cid_old, miner1_state_cid, Zero::zero(), 0, None);
352        state_tree_old.set_actor(&addr, miner1).unwrap();
353        let tree_root = state_tree_old.flush().unwrap();
354
355        let (new_manifest_cid, _new_manifest) = make_test_manifest(&store, "fil/12/");
356
357        let mut chain_config = ChainConfig::devnet();
358        if let Some(entry) = chain_config.height_infos.get_mut(&Height::Watermelon) {
359            entry.bundle = Some(new_manifest_cid);
360        }
361        let new_state_cid =
362            super::super::run_migration(&chain_config, &store, &tree_root, 200).unwrap();
363
364        let new_state_cid2 =
365            super::super::run_migration(&chain_config, &store, &tree_root, 200).unwrap();
366
367        assert_eq!(new_state_cid, new_state_cid2);
368
369        let new_state_tree = StateTree::new_from_root(store.clone(), &new_state_cid).unwrap();
370        let new_miner_state_cid = new_state_tree.get_required_actor(&addr).unwrap().state;
371        let new_miner_state: fil_actor_miner_state::v12::State =
372            store.get_cbor_required(&new_miner_state_cid).unwrap();
373        let deadlines: fil_actor_miner_state::v12::Deadlines =
374            store.get_cbor_required(&new_miner_state.deadlines).unwrap();
375        deadlines
376            .for_each(&store, |_, deadline| {
377                let sectors_snapshots =
378                    ArrayNew::<fil_actor_miner_state::v12::SectorOnChainInfo, _>::load(
379                        &deadline.sectors_snapshot,
380                        &store,
381                    )
382                    .unwrap();
383                assert_eq!(
384                    sectors_snapshots.get(0).unwrap().unwrap().flags,
385                    fil_actor_miner_state::v12::SectorOnChainInfoFlags::SIMPLE_QA_POWER
386                );
387                assert!(
388                    !sectors_snapshots.get(1).unwrap().unwrap().flags.contains(
389                        fil_actor_miner_state::v12::SectorOnChainInfoFlags::SIMPLE_QA_POWER
390                    )
391                );
392                Ok(())
393            })
394            .unwrap();
395    }
396
397    fn make_input_tree<BS: Blockstore>(store: &Arc<BS>) -> (StateTree<BS>, BuiltinActorManifest) {
398        let mut tree = StateTree::new(store.clone(), StateTreeVersion::V5).unwrap();
399
400        let (_manifest_cid, manifest) = make_test_manifest(&store, "fil/11/");
401        let system_cid = manifest.get_system();
402        let system_state = fil_actor_system_state::v11::State {
403            builtin_actors: manifest.source_cid(),
404        };
405        let system_state_cid = store.put_cbor_default(&system_state).unwrap();
406        init_actor(
407            &mut tree,
408            system_state_cid,
409            system_cid,
410            &system::ADDRESS.into(),
411            Zero::zero(),
412        );
413
414        let init_cid = manifest.get_init();
415        let init_state =
416            fil_actor_init_state::v11::State::new(&store, "migrationtest".into()).unwrap();
417        let init_state_cid = store.put_cbor_default(&init_state).unwrap();
418        init_actor(
419            &mut tree,
420            init_state_cid,
421            init_cid,
422            &init::ADDRESS.into(),
423            Zero::zero(),
424        );
425
426        tree.flush().unwrap();
427
428        (tree, manifest)
429    }
430
431    fn init_actor<BS: Blockstore>(
432        tree: &mut StateTree<BS>,
433        state: Cid,
434        code: Cid,
435        addr: &Address,
436        balance: TokenAmount,
437    ) {
438        let actor = ActorState::new(code, state, balance, 0, None);
439        tree.set_actor(addr, actor).unwrap();
440    }
441
442    fn make_test_manifest<BS: Blockstore>(store: &BS, prefix: &str) -> (Cid, BuiltinActorManifest) {
443        let mut manifest_data = vec![];
444        for name in [
445            "account",
446            "cron",
447            "init",
448            "storagemarket",
449            "storageminer",
450            "multisig",
451            "paymentchannel",
452            "storagepower",
453            "reward",
454            "system",
455            "verifiedregistry",
456            "datacap",
457        ] {
458            let hash = MultihashCode::Identity.digest(format!("{prefix}{name}").as_bytes());
459            let code_cid = Cid::new_v1(IPLD_RAW, hash);
460            manifest_data.push((name, code_cid));
461        }
462
463        let manifest_cid = store
464            .put_cbor_default(&(1, store.put_cbor_default(&manifest_data).unwrap()))
465            .unwrap();
466        let manifest = BuiltinActorManifest::load_manifest(store, &manifest_cid).unwrap();
467
468        (manifest_cid, manifest)
469    }
470
471    fn make_base_miner_state<BS: Blockstore>(
472        store: &BS,
473        owner: fvm_shared3::ActorID,
474        worker: fvm_shared3::ActorID,
475    ) -> fil_actor_miner_state::v11::State {
476        let control_addresses = vec![];
477        let peer_id = vec![];
478        let multi_address = vec![];
479        let window_post_proof_type =
480            fvm_shared3::sector::RegisteredPoStProof::StackedDRGWindow2KiBV1;
481        let miner_info = fil_actor_miner_state::v11::MinerInfo::new(
482            owner,
483            worker,
484            control_addresses,
485            peer_id,
486            multi_address,
487            window_post_proof_type,
488        )
489        .unwrap();
490
491        let miner_info_cid = store.put_cbor_default(&miner_info).unwrap();
492
493        fil_actor_miner_state::v11::State::new(
494            &make_calibnet_policy!(v11),
495            store,
496            miner_info_cid,
497            0,
498            0,
499        )
500        .unwrap()
501    }
502}