test_vm/
util.rs

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
34// Generate count addresses by seeding an rng
35pub 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
44// Generate nice 32 byte arrays sampled uniformly at random based off of a u64 seed
45fn 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    // Send funds from faucet to pk address, creating account actor
60    for pk_addr in pk_addrs.clone() {
61        apply_ok(v, TEST_FAUCET_ADDR, pk_addr, balance.clone(), METHOD_SEND, RawBytes::default());
62    }
63    // Normalize pk address to return id address of account actor
64    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    // extract chain state
229    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    // keep advancing until the epoch of interest is within the deadline
305    // if e is dline.last() == dline.close -1 cron is not run
306    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        // stop if either we reach deadline of e or the proving deadline for sector s
325        (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            // in the case e is within the proving deadline don't post, leave that to the caller
330            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    // root address is msig, send proposal from root key
527    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}