1use 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 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}