1use crate::*;
2use fil_actor_cron::Method as CronMethod;
3use fil_actor_market::{
4 ClientDealProposal, DealProposal, Label, Method as MarketMethod, PublishStorageDealsParams,
5 PublishStorageDealsReturn,
6};
7use fil_actor_miner::{
8 aggregate_pre_commit_network_fee, max_prove_commit_duration,
9 new_deadline_info_from_offset_and_epoch, Deadline, DeadlineInfo, DeclareFaultsRecoveredParams,
10 Method as MinerMethod, PoStPartition, PowerPair, PreCommitSectorBatchParams,
11 ProveCommitAggregateParams, RecoveryDeclaration, SectorOnChainInfo, SectorPreCommitInfo,
12 SectorPreCommitOnChainInfo, State as MinerState, SubmitWindowedPoStParams,
13};
14use fil_actor_multisig::Method as MultisigMethod;
15use fil_actor_multisig::ProposeParams;
16use fil_actor_power::{
17 CreateMinerParams, CreateMinerReturn, Method as PowerMethod, UpdateClaimedPowerParams,
18};
19use fil_actor_reward::Method as RewardMethod;
20use fil_actor_verifreg::{Method as VerifregMethod, VerifierParams};
21use fvm_ipld_bitfield::{BitField, UnvalidatedBitField};
22use fvm_ipld_encoding::{BytesDe, Cbor, RawBytes};
23use fvm_shared::address::{Address, BLS_PUB_LEN};
24use fvm_shared::crypto::signature::{Signature, SignatureType};
25use fvm_shared::econ::TokenAmount;
26use fvm_shared::error::ExitCode;
27use fvm_shared::piece::PaddedPieceSize;
28use fvm_shared::sector::{PoStProof, RegisteredPoStProof, RegisteredSealProof, SectorNumber};
29use fvm_shared::{MethodNum, METHOD_SEND};
30use rand::prelude::*;
31use rand_chacha::ChaCha8Rng;
32use std::cmp::min;
33
34pub fn pk_addrs_from(seed: u64, count: u64) -> Vec<Address> {
36 let mut seed_arr = [0u8; 32];
37 for (i, b) in seed.to_ne_bytes().iter().enumerate() {
38 seed_arr[i] = *b;
39 }
40 let mut rng = ChaCha8Rng::from_seed(seed_arr);
41 (0..count).map(|_| new_bls_from_rng(&mut rng)).collect()
42}
43
44fn new_bls_from_rng(rng: &mut ChaCha8Rng) -> Address {
46 let mut bytes = [0u8; BLS_PUB_LEN];
47 rng.fill_bytes(&mut bytes);
48 Address::new_bls(&bytes).unwrap()
49}
50
51const ACCOUNT_SEED: u64 = 93837778;
52
53pub fn create_accounts(v: &VM, count: u64, balance: TokenAmount) -> Vec<Address> {
54 create_accounts_seeded(v, count, balance, ACCOUNT_SEED)
55}
56
57pub fn create_accounts_seeded(v: &VM, count: u64, balance: TokenAmount, seed: u64) -> Vec<Address> {
58 let pk_addrs = pk_addrs_from(seed, count);
59 for pk_addr in pk_addrs.clone() {
61 apply_ok(v, TEST_FAUCET_ADDR, pk_addr, balance.clone(), METHOD_SEND, RawBytes::default());
62 }
63 pk_addrs.iter().map(|&pk_addr| v.normalize_address(&pk_addr).unwrap()).collect()
65}
66
67pub fn apply_ok<C: Cbor>(
68 v: &VM,
69 from: Address,
70 to: Address,
71 value: TokenAmount,
72 method: MethodNum,
73 params: C,
74) -> RawBytes {
75 apply_code(v, from, to, value, method, params, ExitCode::OK)
76}
77
78pub fn apply_code<C: Cbor>(
79 v: &VM,
80 from: Address,
81 to: Address,
82 value: TokenAmount,
83 method: MethodNum,
84 params: C,
85 code: ExitCode,
86) -> RawBytes {
87 let res = v.apply_message(from, to, value, method, params).unwrap();
88 assert_eq!(code, res.code);
89 res.ret
90}
91
92pub fn create_miner(
93 v: &mut VM,
94 owner: Address,
95 worker: Address,
96 post_proof_type: RegisteredPoStProof,
97 balance: TokenAmount,
98) -> (Address, Address) {
99 let multiaddrs = vec![BytesDe("multiaddr".as_bytes().to_vec())];
100 let peer_id = "miner".as_bytes().to_vec();
101 let params = CreateMinerParams {
102 owner,
103 worker,
104 window_post_proof_type: post_proof_type,
105 peer: peer_id,
106 multiaddrs,
107 };
108
109 let res: CreateMinerReturn = v
110 .apply_message(
111 owner,
112 *STORAGE_POWER_ACTOR_ADDR,
113 balance,
114 PowerMethod::CreateMiner as u64,
115 params,
116 )
117 .unwrap()
118 .ret
119 .deserialize()
120 .unwrap();
121 (res.id_address, res.robust_address)
122}
123
124#[allow(clippy::too_many_arguments)]
125pub fn precommit_sectors(
126 v: &mut VM,
127 count: u64,
128 batch_size: i64,
129 worker: Address,
130 maddr: Address,
131 seal_proof: RegisteredSealProof,
132 sector_number_base: SectorNumber,
133 expect_cron_enroll: bool,
134 exp: Option<ChainEpoch>,
135) -> Vec<SectorPreCommitOnChainInfo> {
136 let mid = v.normalize_address(&maddr).unwrap();
137 let invocs_common = || -> Vec<ExpectInvocation> {
138 vec![
139 ExpectInvocation {
140 to: *REWARD_ACTOR_ADDR,
141 method: RewardMethod::ThisEpochReward as u64,
142 ..Default::default()
143 },
144 ExpectInvocation {
145 to: *STORAGE_POWER_ACTOR_ADDR,
146 method: PowerMethod::CurrentTotalPower as u64,
147 ..Default::default()
148 },
149 ]
150 };
151 let invoc_first = || -> ExpectInvocation {
152 ExpectInvocation {
153 to: *STORAGE_POWER_ACTOR_ADDR,
154 method: PowerMethod::EnrollCronEvent as u64,
155 ..Default::default()
156 }
157 };
158 let invoc_net_fee = |fee: TokenAmount| -> ExpectInvocation {
159 ExpectInvocation {
160 to: *BURNT_FUNDS_ACTOR_ADDR,
161 method: METHOD_SEND,
162 value: Some(fee),
163 ..Default::default()
164 }
165 };
166 let expiration = match exp {
167 None => {
168 v.get_epoch()
169 + Policy::default().min_sector_expiration
170 + max_prove_commit_duration(&Policy::default(), seal_proof).unwrap()
171 }
172 Some(e) => e,
173 };
174
175 let mut sector_idx = 0u64;
176 while sector_idx < count {
177 let msg_sector_idx_base = sector_idx;
178 let mut invocs = invocs_common();
179
180 let mut param_sectors = Vec::<SectorPreCommitInfo>::new();
181 let mut j = 0;
182 while j < batch_size && sector_idx < count {
183 let sector_number = sector_number_base + sector_idx;
184 param_sectors.push(SectorPreCommitInfo {
185 seal_proof,
186 sector_number,
187 sealed_cid: make_sealed_cid(format!("sn: {}", sector_number).as_bytes()),
188 seal_rand_epoch: v.get_epoch() - 1,
189 deal_ids: vec![],
190 expiration,
191 ..Default::default()
192 });
193 sector_idx += 1;
194 j += 1;
195 }
196 if param_sectors.len() > 1 {
197 invocs.push(invoc_net_fee(aggregate_pre_commit_network_fee(
198 param_sectors.len() as i64,
199 &TokenAmount::zero(),
200 )));
201 }
202 if expect_cron_enroll && msg_sector_idx_base == 0 {
203 invocs.push(invoc_first());
204 }
205 apply_ok(
206 v,
207 worker,
208 maddr,
209 TokenAmount::zero(),
210 MinerMethod::PreCommitSectorBatch as u64,
211 PreCommitSectorBatchParams { sectors: param_sectors.clone() },
212 );
213 let expect = ExpectInvocation {
214 to: mid,
215 method: MinerMethod::PreCommitSectorBatch as u64,
216 params: Some(
217 serialize(
218 &PreCommitSectorBatchParams { sectors: param_sectors },
219 "precommit batch params",
220 )
221 .unwrap(),
222 ),
223 subinvocs: Some(invocs),
224 ..Default::default()
225 };
226 expect.matches(v.take_invocations().last().unwrap())
227 }
228 let mstate = v.get_state::<MinerState>(mid).unwrap();
230 (0..count)
231 .map(|i| mstate.get_precommitted_sector(v.store, sector_number_base + i).unwrap().unwrap())
232 .collect()
233}
234
235pub fn prove_commit_sectors(
236 v: &mut VM,
237 worker: Address,
238 maddr: Address,
239 precommits: Vec<SectorPreCommitOnChainInfo>,
240 aggregate_size: i64,
241) {
242 let mut precommit_infos = precommits.as_slice();
243 while !precommit_infos.is_empty() {
244 let batch_size = min(aggregate_size, precommit_infos.len() as i64) as usize;
245 let to_prove = &precommit_infos[0..batch_size];
246 precommit_infos = &precommit_infos[batch_size..];
247 let b: Vec<u64> = to_prove.iter().map(|p| p.info.sector_number).collect();
248
249 let prove_commit_aggregate_params = ProveCommitAggregateParams {
250 sector_numbers: make_bitfield(b.as_slice()),
251 aggregate_proof: vec![],
252 };
253 let prove_commit_aggregate_params_ser =
254 serialize(&prove_commit_aggregate_params, "prove commit aggregate params").unwrap();
255
256 apply_ok(
257 v,
258 worker,
259 maddr,
260 TokenAmount::zero(),
261 MinerMethod::ProveCommitAggregate as u64,
262 prove_commit_aggregate_params,
263 );
264
265 ExpectInvocation {
266 to: maddr,
267 method: MinerMethod::ProveCommitAggregate as u64,
268 from: Some(worker),
269 params: Some(prove_commit_aggregate_params_ser),
270 subinvocs: Some(vec![
271 ExpectInvocation {
272 to: *STORAGE_MARKET_ACTOR_ADDR,
273 method: MarketMethod::ComputeDataCommitment as u64,
274 ..Default::default()
275 },
276 ExpectInvocation {
277 to: *REWARD_ACTOR_ADDR,
278 method: RewardMethod::ThisEpochReward as u64,
279 ..Default::default()
280 },
281 ExpectInvocation {
282 to: *STORAGE_POWER_ACTOR_ADDR,
283 method: PowerMethod::CurrentTotalPower as u64,
284 ..Default::default()
285 },
286 ExpectInvocation {
287 to: *STORAGE_POWER_ACTOR_ADDR,
288 method: PowerMethod::UpdatePledgeTotal as u64,
289 ..Default::default()
290 },
291 ExpectInvocation {
292 to: *BURNT_FUNDS_ACTOR_ADDR,
293 method: METHOD_SEND,
294 ..Default::default()
295 },
296 ]),
297 ..Default::default()
298 }
299 .matches(v.take_invocations().last().unwrap());
300 }
301}
302
303pub fn advance_by_deadline_to_epoch(v: VM, maddr: Address, e: ChainEpoch) -> (VM, DeadlineInfo) {
304 let (v, dline_info) = advance_by_deadline(v, maddr, |dline_info| dline_info.close < e);
307 (v.with_epoch(e), dline_info)
308}
309
310pub fn advance_by_deadline_to_index(v: VM, maddr: Address, i: u64) -> (VM, DeadlineInfo) {
311 advance_by_deadline(v, maddr, |dline_info| dline_info.index != i)
312}
313
314pub fn advance_by_deadline_to_epoch_while_proving(
315 mut v: VM,
316 maddr: Address,
317 worker: Address,
318 s: SectorNumber,
319 e: ChainEpoch,
320) -> VM {
321 let mut dline_info;
322 let (d, p_idx) = sector_deadline(&v, maddr, s);
323 loop {
324 (v, dline_info) = advance_by_deadline(v, maddr, |dline_info| {
326 dline_info.index != d && dline_info.close < e
327 });
328 if dline_info.close > e {
329 return v.with_epoch(e);
331 }
332 submit_windowed_post(&v, worker, maddr, dline_info, p_idx, PowerPair::zero());
333 v = advance_by_deadline_to_index(
334 v,
335 maddr,
336 d + 1 % &Policy::default().wpost_period_deadlines,
337 )
338 .0
339 }
340}
341
342pub fn advance_to_proving_deadline(
343 v: VM,
344 maddr: Address,
345 s: SectorNumber,
346) -> (DeadlineInfo, u64, VM) {
347 let (d, p) = sector_deadline(&v, maddr, s);
348 let (v, dline_info) = advance_by_deadline_to_index(v, maddr, d);
349 let v = v.with_epoch(dline_info.open);
350 (dline_info, p, v)
351}
352
353fn advance_by_deadline<F>(mut v: VM, maddr: Address, more: F) -> (VM, DeadlineInfo)
354where
355 F: Fn(DeadlineInfo) -> bool,
356{
357 loop {
358 let dline_info = miner_dline_info(&v, maddr);
359 if !more(dline_info) {
360 return (v, dline_info);
361 }
362 v = v.with_epoch(dline_info.last());
363
364 let res = v
365 .apply_message(
366 *SYSTEM_ACTOR_ADDR,
367 *CRON_ACTOR_ADDR,
368 TokenAmount::zero(),
369 CronMethod::EpochTick as u64,
370 RawBytes::default(),
371 )
372 .unwrap();
373 assert_eq!(ExitCode::OK, res.code);
374 let next = v.get_epoch() + 1;
375 v = v.with_epoch(next);
376 }
377}
378
379pub fn miner_dline_info(v: &VM, m: Address) -> DeadlineInfo {
380 let st = v.get_state::<MinerState>(m).unwrap();
381 new_deadline_info_from_offset_and_epoch(
382 &Policy::default(),
383 st.proving_period_start,
384 v.get_epoch(),
385 )
386}
387
388fn sector_deadline(v: &VM, m: Address, s: SectorNumber) -> (u64, u64) {
389 let st = v.get_state::<MinerState>(m).unwrap();
390 st.find_sector(&Policy::default(), v.store, s).unwrap()
391}
392
393pub fn check_sector_active(v: &VM, m: Address, s: SectorNumber) -> bool {
394 let (d_idx, p_idx) = sector_deadline(v, m, s);
395 let st = v.get_state::<MinerState>(m).unwrap();
396 st.check_sector_active(&Policy::default(), v.store, d_idx, p_idx, s, true).unwrap()
397}
398
399pub fn check_sector_faulty(v: &VM, m: Address, d_idx: u64, p_idx: u64, s: SectorNumber) -> bool {
400 let st = v.get_state::<MinerState>(m).unwrap();
401 let deadlines = st.load_deadlines(v.store).unwrap();
402 let deadline = deadlines.load_deadline(&Policy::default(), v.store, d_idx).unwrap();
403 let partition = deadline.load_partition(v.store, p_idx).unwrap();
404 partition.faults.get(s)
405}
406
407pub fn deadline_state(v: &VM, m: Address, d_idx: u64) -> Deadline {
408 let st = v.get_state::<MinerState>(m).unwrap();
409 let deadlines = st.load_deadlines(v.store).unwrap();
410 deadlines.load_deadline(&Policy::default(), v.store, d_idx).unwrap()
411}
412
413pub fn sector_info(v: &VM, m: Address, s: SectorNumber) -> SectorOnChainInfo {
414 let st = v.get_state::<MinerState>(m).unwrap();
415 st.get_sector(v.store, s).unwrap().unwrap()
416}
417
418pub fn miner_power(v: &VM, m: Address) -> PowerPair {
419 let st = v.get_state::<PowerState>(*STORAGE_POWER_ACTOR_ADDR).unwrap();
420 let claim = st.get_claim(v.store, &m).unwrap().unwrap();
421 PowerPair::new(claim.raw_byte_power, claim.quality_adj_power)
422}
423
424pub fn declare_recovery(
425 v: &VM,
426 worker: Address,
427 maddr: Address,
428 deadline: u64,
429 partition: u64,
430 sector_number: SectorNumber,
431) {
432 let recover_params = DeclareFaultsRecoveredParams {
433 recoveries: vec![RecoveryDeclaration {
434 deadline,
435 partition,
436 sectors: UnvalidatedBitField::Validated(
437 BitField::try_from_bits([sector_number].iter().copied()).unwrap(),
438 ),
439 }],
440 };
441
442 apply_ok(
443 v,
444 worker,
445 maddr,
446 TokenAmount::zero(),
447 MinerMethod::DeclareFaultsRecovered as u64,
448 recover_params,
449 );
450}
451
452pub fn submit_windowed_post(
453 v: &VM,
454 worker: Address,
455 maddr: Address,
456 dline_info: DeadlineInfo,
457 partition_idx: u64,
458 new_power: PowerPair,
459) {
460 let params = SubmitWindowedPoStParams {
461 deadline: dline_info.index,
462 partitions: vec![PoStPartition {
463 index: partition_idx,
464 skipped: fvm_ipld_bitfield::UnvalidatedBitField::Validated(BitField::new()),
465 }],
466 proofs: vec![PoStProof {
467 post_proof: RegisteredPoStProof::StackedDRGWindow32GiBV1,
468 proof_bytes: vec![],
469 }],
470 chain_commit_epoch: dline_info.challenge,
471 chain_commit_rand: Randomness(TEST_VM_RAND_STRING.to_owned().into_bytes()),
472 };
473 apply_ok(v, worker, maddr, TokenAmount::zero(), MinerMethod::SubmitWindowedPoSt as u64, params);
474 let mut subinvocs = None;
475 if new_power != PowerPair::zero() {
476 let update_power_params = serialize(
477 &UpdateClaimedPowerParams {
478 raw_byte_delta: new_power.raw,
479 quality_adjusted_delta: new_power.qa,
480 },
481 "update claim params",
482 )
483 .unwrap();
484 subinvocs = Some(vec![ExpectInvocation {
485 to: *STORAGE_POWER_ACTOR_ADDR,
486 method: PowerMethod::UpdateClaimedPower as u64,
487 params: Some(update_power_params),
488 ..Default::default()
489 }]);
490 }
491
492 ExpectInvocation {
493 to: maddr,
494 method: MinerMethod::SubmitWindowedPoSt as u64,
495 subinvocs,
496 ..Default::default()
497 }
498 .matches(v.take_invocations().last().unwrap());
499}
500
501pub fn submit_invalid_post(
502 v: &VM,
503 worker: Address,
504 maddr: Address,
505 dline_info: DeadlineInfo,
506 partition_idx: u64,
507) {
508 let params = SubmitWindowedPoStParams {
509 deadline: dline_info.index,
510 partitions: vec![PoStPartition {
511 index: partition_idx,
512 skipped: fvm_ipld_bitfield::UnvalidatedBitField::Validated(BitField::new()),
513 }],
514 proofs: vec![PoStProof {
515 post_proof: RegisteredPoStProof::StackedDRGWindow32GiBV1,
516 proof_bytes: TEST_VM_INVALID.as_bytes().to_vec(),
517 }],
518 chain_commit_epoch: dline_info.challenge,
519 chain_commit_rand: Randomness(TEST_VM_RAND_STRING.to_owned().into_bytes()),
520 };
521 apply_ok(v, worker, maddr, TokenAmount::zero(), MinerMethod::SubmitWindowedPoSt as u64, params);
522}
523
524pub fn add_verifier(v: &VM, verifier: Address, data_cap: StoragePower) {
525 let add_verifier_params = VerifierParams { address: verifier, allowance: data_cap };
526 let proposal = ProposeParams {
528 to: *VERIFIED_REGISTRY_ACTOR_ADDR,
529 value: TokenAmount::zero(),
530 method: VerifregMethod::AddVerifier as u64,
531 params: serialize(&add_verifier_params, "verifreg add verifier params").unwrap(),
532 };
533
534 apply_ok(
535 v,
536 TEST_VERIFREG_ROOT_SIGNER_ADDR,
537 TEST_VERIFREG_ROOT_ADDR,
538 TokenAmount::zero(),
539 MultisigMethod::Propose as u64,
540 proposal,
541 );
542 let verifreg_invoc = ExpectInvocation {
543 to: *VERIFIED_REGISTRY_ACTOR_ADDR,
544 method: VerifregMethod::AddVerifier as u64,
545 params: Some(serialize(&add_verifier_params, "verifreg add verifier params").unwrap()),
546 subinvocs: Some(vec![]),
547 ..Default::default()
548 };
549 ExpectInvocation {
550 to: TEST_VERIFREG_ROOT_ADDR,
551 method: MultisigMethod::Propose as u64,
552 subinvocs: Some(vec![verifreg_invoc]),
553 ..Default::default()
554 }
555 .matches(v.take_invocations().last().unwrap());
556}
557
558#[allow(clippy::too_many_arguments)]
559pub fn publish_deal(
560 v: &VM,
561 provider: Address,
562 deal_client: Address,
563 miner_id: Address,
564 deal_label: String,
565 piece_size: PaddedPieceSize,
566 verified_deal: bool,
567 deal_start: ChainEpoch,
568 deal_lifetime: ChainEpoch,
569) -> PublishStorageDealsReturn {
570 let label = Label::String(deal_label.to_string());
571 let deal = DealProposal {
572 piece_cid: make_piece_cid(deal_label.as_bytes()),
573 piece_size,
574 verified_deal,
575 client: deal_client,
576 provider: miner_id,
577 label,
578 start_epoch: deal_start,
579 end_epoch: deal_start + deal_lifetime,
580 storage_price_per_epoch: TokenAmount::from((1 << 20) as u64),
581 provider_collateral: TokenAmount::from(2e18 as u64),
582 client_collateral: TokenAmount::from(1e18 as u64),
583 };
584
585 let publish_params = PublishStorageDealsParams {
586 deals: vec![ClientDealProposal {
587 proposal: deal,
588 client_signature: Signature { sig_type: SignatureType::BLS, bytes: vec![] },
589 }],
590 };
591 let ret: PublishStorageDealsReturn = apply_ok(
592 v,
593 provider,
594 *STORAGE_MARKET_ACTOR_ADDR,
595 TokenAmount::zero(),
596 MarketMethod::PublishStorageDeals as u64,
597 publish_params,
598 )
599 .deserialize()
600 .unwrap();
601
602 let mut expect_publish_invocs = vec![
603 ExpectInvocation {
604 to: miner_id,
605 method: MinerMethod::ControlAddresses as u64,
606 ..Default::default()
607 },
608 ExpectInvocation {
609 to: *REWARD_ACTOR_ADDR,
610 method: RewardMethod::ThisEpochReward as u64,
611 ..Default::default()
612 },
613 ExpectInvocation {
614 to: *STORAGE_POWER_ACTOR_ADDR,
615 method: PowerMethod::CurrentTotalPower as u64,
616 ..Default::default()
617 },
618 ];
619 if verified_deal {
620 expect_publish_invocs.push(ExpectInvocation {
621 to: *VERIFIED_REGISTRY_ACTOR_ADDR,
622 method: VerifregMethod::UseBytes as u64,
623 ..Default::default()
624 })
625 }
626 ExpectInvocation {
627 to: *STORAGE_MARKET_ACTOR_ADDR,
628 method: MarketMethod::PublishStorageDeals as u64,
629 subinvocs: Some(expect_publish_invocs),
630 ..Default::default()
631 }
632 .matches(v.take_invocations().last().unwrap());
633
634 ret
635}
636
637pub fn make_bitfield(bits: &[u64]) -> UnvalidatedBitField {
638 UnvalidatedBitField::Validated(BitField::try_from_bits(bits.iter().copied()).unwrap())
639}
640
641pub fn bf_all(bf: BitField) -> Vec<u64> {
642 bf.bounded_iter(Policy::default().addressed_sectors_max).unwrap().collect()
643}
644
645pub mod invariant_failure_patterns {
646 use lazy_static::lazy_static;
647 use regex::Regex;
648 lazy_static! {
649 pub static ref REWARD_STATE_EPOCH_MISMATCH: Regex =
650 Regex::new("^reward state epoch \\d+ does not match prior_epoch\\+1 \\d+$").unwrap();
651 }
652}